base.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. """!
  2. @package wxplot.base
  3. @brief Base classes for iinteractive plotting using PyPlot
  4. Classes:
  5. - BasePlotFrame
  6. (C) 2011 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Michael Barton, Arizona State University
  10. """
  11. import os
  12. import sys
  13. import wx
  14. import wx.lib.plot as plot
  15. from core.globalvar import ETCICONDIR
  16. from core.settings import UserSettings
  17. from wxplot.dialogs import TextDialog, OptDialog
  18. import grass.script as grass
  19. class BasePlotFrame(wx.Frame):
  20. """!Abstract PyPlot display frame class"""
  21. def __init__(self, parent = None, id = wx.ID_ANY, size = (700, 300),
  22. style = wx.DEFAULT_FRAME_STYLE, rasterList = [], **kwargs):
  23. wx.Frame.__init__(self, parent, id, size = size, style = style, **kwargs)
  24. self.parent = parent # MapFrame
  25. self.mapwin = self.parent.MapWindow
  26. self.Map = Map() # instance of render.Map to be associated with display
  27. self.rasterList = rasterList #list of rasters to plot
  28. self.raster = {} # dictionary of raster maps and their plotting parameters
  29. self.plottype = ''
  30. self.linestyledict = { 'solid' : wx.SOLID,
  31. 'dot' : wx.DOT,
  32. 'long-dash' : wx.LONG_DASH,
  33. 'short-dash' : wx.SHORT_DASH,
  34. 'dot-dash' : wx.DOT_DASH }
  35. self.ptfilldict = { 'transparent' : wx.TRANSPARENT,
  36. 'solid' : wx.SOLID }
  37. #
  38. # Icon
  39. #
  40. self.SetIcon(wx.Icon(os.path.join(ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  41. #
  42. # Add statusbar
  43. #
  44. self.statusbar = self.CreateStatusBar(number = 2, style = 0)
  45. self.statusbar.SetStatusWidths([-2, -1])
  46. #
  47. # Define canvas and settings
  48. #
  49. #
  50. self.client = plot.PlotCanvas(self)
  51. #define the function for drawing pointLabels
  52. self.client.SetPointLabelFunc(self.DrawPointLabel)
  53. # Create mouse event for showing cursor coords in status bar
  54. self.client.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
  55. # Show closest point when enabled
  56. self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
  57. self.plotlist = [] # list of things to plot
  58. self.plot = None # plot draw object
  59. self.ptitle = "" # title of window
  60. self.xlabel = "" # default X-axis label
  61. self.ylabel = "" # default Y-axis label
  62. #
  63. # Bind various events
  64. #
  65. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  66. self.CentreOnScreen()
  67. self._createColorDict()
  68. def _createColorDict(self):
  69. """!Create color dictionary to return wx.Color tuples
  70. for assigning colors to images in imagery groups"""
  71. self.colorDict = {}
  72. for clr in grass.named_colors.iterkeys():
  73. if clr == 'white' or clr == 'black': continue
  74. r = grass.named_colors[clr][0] * 255
  75. g = grass.named_colors[clr][1] * 255
  76. b = grass.named_colors[clr][2] * 255
  77. self.colorDict[clr] = (r,g,b,255)
  78. def InitPlotOpts(self, plottype):
  79. """!Initialize options for entire plot
  80. """
  81. self.plottype = plottype # histogram, profile, or scatter
  82. self.properties = {} # plot properties
  83. self.properties['font'] = {}
  84. self.properties['font']['prop'] = UserSettings.Get(group = self.plottype, key = 'font')
  85. self.properties['font']['wxfont'] = wx.Font(11, wx.FONTFAMILY_SWISS,
  86. wx.FONTSTYLE_NORMAL,
  87. wx.FONTWEIGHT_NORMAL)
  88. if self.plottype == 'profile':
  89. self.properties['marker'] = UserSettings.Get(group = self.plottype, key = 'marker')
  90. # changing color string to tuple for markers/points
  91. colstr = str(self.properties['marker']['color'])
  92. self.properties['marker']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
  93. self.properties['grid'] = UserSettings.Get(group = self.plottype, key = 'grid')
  94. colstr = str(self.properties['grid']['color']) # changing color string to tuple
  95. self.properties['grid']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
  96. self.properties['x-axis'] = {}
  97. self.properties['x-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'x-axis')
  98. self.properties['x-axis']['axis'] = None
  99. self.properties['y-axis'] = {}
  100. self.properties['y-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'y-axis')
  101. self.properties['y-axis']['axis'] = None
  102. self.properties['legend'] = UserSettings.Get(group = self.plottype, key = 'legend')
  103. self.zoom = False # zooming disabled
  104. self.drag = False # draging disabled
  105. self.client.SetShowScrollbars(True) # vertical and horizontal scrollbars
  106. # x and y axis set to normal (non-log)
  107. self.client.setLogScale((False, False))
  108. if self.properties['x-axis']['prop']['type']:
  109. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  110. else:
  111. self.client.SetXSpec('auto')
  112. if self.properties['y-axis']['prop']['type']:
  113. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  114. else:
  115. self.client.SetYSpec('auto')
  116. def InitRasterOpts(self, rasterList, plottype):
  117. """!Initialize or update raster dictionary for plotting
  118. """
  119. rdict = {} # initialize a dictionary
  120. for r in rasterList:
  121. idx = rasterList.index(r)
  122. try:
  123. ret = grass.raster_info(r)
  124. except:
  125. continue
  126. # if r.info cannot parse map, skip it
  127. self.raster[r] = UserSettings.Get(group = plottype, key = 'raster') # some default settings
  128. rdict[r] = {} # initialize sub-dictionaries for each raster in the list
  129. if ret['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  130. rdict[r]['units'] = ''
  131. else:
  132. self.raster[r]['units'] = ret['units']
  133. rdict[r]['plegend'] = r.split('@')[0]
  134. rdict[r]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
  135. rdict[r]['pline'] = None
  136. rdict[r]['datatype'] = ret['datatype']
  137. rdict[r]['pwidth'] = 1
  138. rdict[r]['pstyle'] = 'solid'
  139. if idx <= len(self.colorList):
  140. rdict[r]['pcolor'] = self.colorDict[self.colorList[idx]]
  141. else:
  142. r = randint(0, 255)
  143. b = randint(0, 255)
  144. g = randint(0, 255)
  145. rdict[r]['pcolor'] = ((r,g,b,255))
  146. return rdict
  147. def InitRasterPairs(self, rasterList, plottype):
  148. """!Initialize or update raster dictionary with raster pairs for
  149. bivariate scatterplots
  150. """
  151. if len(rasterList) == 0: return
  152. rdict = {} # initialize a dictionary
  153. for rpair in rasterList:
  154. idx = rasterList.index(rpair)
  155. try:
  156. ret0 = grass.raster_info(rpair[0])
  157. ret1 = grass.raster_info(rpair[1])
  158. except:
  159. continue
  160. # if r.info cannot parse map, skip it
  161. self.raster[rpair] = UserSettings.Get(group = plottype, key = 'rasters') # some default settings
  162. rdict[rpair] = {} # initialize sub-dictionaries for each raster in the list
  163. rdict[rpair][0] = {}
  164. rdict[rpair][1] = {}
  165. if ret0['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  166. rdict[rpair][0]['units'] = ''
  167. else:
  168. self.raster[rpair][0]['units'] = ret0['units']
  169. if ret1['units'] == '(none)' or ret['units'] == '' or ret['units'] == None:
  170. rdict[rpair][1]['units'] = ''
  171. else:
  172. self.raster[rpair][1]['units'] = ret1['units']
  173. rdict[rpair]['plegend'] = rpair[0].split('@')[0] + ' vs ' + rpair[1].split('@')[0]
  174. rdict[rpair]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
  175. rdict[rpair]['ptype'] = 'dot'
  176. rdict[rpair][0]['datatype'] = ret0['datatype']
  177. rdict[rpair][1]['datatype'] = ret1['datatype']
  178. rdict[rpair]['psize'] = 1
  179. rdict[rpair]['pfill'] = 'solid'
  180. if idx <= len(self.colorList):
  181. rdict[rpair]['pcolor'] = self.colorDict[self.colorList[idx]]
  182. else:
  183. r = randint(0, 255)
  184. b = randint(0, 255)
  185. g = randint(0, 255)
  186. rdict[rpair]['pcolor'] = ((r,g,b,255))
  187. return rdict
  188. def SetGraphStyle(self):
  189. """!Set plot and text options
  190. """
  191. self.client.SetFont(self.properties['font']['wxfont'])
  192. self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
  193. self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
  194. self.client.SetEnableZoom(self.zoom)
  195. self.client.SetEnableDrag(self.drag)
  196. #
  197. # axis settings
  198. #
  199. if self.properties['x-axis']['prop']['type'] == 'custom':
  200. self.client.SetXSpec('min')
  201. else:
  202. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  203. if self.properties['y-axis']['prop']['type'] == 'custom':
  204. self.client.SetYSpec('min')
  205. else:
  206. self.client.SetYSpec(self.properties['y-axis']['prop'])
  207. if self.properties['x-axis']['prop']['type'] == 'custom' and \
  208. self.properties['x-axis']['prop']['min'] < self.properties['x-axis']['prop']['max']:
  209. self.properties['x-axis']['axis'] = (self.properties['x-axis']['prop']['min'],
  210. self.properties['x-axis']['prop']['max'])
  211. else:
  212. self.properties['x-axis']['axis'] = None
  213. if self.properties['y-axis']['prop']['type'] == 'custom' and \
  214. self.properties['y-axis']['prop']['min'] < self.properties['y-axis']['prop']['max']:
  215. self.properties['y-axis']['axis'] = (self.properties['y-axis']['prop']['min'],
  216. self.properties['y-axis']['prop']['max'])
  217. else:
  218. self.properties['y-axis']['axis'] = None
  219. self.client.SetEnableGrid(self.properties['grid']['enabled'])
  220. self.client.SetGridColour(wx.Color(self.properties['grid']['color'][0],
  221. self.properties['grid']['color'][1],
  222. self.properties['grid']['color'][2],
  223. 255))
  224. self.client.SetFontSizeLegend(self.properties['font']['prop']['legendSize'])
  225. self.client.SetEnableLegend(self.properties['legend']['enabled'])
  226. if self.properties['x-axis']['prop']['log'] == True:
  227. self.properties['x-axis']['axis'] = None
  228. self.client.SetXSpec('min')
  229. if self.properties['y-axis']['prop']['log'] == True:
  230. self.properties['y-axis']['axis'] = None
  231. self.client.SetYSpec('min')
  232. self.client.setLogScale((self.properties['x-axis']['prop']['log'],
  233. self.properties['y-axis']['prop']['log']))
  234. def DrawPlot(self, plotlist):
  235. """!Draw line and point plot from list plot elements.
  236. """
  237. self.plot = plot.PlotGraphics(plotlist,
  238. self.ptitle,
  239. self.xlabel,
  240. self.ylabel)
  241. if self.properties['x-axis']['prop']['type'] == 'custom':
  242. self.client.SetXSpec('min')
  243. else:
  244. self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
  245. if self.properties['y-axis']['prop']['type'] == 'custom':
  246. self.client.SetYSpec('min')
  247. else:
  248. self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
  249. self.client.Draw(self.plot, self.properties['x-axis']['axis'],
  250. self.properties['y-axis']['axis'])
  251. def DrawPointLabel(self, dc, mDataDict):
  252. """!This is the fuction that defines how the pointLabels are
  253. plotted dc - DC that will be passed mDataDict - Dictionary
  254. of data that you want to use for the pointLabel
  255. As an example I have decided I want a box at the curve
  256. point with some text information about the curve plotted
  257. below. Any wxDC method can be used.
  258. """
  259. dc.SetPen(wx.Pen(wx.BLACK))
  260. dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
  261. sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
  262. dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point
  263. px,py = mDataDict["pointXY"]
  264. cNum = mDataDict["curveNum"]
  265. pntIn = mDataDict["pIndex"]
  266. legend = mDataDict["legend"]
  267. #make a string to display
  268. s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
  269. dc.DrawText(s, sx , sy+1)
  270. def OnZoom(self, event):
  271. """!Enable zooming and disable dragging
  272. """
  273. self.zoom = True
  274. self.drag = False
  275. self.client.SetEnableZoom(self.zoom)
  276. self.client.SetEnableDrag(self.drag)
  277. def OnDrag(self, event):
  278. """!Enable dragging and disable zooming
  279. """
  280. self.zoom = False
  281. self.drag = True
  282. self.client.SetEnableDrag(self.drag)
  283. self.client.SetEnableZoom(self.zoom)
  284. def OnRedraw(self, event):
  285. """!Redraw the plot window. Unzoom to original size
  286. """
  287. self.client.Reset()
  288. self.client.Redraw()
  289. def OnErase(self, event):
  290. """!Erase the plot window
  291. """
  292. self.client.Clear()
  293. self.mapwin.ClearLines(self.mapwin.pdc)
  294. self.mapwin.ClearLines(self.mapwin.pdcTmp)
  295. self.mapwin.polycoords = []
  296. self.mapwin.Refresh()
  297. def SaveToFile(self, event):
  298. """!Save plot to graphics file
  299. """
  300. self.client.SaveFile()
  301. def OnMouseLeftDown(self,event):
  302. self.SetStatusText(_("Left Mouse Down at Point: (%.4f, %.4f)") % \
  303. self.client._getXY(event))
  304. event.Skip() # allows plotCanvas OnMouseLeftDown to be called
  305. def OnMotion(self, event):
  306. """!Indicate when mouse is outside the plot area
  307. """
  308. if self.client.OnLeave(event): print 'out of area'
  309. #show closest point (when enbled)
  310. if self.client.GetEnablePointLabel() == True:
  311. #make up dict with info for the pointLabel
  312. #I've decided to mark the closest point on the closest curve
  313. dlst = self.client.GetClosetPoint( self.client._getXY(event), pointScaled = True)
  314. if dlst != []: #returns [] if none
  315. curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
  316. #make up dictionary to pass to my user function (see DrawPointLabel)
  317. mDataDict = {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
  318. "pointXY":pointXY, "scaledXY":scaledXY}
  319. #pass dict to update the pointLabel
  320. self.client.UpdatePointLabel(mDataDict)
  321. event.Skip() #go to next handler
  322. def PlotOptionsMenu(self, event):
  323. """!Popup menu for plot and text options
  324. """
  325. point = wx.GetMousePosition()
  326. popt = wx.Menu()
  327. # Add items to the menu
  328. settext = wx.MenuItem(popt, wx.ID_ANY, _('Text settings'))
  329. popt.AppendItem(settext)
  330. self.Bind(wx.EVT_MENU, self.PlotText, settext)
  331. setgrid = wx.MenuItem(popt, wx.ID_ANY, _('Plot settings'))
  332. popt.AppendItem(setgrid)
  333. self.Bind(wx.EVT_MENU, self.PlotOptions, setgrid)
  334. # Popup the menu. If an item is selected then its handler
  335. # will be called before PopupMenu returns.
  336. self.PopupMenu(popt)
  337. popt.Destroy()
  338. def NotFunctional(self):
  339. """!Creates a 'not functional' message dialog
  340. """
  341. dlg = wx.MessageDialog(parent = self,
  342. message = _('This feature is not yet functional'),
  343. caption = _('Under Construction'),
  344. style = wx.OK | wx.ICON_INFORMATION)
  345. dlg.ShowModal()
  346. dlg.Destroy()
  347. def OnPlotText(self, dlg):
  348. """!Custom text settings for histogram plot.
  349. """
  350. self.ptitle = dlg.ptitle
  351. self.xlabel = dlg.xlabel
  352. self.ylabel = dlg.ylabel
  353. dlg.UpdateSettings()
  354. self.client.SetFont(self.properties['font']['wxfont'])
  355. self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
  356. self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
  357. if self.plot:
  358. self.plot.setTitle(dlg.ptitle)
  359. self.plot.setXLabel(dlg.xlabel)
  360. self.plot.setYLabel(dlg.ylabel)
  361. self.OnRedraw(event = None)
  362. def PlotText(self, event):
  363. """!Set custom text values for profile title and axis labels.
  364. """
  365. dlg = TextDialog(parent = self, id = wx.ID_ANY,
  366. plottype = self.plottype,
  367. title = _('Histogram text settings'))
  368. if dlg.ShowModal() == wx.ID_OK:
  369. self.OnPlotText(dlg)
  370. dlg.Destroy()
  371. def PlotOptions(self, event):
  372. """!Set various profile options, including: line width, color,
  373. style; marker size, color, fill, and style; grid and legend
  374. options. Calls OptDialog class.
  375. """
  376. dlg = OptDialog(parent = self, id = wx.ID_ANY,
  377. plottype = self.plottype,
  378. title = _('Plot settings'))
  379. btnval = dlg.ShowModal()
  380. if btnval == wx.ID_SAVE:
  381. dlg.UpdateSettings()
  382. self.SetGraphStyle()
  383. dlg.Destroy()
  384. elif btnval == wx.ID_CANCEL:
  385. dlg.Destroy()
  386. def PrintMenu(self, event):
  387. """!Print options and output menu
  388. """
  389. point = wx.GetMousePosition()
  390. printmenu = wx.Menu()
  391. for title, handler in ((_("Page setup"), self.OnPageSetup),
  392. (_("Print preview"), self.OnPrintPreview),
  393. (_("Print display"), self.OnDoPrint)):
  394. item = wx.MenuItem(printmenu, wx.ID_ANY, title)
  395. printmenu.AppendItem(item)
  396. self.Bind(wx.EVT_MENU, handler, item)
  397. # Popup the menu. If an item is selected then its handler
  398. # will be called before PopupMenu returns.
  399. self.PopupMenu(printmenu)
  400. printmenu.Destroy()
  401. def OnPageSetup(self, event):
  402. self.client.PageSetup()
  403. def OnPrintPreview(self, event):
  404. self.client.PrintPreview()
  405. def OnDoPrint(self, event):
  406. self.client.Printout()
  407. def OnQuit(self, event):
  408. self.Close(True)
  409. def OnCloseWindow(self, event):
  410. """!Close plot window and clean up
  411. """
  412. try:
  413. self.mapwin.ClearLines()
  414. self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0, 0.0)
  415. self.mapwin.mouse['use'] = 'pointer'
  416. self.mapwin.mouse['box'] = 'point'
  417. self.mapwin.polycoords = []
  418. self.mapwin.UpdateMap(render = False, renderVector = False)
  419. except:
  420. pass
  421. self.mapwin.SetCursor(self.Parent.cursors["default"])
  422. self.Destroy()