wxplot.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234
  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. self.seglist = []
  673. for point in self.mapwin.polycoords:
  674. # get value of raster cell at coordinate point
  675. ret = gcmd.RunCommand('r.what',
  676. parent = self,
  677. read = True,
  678. input = self.rasterList[0],
  679. east_north = '%d,%d' % (point[0],point[1]))
  680. val = ret.splitlines()[0].split('|')[3]
  681. if val == None or val == '*': continue
  682. val = float(val)
  683. # calculate distance between coordinate points
  684. if lasteast and lastnorth:
  685. dist = math.sqrt(math.pow((lasteast-point[0]),2) + math.pow((lastnorth-point[1]),2))
  686. cumdist += dist
  687. #store total transect length
  688. self.transect_length = cumdist
  689. # build a list of distance,value pairs for each segment of transect
  690. self.seglist.append((cumdist,val))
  691. lasteast = point[0]
  692. lastnorth = point[1]
  693. # delete first and last segment point
  694. try:
  695. self.seglist.pop(0)
  696. self.seglist.pop()
  697. except:
  698. pass
  699. #
  700. # create datalist for each raster map
  701. #
  702. for r in self.raster.iterkeys():
  703. self.raster[r]['datalist'] = []
  704. self.raster[r]['datalist'] = self.CreateDatalist(r, self.coordstr)
  705. # update title
  706. self.ptitle += ' %s ,' % r.split('@')[0]
  707. self.ptitle = self.ptitle.rstrip(',')
  708. #
  709. # set ylabel to match units if they exist
  710. #
  711. self.ylabel = ''
  712. i = 0
  713. for r in self.rasterList:
  714. if self.raster[r]['units'] != '':
  715. self.ylabel += '%s (%d),' % (r['units'], i)
  716. i += 1
  717. if self.ylabel == '':
  718. self.ylabel = _('Raster values')
  719. else:
  720. self.ylabel = self.ylabel.rstrip(',')
  721. def CreateDatalist(self, raster, coords):
  722. """!Build a list of distance, value pairs for points along transect using r.profile
  723. """
  724. datalist = []
  725. # keep total number of transect points to 500 or less to avoid
  726. # freezing with large, high resolution maps
  727. region = grass.region()
  728. curr_res = min(float(region['nsres']),float(region['ewres']))
  729. transect_rec = 0
  730. if self.transect_length / curr_res > 500:
  731. transect_res = self.transect_length / 500
  732. else: transect_res = curr_res
  733. ret = gcmd.RunCommand("r.profile",
  734. parent = self,
  735. input = raster,
  736. profile = coords,
  737. res = transect_res,
  738. null = "nan",
  739. quiet = True,
  740. read = True)
  741. if not ret:
  742. return []
  743. for line in ret.splitlines():
  744. dist, elev = line.strip().split(' ')
  745. if elev != 'nan':
  746. datalist.append((dist,elev))
  747. return datalist
  748. def OnCreateProfile(self, event):
  749. """!Main routine for creating a profile. Uses r.profile to
  750. create a list of distance,cell value pairs. This is passed to
  751. plot to create a line graph of the profile. If the profile
  752. transect is in multiple segments, these are drawn as
  753. points. Profile transect is drawn, using methods in mapdisp.py
  754. """
  755. if len(self.mapwin.polycoords) == 0 or len(self.rasterList) == 0:
  756. dlg = wx.MessageDialog(parent = self,
  757. message = _('You must draw a transect to profile in the map display window.'),
  758. caption = _('Nothing to profile'),
  759. style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
  760. dlg.ShowModal()
  761. dlg.Destroy()
  762. return
  763. self.mapwin.SetCursor(self.parent.cursors["default"])
  764. self.SetCursor(self.parent.cursors["default"])
  765. self.SetGraphStyle()
  766. self.SetupProfile()
  767. p = self.CreatePlotList()
  768. self.DrawPlot(p)
  769. # reset transect
  770. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0,0.0)
  771. self.mapwin.mouse['use'] = 'pointer'
  772. self.mapwin.mouse['box'] = 'point'
  773. def CreatePlotList(self):
  774. """!Create a plot data list from transect datalist and
  775. transect segment endpoint coordinates.
  776. """
  777. # graph the distance, value pairs for the transect
  778. self.plotlist = []
  779. # Add segment marker points to plot data list
  780. if len(self.seglist) > 0 :
  781. self.ppoints = plot.PolyMarker(self.seglist,
  782. legend = ' ' + self.properties['marker']['legend'],
  783. colour = wx.Color(self.properties['marker']['color'][0],
  784. self.properties['marker']['color'][1],
  785. self.properties['marker']['color'][2],
  786. 255),
  787. size = self.properties['marker']['size'],
  788. fillstyle = self.ptfilldict[self.properties['marker']['fill']],
  789. marker = self.properties['marker']['type'])
  790. self.plotlist.append(self.ppoints)
  791. # Add profile distance/elevation pairs to plot data list for each raster profiled
  792. for r in self.rasterList:
  793. col = wx.Color(self.raster[r]['pcolor'][0],
  794. self.raster[r]['pcolor'][1],
  795. self.raster[r]['pcolor'][2],
  796. 255)
  797. self.raster[r]['pline'] = plot.PolyLine(self.raster[r]['datalist'],
  798. colour = col,
  799. width = self.raster[r]['pwidth'],
  800. style = self.linestyledict[self.raster[r]['pstyle']],
  801. legend = self.raster[r]['plegend'])
  802. self.plotlist.append(self.raster[r]['pline'])
  803. if len(self.plotlist) > 0:
  804. return self.plotlist
  805. else:
  806. return None
  807. def Update(self):
  808. """!Update profile after changing options
  809. """
  810. self.SetGraphStyle()
  811. p = self.CreatePlotList()
  812. self.DrawPlot(p)
  813. def SaveProfileToFile(self, event):
  814. """!Save r.profile data to a csv file
  815. """
  816. wildcard = _("Comma separated value (*.csv)|*.csv")
  817. dlg = wx.FileDialog(parent = self,
  818. message = _("Path and prefix (for raster name) to save profile values..."),
  819. defaultDir = os.getcwd(),
  820. defaultFile = "", wildcard = wildcard, style = wx.SAVE)
  821. if dlg.ShowModal() == wx.ID_OK:
  822. path = dlg.GetPath()
  823. for r in self.rasterList:
  824. pfile = path+'_'+str(r['name'])+'.csv'
  825. try:
  826. file = open(pfile, "w")
  827. except IOError:
  828. wx.MessageBox(parent = self,
  829. message = _("Unable to open file <%s> for writing.") % pfile,
  830. caption = _("Error"), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  831. return False
  832. for datapair in self.raster[r]['datalist']:
  833. file.write('%d,%d\n' % (float(datapair[0]),float(datapair[1])))
  834. file.close()
  835. dlg.Destroy()
  836. class ScatterFrame(AbstractPlotFrame):
  837. """!Mainframe for displaying bivariate scatter plot of two raster maps. Uses wx.lib.plot.
  838. """
  839. def __init__(self, parent, id, pos, style, size, rasterList = []):
  840. AbstractPlotFrame.__init__(self, parent)
  841. self.toolbar = ScatterplotToolbar(parent = self)
  842. self.SetToolBar(self.toolbar)
  843. self.SetLabel(_("GRASS Bivariate Scatterplot Tool"))
  844. #
  845. # Init variables
  846. #
  847. self.rasterList = rasterList
  848. self.plottype = 'scatter'
  849. self.ptitle = _('Bivariate Scatterplot') # title of window
  850. self.xlabel = _("Raster cell values") # default X-axis label
  851. self.ylabel = _("Raster cell values") # default Y-axis label
  852. self.maptype = 'raster' # default type of scatterplot
  853. self.scattertype = 'normal'
  854. self.bins = 255
  855. self.colorList = ["blue", "red", "black", "green", "yellow", "magenta", "cyan", \
  856. "aqua", "grey", "orange", "brown", "purple", "violet", \
  857. "indigo"]
  858. if len(self.rasterList) > 1: # set raster name(s) from layer manager if a map is selected
  859. self.InitRasterOpts(self.rasterList, 'scatter')
  860. self._initOpts()
  861. def _initOpts(self):
  862. """!Initialize plot options
  863. """
  864. self.InitPlotOpts('scatter')
  865. def OnCreateScatter(self, event):
  866. """!Main routine for creating a scatterplot. Uses r.stats to
  867. create a list of cell value pairs. This is passed to
  868. plot to create a scatterplot.
  869. """
  870. self.SetCursor(self.parent.cursors["default"])
  871. self.SetGraphStyle()
  872. self.SetupScatterplot()
  873. p = self.CreatePlotList()
  874. self.DrawPlot(p)
  875. def OnSelectRaster(self, event):
  876. """!Select raster map(s) to profile
  877. """
  878. dlg = dialogs.ScatterRasterDialog(parent = self)
  879. if dlg.ShowModal() == wx.ID_OK:
  880. rlist = dlg.rasterList
  881. if rlist < 2:
  882. dlg.Destroy()
  883. return # need at least 2 rasters for scatterplot
  884. self.bins = dlg.bins # bins for r.stats with float and dcell maps
  885. self.scattertype = dlg.scattertype # scatterplot or bubbleplot
  886. self.rasterList = self.CreatePairs(rlist) # list of raster pairs (tuples)
  887. self.raster = self.InitRasterPairs(self.rasterList, 'scatter') # dictionary of raster pairs
  888. # plot histogram
  889. if len(self.rasterList) > 0:
  890. self.OnCreateScatter(event = None)
  891. dlg.Destroy()
  892. def CreatePairs(self, rlist):
  893. """!Transforms list of rasters into tuples of raster pairs
  894. """
  895. rasterList = []
  896. next = 'first'
  897. for r in rlist:
  898. if next == 'first':
  899. first = r
  900. next = 'second'
  901. else:
  902. second = r
  903. t = (first, second)
  904. rasterList.append(t)
  905. next = 'first'
  906. first = second = ''
  907. return rasterList
  908. def SetupScatterplot(self):
  909. """!Build data list for ploting each raster
  910. """
  911. #
  912. # initialize title string
  913. #
  914. self.ptitle = _('Bivariate Scatterplot of ')
  915. #
  916. # create a datalist for plotting for each raster pair
  917. #
  918. if len(self.rasterList) == 0: return # at least 1 pair of maps needed to plot
  919. for rpair in self.rasterList:
  920. self.raster[rpair]['datalist'] = self.CreateDatalist(rpair)
  921. # update title
  922. self.ptitle += '%s vs %s, ' % (rpair[0].split('@')[0], rpair[1].split('@')[0])
  923. self.ptitle = self.ptitle.strip(', ')
  924. #
  925. # set xlabel & ylabel based on raster maps of first pair to be plotted
  926. #
  927. units = self.raster[self.rasterList[0]][0]['units']
  928. if units != '' and units != '(none)' and units != None:
  929. self.xlabel = _('Raster cell values %s') % units
  930. else:
  931. self.xlabel = _('Raster cell values')
  932. units = self.raster[self.rasterList[0]][1]['units']
  933. if units != '' and units != '(none)' and units != None:
  934. self.ylabel = _('Raster cell values %s') % units
  935. else:
  936. self.ylabel = _('Raster cell values')
  937. def CreateDatalist(self, rpair):
  938. """!Build a list of cell value, frequency pairs for histogram
  939. frequency can be in cell counts, percents, or area
  940. """
  941. datalist = []
  942. if self.scattertype == 'bubble':
  943. freqflag = 'cn'
  944. else:
  945. freqflag = 'n'
  946. try:
  947. ret = gcmd.RunCommand("r.stats",
  948. parent = self,
  949. input = '%s,%s' % rpair,
  950. flags = freqflag,
  951. nsteps = self.bins,
  952. fs = ',',
  953. quiet = True,
  954. read = True)
  955. if not ret:
  956. return datalist
  957. for line in ret.splitlines():
  958. rast1, rast2 = line.strip().split(',')
  959. rast1 = rast1.strip()
  960. if '-' in rast1: rast1 = rast1.split('-')[0]
  961. rast2 = rast2.strip()
  962. if '-' in rast2: rast2 = rast2.split('-')[0]
  963. rast1 = rast1.encode('ascii', 'ignore')
  964. rast2 = rast2.encode('ascii', 'ignore')
  965. datalist.append((rast1,rast2))
  966. return datalist
  967. except gcmd.GException, e:
  968. gcmd.GError(parent = self,
  969. message = e.value)
  970. return None
  971. def CreatePlotList(self):
  972. """!Make list of elements to plot
  973. """
  974. # graph the cell value, frequency pairs for the histogram
  975. self.plotlist = []
  976. for rpair in self.rasterList:
  977. if 'datalist' not in self.raster[rpair] or \
  978. self.raster[rpair]['datalist'] == None: return
  979. if len(self.raster[rpair]['datalist']) > 0:
  980. col = wx.Color(self.raster[rpair]['pcolor'][0],
  981. self.raster[rpair]['pcolor'][1],
  982. self.raster[rpair]['pcolor'][2],
  983. 255)
  984. scatterpoints = plot.PolyMarker(self.raster[rpair]['datalist'],
  985. legend = ' ' + self.raster[rpair]['plegend'],
  986. colour = col,size = self.raster[rpair]['psize'],
  987. fillstyle = self.ptfilldict[self.raster[rpair]['pfill']],
  988. marker = self.raster[rpair]['ptype'])
  989. self.plotlist.append(scatterpoints)
  990. if len(self.plotlist) > 0:
  991. return self.plotlist
  992. else:
  993. return None
  994. def Update(self):
  995. """!Update histogram after changing options
  996. """
  997. self.SetGraphStyle()
  998. p = self.CreatePlotList()
  999. self.DrawPlot(p)