base.py 23 KB

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