wxplot.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. """!
  2. @package wxplot.py
  3. Iinteractive plotting using PyPlot (wx.lib.plot.py).
  4. Classes:
  5. - AbstractPlotFrame
  6. - HistFrame
  7. - ProfileFrame
  8. (C) 2011 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Michael Barton, Arizona State University
  12. """
  13. import os
  14. import sys
  15. import math
  16. import wx
  17. import wx.lib.colourselect as csel
  18. import globalvar
  19. import gcmd
  20. from render import Map
  21. from toolbars import Histogram2Toolbar
  22. from toolbars import ProfileToolbar
  23. from toolbars import ScatterplotToolbar
  24. from preferences import globalSettings as UserSettings
  25. import wxplot_dialogs as dialogs
  26. from grass.script import core as grass
  27. from grass.script import raster as raster
  28. try:
  29. import numpy
  30. import wx.lib.plot as plot
  31. except ImportError:
  32. msg = _("This module requires the NumPy module, which could not be "
  33. "imported. It probably is not installed (it's not part of the "
  34. "standard Python distribution). See the Numeric Python site "
  35. "(http://numpy.scipy.org) for information on downloading source or "
  36. "binaries.")
  37. print >> sys.stderr, "wxplot.py: " + msg
  38. class AbstractPlotFrame(wx.Frame):
  39. """!Abstract PyPlot display frame class"""
  40. def __init__(self, parent = None, id = wx.ID_ANY, size = (700, 300),
  41. style = wx.DEFAULT_FRAME_STYLE, rasterList = [], **kwargs):
  42. wx.Frame.__init__(self, parent, id, size = size, style = style, **kwargs)
  43. self.parent = parent # MapFrame
  44. self.mapwin = self.parent.MapWindow
  45. self.Map = Map() # instance of render.Map to be associated with display
  46. self.rasterList = rasterList #list of rasters to plot
  47. self.raster = {} # dictionary of raster maps and their plotting parameters
  48. self.plottype = ''
  49. self.linestyledict = { 'solid' : wx.SOLID,
  50. 'dot' : wx.DOT,
  51. 'long-dash' : wx.LONG_DASH,
  52. 'short-dash' : wx.SHORT_DASH,
  53. 'dot-dash' : wx.DOT_DASH }
  54. self.ptfilldict = { 'transparent' : wx.TRANSPARENT,
  55. 'solid' : wx.SOLID }
  56. #
  57. # Icon
  58. #
  59. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  60. #
  61. # Add statusbar
  62. #
  63. self.statusbar = self.CreateStatusBar(number = 2, style = 0)
  64. self.statusbar.SetStatusWidths([-2, -1])
  65. #
  66. # Define canvas and settings
  67. #
  68. #
  69. self.client = plot.PlotCanvas(self)
  70. #define the function for drawing pointLabels
  71. self.client.SetPointLabelFunc(self.DrawPointLabel)
  72. # Create mouse event for showing cursor coords in status bar
  73. self.client.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
  74. # Show closest point when enabled
  75. self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
  76. self.plotlist = [] # list of things to plot
  77. self.plot = None # plot draw object
  78. self.ptitle = "" # title of window
  79. self.xlabel = "" # default X-axis label
  80. self.ylabel = "" # default Y-axis label
  81. #
  82. # Bind various events
  83. #
  84. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  85. self.CentreOnScreen()
  86. self._createColorDict()
  87. def _createColorDict(self):
  88. """!Create color dictionary to return wx.Color tuples
  89. for assigning colors to images in imagery groups"""
  90. self.colorDict = {}
  91. for clr in grass.named_colors.iterkeys():
  92. if clr == 'white' or clr == 'black': continue
  93. r = grass.named_colors[clr][0] * 255
  94. g = grass.named_colors[clr][1] * 255
  95. b = grass.named_colors[clr][2] * 255
  96. self.colorDict[clr] = (r,g,b,255)
  97. def InitPlotOpts(self, plottype):
  98. """!Initialize options for entire plot
  99. """
  100. self.plottype = plottype # histogram, profile, or scatter
  101. self.properties = {} # plot properties
  102. self.properties['font'] = {}
  103. self.properties['font']['prop'] = UserSettings.Get(group = self.plottype, key = 'font')
  104. self.properties['font']['wxfont'] = wx.Font(11, wx.FONTFAMILY_SWISS,
  105. wx.FONTSTYLE_NORMAL,
  106. wx.FONTWEIGHT_NORMAL)
  107. if self.plottype == 'profile':
  108. self.properties['marker'] = UserSettings.Get(group = self.plottype, key = 'marker')
  109. # changing color string to tuple for markers/points
  110. colstr = str(self.properties['marker']['color'])
  111. self.properties['marker']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
  112. self.properties['grid'] = UserSettings.Get(group = self.plottype, key = 'grid')
  113. colstr = str(self.properties['grid']['color']) # changing color string to tuple
  114. self.properties['grid']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
  115. self.properties['x-axis'] = {}
  116. self.properties['x-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'x-axis')
  117. self.properties['x-axis']['axis'] = None
  118. self.properties['y-axis'] = {}
  119. self.properties['y-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'y-axis')
  120. self.properties['y-axis']['axis'] = None
  121. self.properties['legend'] = UserSettings.Get(group = self.plottype, key = 'legend')
  122. self.zoom = False # zooming disabled
  123. self.drag = False # draging disabled
  124. self.client.SetShowScrollbars(True) # vertical and horizontal scrollbars
  125. # x and y axis set to normal (non-log)
  126. self.client.setLogScale((False, False))
  127. if self.properties['x-axis']['prop']['type']:
  128. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  129. else:
  130. self.client.SetXSpec('auto')
  131. if self.properties['y-axis']['prop']['type']:
  132. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  133. else:
  134. self.client.SetYSpec('auto')
  135. def InitRasterOpts(self, rasterList, plottype):
  136. """!Initialize or update raster dictionary for plotting
  137. """
  138. rdict = {} # initialize a dictionary
  139. for r in rasterList:
  140. idx = rasterList.index(r)
  141. try:
  142. ret = raster.raster_info(r)
  143. except:
  144. continue
  145. # if r.info cannot parse map, skip it
  146. self.raster[r] = UserSettings.Get(group = plottype, key = 'raster') # some default settings
  147. rdict[r] = {} # initialize sub-dictionaries for each raster in the list
  148. if ret['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  149. rdict[r]['units'] = ''
  150. else:
  151. self.raster[r]['units'] = ret['units']
  152. rdict[r]['plegend'] = r.split('@')[0]
  153. rdict[r]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
  154. rdict[r]['pline'] = None
  155. rdict[r]['datatype'] = ret['datatype']
  156. rdict[r]['pwidth'] = 1
  157. rdict[r]['pstyle'] = 'solid'
  158. if idx <= len(self.colorList):
  159. rdict[r]['pcolor'] = self.colorDict[self.colorList[idx]]
  160. else:
  161. r = randint(0, 255)
  162. b = randint(0, 255)
  163. g = randint(0, 255)
  164. rdict[r]['pcolor'] = ((r,g,b,255))
  165. return rdict
  166. def InitRasterPairs(self, rasterList, plottype):
  167. """!Initialize or update raster dictionary with raster pairs for
  168. bivariate scatterplots
  169. """
  170. if len(rasterList) == 0: return
  171. rdict = {} # initialize a dictionary
  172. for rpair in rasterList:
  173. idx = rasterList.index(rpair)
  174. try:
  175. ret0 = raster.raster_info(rpair[0])
  176. ret1 = raster.raster_info(rpair[1])
  177. except:
  178. continue
  179. # if r.info cannot parse map, skip it
  180. self.raster[rpair] = UserSettings.Get(group = plottype, key = 'rasters') # some default settings
  181. rdict[rpair] = {} # initialize sub-dictionaries for each raster in the list
  182. rdict[rpair][0] = {}
  183. rdict[rpair][1] = {}
  184. if ret0['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  185. rdict[rpair][0]['units'] = ''
  186. else:
  187. self.raster[rpair][0]['units'] = ret0['units']
  188. if ret1['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  189. rdict[rpair][1]['units'] = ''
  190. else:
  191. self.raster[rpair][1]['units'] = ret1['units']
  192. rdict[rpair]['plegend'] = rpair[0].split('@')[0] + ' vs ' + rpair[1].split('@')[0]
  193. rdict[rpair]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
  194. rdict[rpair]['ptype'] = 'dot'
  195. rdict[rpair][0]['datatype'] = ret0['datatype']
  196. rdict[rpair][1]['datatype'] = ret1['datatype']
  197. rdict[rpair]['psize'] = 1
  198. rdict[rpair]['pfill'] = 'solid'
  199. if idx <= len(self.colorList):
  200. rdict[rpair]['pcolor'] = self.colorDict[self.colorList[idx]]
  201. else:
  202. r = randint(0, 255)
  203. b = randint(0, 255)
  204. g = randint(0, 255)
  205. rdict[rpair]['pcolor'] = ((r,g,b,255))
  206. return rdict
  207. def SetGraphStyle(self):
  208. """!Set plot and text options
  209. """
  210. self.client.SetFont(self.properties['font']['wxfont'])
  211. self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
  212. self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
  213. self.client.SetEnableZoom(self.zoom)
  214. self.client.SetEnableDrag(self.drag)
  215. #
  216. # axis settings
  217. #
  218. if self.properties['x-axis']['prop']['type'] == 'custom':
  219. self.client.SetXSpec('min')
  220. else:
  221. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  222. if self.properties['y-axis']['prop']['type'] == 'custom':
  223. self.client.SetYSpec('min')
  224. else:
  225. self.client.SetYSpec(self.properties['y-axis']['prop'])
  226. if self.properties['x-axis']['prop']['type'] == 'custom' and \
  227. self.properties['x-axis']['prop']['min'] < self.properties['x-axis']['prop']['max']:
  228. self.properties['x-axis']['axis'] = (self.properties['x-axis']['prop']['min'],
  229. self.properties['x-axis']['prop']['max'])
  230. else:
  231. self.properties['x-axis']['axis'] = None
  232. if self.properties['y-axis']['prop']['type'] == 'custom' and \
  233. self.properties['y-axis']['prop']['min'] < self.properties['y-axis']['prop']['max']:
  234. self.properties['y-axis']['axis'] = (self.properties['y-axis']['prop']['min'],
  235. self.properties['y-axis']['prop']['max'])
  236. else:
  237. self.properties['y-axis']['axis'] = None
  238. self.client.SetEnableGrid(self.properties['grid']['enabled'])
  239. self.client.SetGridColour(wx.Color(self.properties['grid']['color'][0],
  240. self.properties['grid']['color'][1],
  241. self.properties['grid']['color'][2],
  242. 255))
  243. self.client.SetFontSizeLegend(self.properties['font']['prop']['legendSize'])
  244. self.client.SetEnableLegend(self.properties['legend']['enabled'])
  245. if self.properties['x-axis']['prop']['log'] == True:
  246. self.properties['x-axis']['axis'] = None
  247. self.client.SetXSpec('min')
  248. if self.properties['y-axis']['prop']['log'] == True:
  249. self.properties['y-axis']['axis'] = None
  250. self.client.SetYSpec('min')
  251. self.client.setLogScale((self.properties['x-axis']['prop']['log'],
  252. self.properties['y-axis']['prop']['log']))
  253. def DrawPlot(self, plotlist):
  254. """!Draw line and point plot from list plot elements.
  255. """
  256. self.plot = plot.PlotGraphics(plotlist,
  257. self.ptitle,
  258. self.xlabel,
  259. self.ylabel)
  260. if self.properties['x-axis']['prop']['type'] == 'custom':
  261. self.client.SetXSpec('min')
  262. else:
  263. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  264. if self.properties['y-axis']['prop']['type'] == 'custom':
  265. self.client.SetYSpec('min')
  266. else:
  267. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  268. self.client.Draw(self.plot, self.properties['x-axis']['axis'],
  269. self.properties['y-axis']['axis'])
  270. def DrawPointLabel(self, dc, mDataDict):
  271. """!This is the fuction that defines how the pointLabels are
  272. plotted dc - DC that will be passed mDataDict - Dictionary
  273. of data that you want to use for the pointLabel
  274. As an example I have decided I want a box at the curve
  275. point with some text information about the curve plotted
  276. below. Any wxDC method can be used.
  277. """
  278. dc.SetPen(wx.Pen(wx.BLACK))
  279. dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
  280. sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
  281. dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point
  282. px,py = mDataDict["pointXY"]
  283. cNum = mDataDict["curveNum"]
  284. pntIn = mDataDict["pIndex"]
  285. legend = mDataDict["legend"]
  286. #make a string to display
  287. s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
  288. dc.DrawText(s, sx , sy+1)
  289. def OnZoom(self, event):
  290. """!Enable zooming and disable dragging
  291. """
  292. self.zoom = True
  293. self.drag = False
  294. self.client.SetEnableZoom(self.zoom)
  295. self.client.SetEnableDrag(self.drag)
  296. def OnDrag(self, event):
  297. """!Enable dragging and disable zooming
  298. """
  299. self.zoom = False
  300. self.drag = True
  301. self.client.SetEnableDrag(self.drag)
  302. self.client.SetEnableZoom(self.zoom)
  303. def OnRedraw(self, event):
  304. """!Redraw the plot window. Unzoom to original size
  305. """
  306. self.client.Reset()
  307. self.client.Redraw()
  308. def OnErase(self, event):
  309. """!Erase the plot window
  310. """
  311. self.client.Clear()
  312. self.mapwin.ClearLines(self.mapwin.pdc)
  313. self.mapwin.ClearLines(self.mapwin.pdcTmp)
  314. self.mapwin.polycoords = []
  315. self.mapwin.Refresh()
  316. def SaveToFile(self, event):
  317. """!Save plot to graphics file
  318. """
  319. self.client.SaveFile()
  320. def OnMouseLeftDown(self,event):
  321. self.SetStatusText(_("Left Mouse Down at Point: (%.4f, %.4f)") % \
  322. self.client._getXY(event))
  323. event.Skip() # allows plotCanvas OnMouseLeftDown to be called
  324. def OnMotion(self, event):
  325. """!Indicate when mouse is outside the plot area
  326. """
  327. if self.client.OnLeave(event): print 'out of area'
  328. #show closest point (when enbled)
  329. if self.client.GetEnablePointLabel() == True:
  330. #make up dict with info for the pointLabel
  331. #I've decided to mark the closest point on the closest curve
  332. dlst = self.client.GetClosetPoint( self.client._getXY(event), pointScaled = True)
  333. if dlst != []: #returns [] if none
  334. curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
  335. #make up dictionary to pass to my user function (see DrawPointLabel)
  336. mDataDict = {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
  337. "pointXY":pointXY, "scaledXY":scaledXY}
  338. #pass dict to update the pointLabel
  339. self.client.UpdatePointLabel(mDataDict)
  340. event.Skip() #go to next handler
  341. def PlotOptionsMenu(self, event):
  342. """!Popup menu for plot and text options
  343. """
  344. point = wx.GetMousePosition()
  345. popt = wx.Menu()
  346. # Add items to the menu
  347. settext = wx.MenuItem(popt, wx.ID_ANY, _('Text settings'))
  348. popt.AppendItem(settext)
  349. self.Bind(wx.EVT_MENU, self.PlotText, settext)
  350. setgrid = wx.MenuItem(popt, wx.ID_ANY, _('Plot settings'))
  351. popt.AppendItem(setgrid)
  352. self.Bind(wx.EVT_MENU, self.PlotOptions, setgrid)
  353. # Popup the menu. If an item is selected then its handler
  354. # will be called before PopupMenu returns.
  355. self.PopupMenu(popt)
  356. popt.Destroy()
  357. def NotFunctional(self):
  358. """!Creates a 'not functional' message dialog
  359. """
  360. dlg = wx.MessageDialog(parent = self,
  361. message = _('This feature is not yet functional'),
  362. caption = _('Under Construction'),
  363. style = wx.OK | wx.ICON_INFORMATION)
  364. dlg.ShowModal()
  365. dlg.Destroy()
  366. def OnPlotText(self, dlg):
  367. """!Custom text settings for histogram plot.
  368. """
  369. self.ptitle = dlg.ptitle
  370. self.xlabel = dlg.xlabel
  371. self.ylabel = dlg.ylabel
  372. dlg.UpdateSettings()
  373. self.client.SetFont(self.properties['font']['wxfont'])
  374. self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
  375. self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
  376. if self.plot:
  377. self.plot.setTitle(dlg.ptitle)
  378. self.plot.setXLabel(dlg.xlabel)
  379. self.plot.setYLabel(dlg.ylabel)
  380. self.OnRedraw(event = None)
  381. def PlotText(self, event):
  382. """!Set custom text values for profile title and axis labels.
  383. """
  384. dlg = dialogs.TextDialog(parent = self, id = wx.ID_ANY,
  385. plottype = self.plottype,
  386. title = _('Histogram text settings'))
  387. if dlg.ShowModal() == wx.ID_OK:
  388. self.OnPlotText(dlg)
  389. dlg.Destroy()
  390. def PlotOptions(self, event):
  391. """!Set various profile options, including: line width, color,
  392. style; marker size, color, fill, and style; grid and legend
  393. options. Calls OptDialog class.
  394. """
  395. dlg = dialogs.OptDialog(parent = self, id = wx.ID_ANY,
  396. plottype = self.plottype,
  397. title = _('Plot settings'))
  398. btnval = dlg.ShowModal()
  399. if btnval == wx.ID_SAVE:
  400. dlg.UpdateSettings()
  401. self.SetGraphStyle()
  402. dlg.Destroy()
  403. elif btnval == wx.ID_CANCEL:
  404. dlg.Destroy()
  405. def PrintMenu(self, event):
  406. """!Print options and output menu
  407. """
  408. point = wx.GetMousePosition()
  409. printmenu = wx.Menu()
  410. for title, handler in ((_("Page setup"), self.OnPageSetup),
  411. (_("Print preview"), self.OnPrintPreview),
  412. (_("Print display"), self.OnDoPrint)):
  413. item = wx.MenuItem(printmenu, wx.ID_ANY, title)
  414. printmenu.AppendItem(item)
  415. self.Bind(wx.EVT_MENU, handler, item)
  416. # Popup the menu. If an item is selected then its handler
  417. # will be called before PopupMenu returns.
  418. self.PopupMenu(printmenu)
  419. printmenu.Destroy()
  420. def OnPageSetup(self, event):
  421. self.client.PageSetup()
  422. def OnPrintPreview(self, event):
  423. self.client.PrintPreview()
  424. def OnDoPrint(self, event):
  425. self.client.Printout()
  426. def OnQuit(self, event):
  427. self.Close(True)
  428. def OnCloseWindow(self, event):
  429. """!Close plot window and clean up
  430. """
  431. try:
  432. self.mapwin.ClearLines()
  433. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0, 0.0)
  434. self.mapwin.mouse['use'] = 'pointer'
  435. self.mapwin.mouse['box'] = 'point'
  436. self.mapwin.polycoords = []
  437. self.mapwin.UpdateMap(render = False, renderVector = False)
  438. except:
  439. pass
  440. self.mapwin.SetCursor(self.Parent.cursors["default"])
  441. self.Destroy()
  442. class HistFrame(AbstractPlotFrame):
  443. def __init__(self, parent, id, pos, style, size, rasterList = []):
  444. """!Mainframe for displaying histogram of raster map. Uses wx.lib.plot.
  445. """
  446. AbstractPlotFrame.__init__(self, parent)
  447. self.toolbar = Histogram2Toolbar(parent = self)
  448. self.SetToolBar(self.toolbar)
  449. self.SetLabel(_("GRASS Histogramming Tool"))
  450. #
  451. # Init variables
  452. #
  453. self.rasterList = rasterList
  454. self.plottype = 'histogram'
  455. self.group = ''
  456. self.ptitle = _('Histogram of') # title of window
  457. self.xlabel = _("Raster cell values") # default X-axis label
  458. self.ylabel = _("Cell counts") # default Y-axis label
  459. self.maptype = 'raster' # default type of histogram to plot
  460. self.histtype = 'count'
  461. self.bins = 255
  462. self.colorList = ["blue", "green", "red", "yellow", "magenta", "cyan", \
  463. "aqua", "black", "grey", "orange", "brown", "purple", "violet", \
  464. "indigo"]
  465. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  466. self.InitRasterOpts(self.rasterList, self.plottype)
  467. self._initOpts()
  468. def _initOpts(self):
  469. """!Initialize plot options
  470. """
  471. self.InitPlotOpts('histogram')
  472. def OnCreateHist(self, event):
  473. """!Main routine for creating a histogram. Uses r.stats to
  474. create a list of cell value and count/percent/area pairs. This is passed to
  475. plot to create a line graph of the histogram.
  476. """
  477. self.SetCursor(self.parent.cursors["default"])
  478. self.SetGraphStyle()
  479. self.SetupHistogram()
  480. p = self.CreatePlotList()
  481. self.DrawPlot(p)
  482. def OnSelectRaster(self, event):
  483. """!Select raster map(s) to profile
  484. """
  485. dlg = dialogs.HistRasterDialog(parent = self)
  486. if dlg.ShowModal() == wx.ID_OK:
  487. self.rasterList = dlg.rasterList
  488. self.group = dlg.group
  489. self.bins = dlg.bins
  490. self.histtype = dlg.histtype
  491. self.maptype = dlg.maptype
  492. self.raster = self.InitRasterOpts(self.rasterList, self.plottype)
  493. # plot histogram
  494. if len(self.rasterList) > 0:
  495. self.OnCreateHist(event = None)
  496. dlg.Destroy()
  497. def SetupHistogram(self):
  498. """!Build data list for ploting each raster
  499. """
  500. #
  501. # populate raster dictionary
  502. #
  503. if len(self.rasterList) == 0: return # nothing selected
  504. for r in self.rasterList:
  505. self.raster[r]['datalist'] = self.CreateDatalist(r)
  506. #
  507. # update title
  508. #
  509. if self.maptype == 'group':
  510. self.ptitle = _('Histogram of %s') % self.group.split('@')[0]
  511. else:
  512. self.ptitle = _('Histogram of %s') % self.rasterList[0].split('@')[0]
  513. #
  514. # set xlabel based on first raster map in list to be histogrammed
  515. #
  516. units = self.raster[self.rasterList[0]]['units']
  517. if units != '' and units != '(none)' and units != None:
  518. self.xlabel = _('Raster cell values %s') % units
  519. else:
  520. self.xlabel = _('Raster cell values')
  521. #
  522. # set ylabel from self.histtype
  523. #
  524. if self.histtype == 'count': self.ylabel = _('Cell counts')
  525. if self.histtype == 'percent': self.ylabel = _('Percent of total cells')
  526. if self.histtype == 'area': self.ylabel = _('Area')
  527. def CreateDatalist(self, raster):
  528. """!Build a list of cell value, frequency pairs for histogram
  529. frequency can be in cell counts, percents, or area
  530. """
  531. datalist = []
  532. if self.histtype == 'count': freqflag = 'cn'
  533. if self.histtype == 'percent': freqflag = 'pn'
  534. if self.histtype == 'area': freqflag = 'an'
  535. try:
  536. ret = gcmd.RunCommand("r.stats",
  537. parent = self,
  538. input = raster,
  539. flags = freqflag,
  540. nsteps = self.bins,
  541. fs = ',',
  542. quiet = True,
  543. read = True)
  544. if not ret:
  545. return datalist
  546. for line in ret.splitlines():
  547. cellval, histval = line.strip().split(',')
  548. histval = histval.strip()
  549. if self.raster[raster]['datatype'] != 'CELL':
  550. cellval = cellval.split('-')[0]
  551. if self.histtype == 'percent':
  552. histval = histval.rstrip('%')
  553. datalist.append((cellval,histval))
  554. return datalist
  555. except gcmd.GException, e:
  556. gcmd.GError(parent = self,
  557. message = e.value)
  558. return None
  559. def CreatePlotList(self):
  560. """!Make list of elements to plot
  561. """
  562. # graph the cell value, frequency pairs for the histogram
  563. self.plotlist = []
  564. for r in self.rasterList:
  565. if len(self.raster[r]['datalist']) > 0:
  566. col = wx.Color(self.raster[r]['pcolor'][0],
  567. self.raster[r]['pcolor'][1],
  568. self.raster[r]['pcolor'][2],
  569. 255)
  570. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  571. colour = col,
  572. width = self.raster[r]['pwidth'],
  573. style = self.linestyledict[self.raster[r]['pstyle']],
  574. legend = self.raster[r]['plegend'])
  575. self.plotlist.append(self.raster[r]['pline'])
  576. if len(self.plotlist) > 0:
  577. return self.plotlist
  578. else:
  579. return None
  580. def Update(self):
  581. """!Update histogram after changing options
  582. """
  583. self.SetGraphStyle()
  584. p = self.CreatePlotList()
  585. self.DrawPlot(p)
  586. class ProfileFrame(AbstractPlotFrame):
  587. """!Mainframe for displaying profile of one or more raster maps. Uses wx.lib.plot.
  588. """
  589. def __init__(self, parent, id, pos, style, size, rasterList = []):
  590. AbstractPlotFrame.__init__(self, parent)
  591. self.toolbar = ProfileToolbar(parent = self)
  592. self.SetToolBar(self.toolbar)
  593. self.SetLabel(_("GRASS Profile Analysis Tool"))
  594. #
  595. # Init variables
  596. #
  597. self.rasterList = rasterList
  598. self.plottype = 'profile'
  599. self.coordstr = '' # string of coordinates for r.profile
  600. self.seglist = [] # segment endpoint list
  601. self.ppoints = '' # segment endpoints data
  602. self.transect_length = 0.0 # total transect length
  603. self.ptitle = _('Profile of') # title of window
  604. self.raster = {}
  605. self.colorList = ["blue", "red", "green", "yellow", "magenta", "cyan", \
  606. "aqua", "black", "grey", "orange", "brown", "purple", "violet", \
  607. "indigo"]
  608. if len(self.rasterList) > 0: # set raster name(s) from layer manager if a map is selected
  609. self.InitRasterOpts(self.rasterList, self.plottype)
  610. self._initOpts()
  611. # determine units (axis labels)
  612. if self.parent.Map.projinfo['units'] != '':
  613. self.xlabel = _('Distance (%s)') % self.parent.Map.projinfo['units']
  614. else:
  615. self.xlabel = _("Distance along transect")
  616. self.ylabel = _("Cell values")
  617. def _initOpts(self):
  618. """!Initialize plot options
  619. """
  620. self.InitPlotOpts('profile')
  621. def OnDrawTransect(self, event):
  622. """!Draws transect to profile in map display
  623. """
  624. self.mapwin.polycoords = []
  625. self.seglist = []
  626. self.mapwin.ClearLines(self.mapwin.pdc)
  627. self.ppoints = ''
  628. self.parent.SetFocus()
  629. self.parent.Raise()
  630. self.mapwin.mouse['use'] = 'profile'
  631. self.mapwin.mouse['box'] = 'line'
  632. self.mapwin.pen = wx.Pen(colour = 'Red', width = 2, style = wx.SHORT_DASH)
  633. self.mapwin.polypen = wx.Pen(colour = 'dark green', width = 2, style = wx.SHORT_DASH)
  634. self.mapwin.SetCursor(self.Parent.cursors["cross"])
  635. def OnSelectRaster(self, event):
  636. """!Select raster map(s) to profile
  637. """
  638. dlg = dialogs.ProfileRasterDialog(parent = self)
  639. if dlg.ShowModal() == wx.ID_OK:
  640. self.rasterList = dlg.rasterList
  641. self.raster = self.InitRasterOpts(self.rasterList, self.plottype)
  642. # plot profile
  643. if len(self.mapwin.polycoords) > 0 and len(self.rasterList) > 0:
  644. self.OnCreateProfile(event = None)
  645. dlg.Destroy()
  646. def SetupProfile(self):
  647. """!Create coordinate string for profiling. Create segment list for
  648. transect segment markers.
  649. """
  650. #
  651. # create list of coordinate points for r.profile
  652. #
  653. dist = 0
  654. cumdist = 0
  655. self.coordstr = ''
  656. lasteast = lastnorth = None
  657. if len(self.mapwin.polycoords) > 0:
  658. for point in self.mapwin.polycoords:
  659. # build string of coordinate points for r.profile
  660. if self.coordstr == '':
  661. self.coordstr = '%d,%d' % (point[0], point[1])
  662. else:
  663. self.coordstr = '%s,%d,%d' % (self.coordstr, point[0], point[1])
  664. if len(self.rasterList) == 0:
  665. return
  666. # title of window
  667. self.ptitle = _('Profile of')
  668. #
  669. # create list of coordinates for transect segment markers
  670. #
  671. if len(self.mapwin.polycoords) > 0:
  672. for point in self.mapwin.polycoords:
  673. # get value of raster cell at coordinate point
  674. ret = gcmd.RunCommand('r.what',
  675. parent = self,
  676. read = True,
  677. input = self.rasterList[0],
  678. east_north = '%d,%d' % (point[0],point[1]))
  679. val = float(ret.splitlines()[0].split('|')[3])
  680. # calculate distance between coordinate points
  681. if lasteast and lastnorth:
  682. dist = math.sqrt(math.pow((lasteast-point[0]),2) + math.pow((lastnorth-point[1]),2))
  683. cumdist += dist
  684. #store total transect length
  685. self.transect_length = cumdist
  686. # build a list of distance,value pairs for each segment of transect
  687. self.seglist.append((cumdist,val))
  688. lasteast = point[0]
  689. lastnorth = point[1]
  690. # delete first and last segment point
  691. try:
  692. self.seglist.pop(0)
  693. self.seglist.pop()
  694. except:
  695. pass
  696. #
  697. # create datalist for each raster map
  698. #
  699. for r in self.raster.iterkeys():
  700. self.raster[r]['datalist'] = []
  701. self.raster[r]['datalist'] = self.CreateDatalist(r, self.coordstr)
  702. # update title
  703. self.ptitle += ' %s ,' % r.split('@')[0]
  704. self.ptitle = self.ptitle.rstrip(',')
  705. #
  706. # set ylabel to match units if they exist
  707. #
  708. self.ylabel = ''
  709. i = 0
  710. for r in self.rasterList:
  711. if self.raster[r]['units'] != '':
  712. self.ylabel += '%s (%d),' % (r['units'], i)
  713. i += 1
  714. if self.ylabel == '':
  715. self.ylabel = _('Raster values')
  716. else:
  717. self.ylabel = self.ylabel.rstrip(',')
  718. def CreateDatalist(self, raster, coords):
  719. """!Build a list of distance, value pairs for points along transect using r.profile
  720. """
  721. datalist = []
  722. # keep total number of transect points to 500 or less to avoid
  723. # freezing with large, high resolution maps
  724. region = grass.region()
  725. curr_res = min(float(region['nsres']),float(region['ewres']))
  726. transect_rec = 0
  727. if self.transect_length / curr_res > 500:
  728. transect_res = self.transect_length / 500
  729. else: transect_res = curr_res
  730. ret = gcmd.RunCommand("r.profile",
  731. parent = self,
  732. input = raster,
  733. profile = coords,
  734. res = transect_res,
  735. null = "nan",
  736. quiet = True,
  737. read = True)
  738. if not ret:
  739. return []
  740. for line in ret.splitlines():
  741. dist, elev = line.strip().split(' ')
  742. if elev != 'nan':
  743. datalist.append((dist,elev))
  744. return datalist
  745. def OnCreateProfile(self, event):
  746. """!Main routine for creating a profile. Uses r.profile to
  747. create a list of distance,cell value pairs. This is passed to
  748. plot to create a line graph of the profile. If the profile
  749. transect is in multiple segments, these are drawn as
  750. points. Profile transect is drawn, using methods in mapdisp.py
  751. """
  752. if len(self.mapwin.polycoords) == 0 or len(self.rasterList) == 0:
  753. dlg = wx.MessageDialog(parent = self,
  754. message = _('You must draw a transect to profile in the map display window.'),
  755. caption = _('Nothing to profile'),
  756. style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  757. dlg.ShowModal()
  758. dlg.Destroy()
  759. return
  760. self.mapwin.SetCursor(self.parent.cursors["default"])
  761. self.SetCursor(self.parent.cursors["default"])
  762. self.SetGraphStyle()
  763. self.SetupProfile()
  764. p = self.CreatePlotList()
  765. self.DrawPlot(p)
  766. # reset transect
  767. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0,0.0)
  768. self.mapwin.mouse['use'] = 'pointer'
  769. self.mapwin.mouse['box'] = 'point'
  770. def CreatePlotList(self):
  771. """!Create a plot data list from transect datalist and
  772. transect segment endpoint coordinates.
  773. """
  774. # graph the distance, value pairs for the transect
  775. self.plotlist = []
  776. # Add segment marker points to plot data list
  777. if len(self.seglist) > 0 :
  778. self.ppoints = plot.PolyMarker(self.seglist,
  779. legend = ' ' + self.properties['marker']['legend'],
  780. colour = wx.Color(self.properties['marker']['color'][0],
  781. self.properties['marker']['color'][1],
  782. self.properties['marker']['color'][2],
  783. 255),
  784. size = self.properties['marker']['size'],
  785. fillstyle = self.ptfilldict[self.properties['marker']['fill']],
  786. marker = self.properties['marker']['type'])
  787. self.plotlist.append(self.ppoints)
  788. # Add profile distance/elevation pairs to plot data list for each raster profiled
  789. for r in self.rasterList:
  790. col = wx.Color(self.raster[r]['pcolor'][0],
  791. self.raster[r]['pcolor'][1],
  792. self.raster[r]['pcolor'][2],
  793. 255)
  794. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  795. colour = col,
  796. width = self.raster[r]['pwidth'],
  797. style = self.linestyledict[self.raster[r]['pstyle']],
  798. legend = self.raster[r]['plegend'])
  799. self.plotlist.append(self.raster[r]['pline'])
  800. if len(self.plotlist) > 0:
  801. return self.plotlist
  802. else:
  803. return None
  804. def Update(self):
  805. """!Update profile after changing options
  806. """
  807. self.SetGraphStyle()
  808. p = self.CreatePlotList()
  809. self.DrawPlot(p)
  810. def SaveProfileToFile(self, event):
  811. """!Save r.profile data to a csv file
  812. """
  813. wildcard = _("Comma separated value (*.csv)|*.csv")
  814. dlg = wx.FileDialog(parent = self,
  815. message = _("Path and prefix (for raster name) to save profile values..."),
  816. defaultDir = os.getcwd(),
  817. defaultFile = "", wildcard = wildcard, style = wx.SAVE)
  818. if dlg.ShowModal() == wx.ID_OK:
  819. path = dlg.GetPath()
  820. for r in self.rasterList:
  821. pfile = path+'_'+str(r['name'])+'.csv'
  822. try:
  823. file = open(pfile, "w")
  824. except IOError:
  825. wx.MessageBox(parent = self,
  826. message = _("Unable to open file <%s> for writing.") % pfile,
  827. caption = _("Error"), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  828. return False
  829. for datapair in self.raster[r]['datalist']:
  830. file.write('%d,%d\n' % (float(datapair[0]),float(datapair[1])))
  831. file.close()
  832. dlg.Destroy()
  833. class ScatterFrame(AbstractPlotFrame):
  834. """!Mainframe for displaying bivariate scatter plot of two raster maps. Uses wx.lib.plot.
  835. """
  836. def __init__(self, parent, id, pos, style, size, rasterList = []):
  837. AbstractPlotFrame.__init__(self, parent)
  838. self.toolbar = ScatterplotToolbar(parent = self)
  839. self.SetToolBar(self.toolbar)
  840. self.SetLabel(_("GRASS Bivariate Scatterplot Tool"))
  841. #
  842. # Init variables
  843. #
  844. self.rasterList = rasterList
  845. self.plottype = 'scatter'
  846. self.ptitle = _('Bivariate Scatterplot') # title of window
  847. self.xlabel = _("Raster cell values") # default X-axis label
  848. self.ylabel = _("Raster cell values") # default Y-axis label
  849. self.maptype = 'raster' # default type of scatterplot
  850. self.scattertype = 'normal'
  851. self.bins = 255
  852. self.colorList = ["blue", "red", "black", "green", "yellow", "magenta", "cyan", \
  853. "aqua", "grey", "orange", "brown", "purple", "violet", \
  854. "indigo"]
  855. if len(self.rasterList) > 1: # set raster name(s) from layer manager if a map is selected
  856. self.InitRasterOpts(self.rasterList, 'scatter')
  857. self._initOpts()
  858. def _initOpts(self):
  859. """!Initialize plot options
  860. """
  861. self.InitPlotOpts('scatter')
  862. def OnCreateScatter(self, event):
  863. """!Main routine for creating a scatterplot. Uses r.stats to
  864. create a list of cell value pairs. This is passed to
  865. plot to create a scatterplot.
  866. """
  867. self.SetCursor(self.parent.cursors["default"])
  868. self.SetGraphStyle()
  869. self.SetupScatterplot()
  870. p = self.CreatePlotList()
  871. self.DrawPlot(p)
  872. def OnSelectRaster(self, event):
  873. """!Select raster map(s) to profile
  874. """
  875. dlg = dialogs.ScatterRasterDialog(parent = self)
  876. if dlg.ShowModal() == wx.ID_OK:
  877. rlist = dlg.rasterList
  878. if rlist < 2:
  879. dlg.Destroy()
  880. return # need at least 2 rasters for scatterplot
  881. self.bins = dlg.bins # bins for r.stats with float and dcell maps
  882. self.scattertype = dlg.scattertype # scatterplot or bubbleplot
  883. self.rasterList = self.CreatePairs(rlist) # list of raster pairs (tuples)
  884. self.raster = self.InitRasterPairs(self.rasterList, 'scatter') # dictionary of raster pairs
  885. # plot histogram
  886. if len(self.rasterList) > 0:
  887. self.OnCreateScatter(event = None)
  888. dlg.Destroy()
  889. def CreatePairs(self, rlist):
  890. """!Transforms list of rasters into tuples of raster pairs
  891. """
  892. rasterList = []
  893. next = 'first'
  894. for r in rlist:
  895. if next == 'first':
  896. first = r
  897. next = 'second'
  898. else:
  899. second = r
  900. t = (first, second)
  901. rasterList.append(t)
  902. next = 'first'
  903. first = second = ''
  904. return rasterList
  905. def SetupScatterplot(self):
  906. """!Build data list for ploting each raster
  907. """
  908. #
  909. # initialize title string
  910. #
  911. self.ptitle = _('Bivariate Scatterplot of ')
  912. #
  913. # create a datalist for plotting for each raster pair
  914. #
  915. if len(self.rasterList) == 0: return # at least 1 pair of maps needed to plot
  916. for rpair in self.rasterList:
  917. self.raster[rpair]['datalist'] = self.CreateDatalist(rpair)
  918. # update title
  919. self.ptitle += '%s vs %s, ' % (rpair[0].split('@')[0], rpair[1].split('@')[0])
  920. self.ptitle = self.ptitle.strip(', ')
  921. #
  922. # set xlabel & ylabel based on raster maps of first pair to be plotted
  923. #
  924. units = self.raster[self.rasterList[0]][0]['units']
  925. if units != '' and units != '(none)' and units != None:
  926. self.xlabel = _('Raster cell values %s') % units
  927. else:
  928. self.xlabel = _('Raster cell values')
  929. units = self.raster[self.rasterList[0]][1]['units']
  930. if units != '' and units != '(none)' and units != None:
  931. self.ylabel = _('Raster cell values %s') % units
  932. else:
  933. self.ylabel = _('Raster cell values')
  934. def CreateDatalist(self, rpair):
  935. """!Build a list of cell value, frequency pairs for histogram
  936. frequency can be in cell counts, percents, or area
  937. """
  938. datalist = []
  939. if self.scattertype == 'bubble':
  940. freqflag = 'cn'
  941. else:
  942. freqflag = 'n'
  943. try:
  944. ret = gcmd.RunCommand("r.stats",
  945. parent = self,
  946. input = '%s,%s' % rpair,
  947. flags = freqflag,
  948. nsteps = self.bins,
  949. fs = ',',
  950. quiet = True,
  951. read = True)
  952. if not ret:
  953. return datalist
  954. for line in ret.splitlines():
  955. rast1, rast2 = line.strip().split(',')
  956. rast1 = rast1.strip()
  957. if '-' in rast1: rast1 = rast1.split('-')[0]
  958. rast2 = rast2.strip()
  959. if '-' in rast2: rast2 = rast2.split('-')[0]
  960. rast1 = rast1.encode('ascii', 'ignore')
  961. rast2 = rast2.encode('ascii', 'ignore')
  962. datalist.append((rast1,rast2))
  963. return datalist
  964. except gcmd.GException, e:
  965. gcmd.GError(parent = self,
  966. message = e.value)
  967. return None
  968. def CreatePlotList(self):
  969. """!Make list of elements to plot
  970. """
  971. # graph the cell value, frequency pairs for the histogram
  972. self.plotlist = []
  973. for rpair in self.rasterList:
  974. if 'datalist' not in self.raster[rpair] or \
  975. self.raster[rpair]['datalist'] == None: return
  976. if len(self.raster[rpair]['datalist']) > 0:
  977. col = wx.Color(self.raster[rpair]['pcolor'][0],
  978. self.raster[rpair]['pcolor'][1],
  979. self.raster[rpair]['pcolor'][2],
  980. 255)
  981. scatterpoints = plot.PolyMarker(self.raster[rpair]['datalist'],
  982. legend = ' ' + self.raster[rpair]['plegend'],
  983. colour = col,size = self.raster[rpair]['psize'],
  984. fillstyle = self.ptfilldict[self.raster[rpair]['pfill']],
  985. marker = self.raster[rpair]['ptype'])
  986. self.plotlist.append(scatterpoints)
  987. if len(self.plotlist) > 0:
  988. return self.plotlist
  989. else:
  990. return None
  991. def Update(self):
  992. """!Update histogram after changing options
  993. """
  994. self.SetGraphStyle()
  995. p = self.CreatePlotList()
  996. self.DrawPlot(p)