base.py 20 KB

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