scatter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. """
  2. @package wxplot.scatter
  3. @brief Scatter plotting using PyPlot
  4. Classes:
  5. - scatter::ScatterFrame
  6. - scatter::ScatterToolbar
  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 sys
  13. import wx
  14. import grass.script as grass
  15. import wx.lib.plot as plot
  16. from wxplot.base import BasePlotFrame, PlotIcons
  17. from gui_core.toolbars import BaseToolbar, BaseIcons
  18. from gui_core.wrap import StockCursor
  19. from wxplot.dialogs import ScatterRasterDialog, PlotStatsFrame
  20. from core.gcmd import RunCommand, GException, GError, GMessage
  21. class ScatterFrame(BasePlotFrame):
  22. """Mainframe for displaying bivariate scatter plot of two raster maps. Uses wx.lib.plot.
  23. """
  24. def __init__(self, parent, giface, id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE,
  25. size=wx.Size(700, 400),
  26. rasterList=[], **kwargs):
  27. BasePlotFrame.__init__(self, parent, giface=giface, size=size, **kwargs)
  28. self.toolbar = ScatterToolbar(parent=self)
  29. # workaround for http://trac.wxwidgets.org/ticket/13888
  30. if sys.platform != 'darwin':
  31. self.SetToolBar(self.toolbar)
  32. self.SetTitle(_("GRASS Bivariate Scatterplot Tool"))
  33. #
  34. # Init variables
  35. #
  36. self.rasterList = rasterList
  37. self.plottype = 'scatter'
  38. self.ptitle = _('Bivariate Scatterplot') # title of window
  39. self.xlabel = _("Raster cell values") # default X-axis label
  40. self.ylabel = _("Raster cell values") # default Y-axis label
  41. self.maptype = 'raster' # default type of scatterplot
  42. self.scattertype = 'normal'
  43. self.bins = 255
  44. self.colorList = [
  45. "blue",
  46. "red",
  47. "black",
  48. "green",
  49. "yellow",
  50. "magenta",
  51. "cyan",
  52. "aqua",
  53. "grey",
  54. "orange",
  55. "brown",
  56. "purple",
  57. "violet",
  58. "indigo"]
  59. self._initOpts()
  60. if len(
  61. self.rasterList) > 1: # set raster name(s) from layer manager if a map is selected
  62. self.InitRasterOpts(self.rasterList, 'scatter')
  63. else:
  64. self.raster = {}
  65. def _initOpts(self):
  66. """Initialize plot options
  67. """
  68. self.InitPlotOpts('scatter')
  69. def OnCreateScatter(self, event):
  70. """Main routine for creating a scatterplot. Uses r.stats to
  71. create a list of cell value pairs. This is passed to
  72. plot to create a scatterplot.
  73. """
  74. self.SetCursor(StockCursor(wx.CURSOR_ARROW))
  75. self.SetGraphStyle()
  76. wx.BeginBusyCursor()
  77. wx.SafeYield()
  78. self.SetupScatterplot()
  79. p = self.CreatePlotList()
  80. if p:
  81. self.DrawPlot(p)
  82. wx.EndBusyCursor()
  83. else:
  84. wx.EndBusyCursor()
  85. GMessage(_("Nothing to plot."), parent=self)
  86. def OnSelectRaster(self, event):
  87. """Select raster map(s) to profile
  88. """
  89. dlg = ScatterRasterDialog(parent=self)
  90. dlg.CenterOnParent()
  91. if dlg.ShowModal() == wx.ID_OK:
  92. self.rasterList = dlg.GetRasterPairs()
  93. if not self.rasterList:
  94. GMessage(
  95. _("At least 2 raster maps must be specified"),
  96. parent=dlg)
  97. return
  98. # scatterplot or bubbleplot
  99. # bins for r.stats with float and dcell maps
  100. self.scattertype, self.bins = dlg.GetSettings()
  101. self.raster = self.InitRasterPairs(
  102. self.rasterList, 'scatter') # dictionary of raster pairs
  103. # plot histogram
  104. if self.rasterList:
  105. self.OnCreateScatter(event=None)
  106. dlg.Destroy()
  107. def SetupScatterplot(self):
  108. """Build data list for ploting each raster
  109. """
  110. #
  111. # initialize title string
  112. #
  113. self.ptitle = _('Bivariate Scatterplot of ')
  114. #
  115. # create a datalist for plotting for each raster pair
  116. #
  117. if len(self.rasterList) == 0:
  118. return # at least 1 pair of maps needed to plot
  119. for rpair in self.rasterList:
  120. self.raster[rpair]['datalist'] = self.CreateDatalist(rpair)
  121. # update title
  122. self.ptitle += '%s vs %s, ' % (
  123. rpair[0].split('@')[0],
  124. rpair[1].split('@')[0])
  125. self.ptitle = self.ptitle.strip(', ')
  126. #
  127. # set xlabel & ylabel based on raster maps of first pair to be plotted
  128. #
  129. self.xlabel = _('Raster <%s> cell values') % rpair[0].split('@')[0]
  130. self.ylabel = _('Raster <%s> cell values') % rpair[1].split('@')[0]
  131. units = self.raster[self.rasterList[0]][0]['units']
  132. if units != '':
  133. self.xlabel += _(': %s') % units
  134. units = self.raster[self.rasterList[0]][1]['units']
  135. if units != '':
  136. self.ylabel += _(': %s') % units
  137. def CreateDatalist(self, rpair):
  138. """Build a list of cell value, frequency pairs for histogram
  139. frequency can be in cell counts, percents, or area
  140. """
  141. datalist = []
  142. if self.scattertype == 'bubble':
  143. freqflag = 'cn'
  144. else:
  145. freqflag = 'n'
  146. try:
  147. ret = RunCommand("r.stats",
  148. parent=self,
  149. input='%s,%s' % rpair,
  150. flags=freqflag,
  151. nsteps=self.bins,
  152. sep=',',
  153. quiet=True,
  154. read=True)
  155. if not ret:
  156. return datalist
  157. for line in ret.splitlines():
  158. rast1, rast2 = line.strip().split(',')
  159. rast1 = rast1.strip()
  160. if '-' in rast1:
  161. if rast1[0] == '-':
  162. rast1 = '-' + rast1.split('-')[1]
  163. else:
  164. rast1 = rast1.split('-')[0]
  165. rast2 = rast2.strip()
  166. if '-' in rast2:
  167. if rast2[0] == '-':
  168. rast2 = '-' + rast2.split('-')[1]
  169. else:
  170. rast2 = rast2.split('-')[0]
  171. rast1 = rast1.encode('ascii', 'ignore')
  172. rast2 = rast2.encode('ascii', 'ignore')
  173. datalist.append((rast1, rast2))
  174. return datalist
  175. except GException as e:
  176. GError(parent=self,
  177. message=e.value)
  178. return None
  179. def CreatePlotList(self):
  180. """Make list of elements to plot
  181. """
  182. # graph the cell value, frequency pairs for the histogram
  183. self.plotlist = []
  184. for rpair in self.rasterList:
  185. if 'datalist' not in self.raster[rpair] or \
  186. self.raster[rpair]['datalist'] is None:
  187. continue
  188. if len(self.raster[rpair]['datalist']) > 0:
  189. col = wx.Colour(self.raster[rpair]['pcolor'][0],
  190. self.raster[rpair]['pcolor'][1],
  191. self.raster[rpair]['pcolor'][2],
  192. 255)
  193. scatterpoints = plot.PolyMarker(
  194. self.raster[rpair]['datalist'],
  195. legend=' ' + self.raster[rpair]['plegend'],
  196. colour=col, size=self.raster[rpair]['psize'],
  197. fillstyle=self.ptfilldict[self.raster[rpair]['pfill']],
  198. marker=self.raster[rpair]['ptype'])
  199. self.plotlist.append(scatterpoints)
  200. return self.plotlist
  201. def Update(self):
  202. """Update histogram after changing options
  203. """
  204. self.SetGraphStyle()
  205. p = self.CreatePlotList()
  206. if p:
  207. self.DrawPlot(p)
  208. else:
  209. GMessage(_("Nothing to plot."), parent=self)
  210. def OnRegression(self, event):
  211. """Displays regression information in messagebox
  212. """
  213. message = []
  214. title = _('Regression Statistics for Scatterplot(s)')
  215. for rpair in self.rasterList:
  216. if not isinstance(rpair, tuple):
  217. continue
  218. rast1, rast2 = rpair
  219. rast1 = rast1.split('@')[0]
  220. rast2 = rast2.split('@')[0]
  221. ret = grass.parse_command(
  222. 'r.regression.line', mapx=rast1, mapy=rast2, flags='g',
  223. quiet=True, parse=(grass.parse_key_val, {'sep': '='}))
  224. eqtitle = _('Regression equation for raster map <%(rast1)s> vs. <%(rast2)s>:\n\n') % {
  225. 'rast1': rast1, 'rast2': rast2}
  226. eq = ' %s = %s + %s(%s)\n\n' % (rast2, ret['a'], ret['b'], rast1)
  227. num = 'N = %s\n' % ret['N']
  228. rval = 'R = %s\n' % ret['R']
  229. rsq = 'R-squared = %f\n' % pow(float(ret['R']), 2)
  230. ftest = 'F = %s\n' % ret['F']
  231. str = eqtitle + eq + num + rval + rsq + ftest
  232. message.append(str)
  233. stats = PlotStatsFrame(self, id=wx.ID_ANY, message=message,
  234. title=title)
  235. if stats.Show() == wx.ID_CLOSE:
  236. stats.Destroy()
  237. class ScatterToolbar(BaseToolbar):
  238. """Toolbar for bivariate scatterplots of raster map pairs
  239. """
  240. def __init__(self, parent):
  241. BaseToolbar.__init__(self, parent)
  242. # workaround for http://trac.wxwidgets.org/ticket/13888
  243. if sys.platform == 'darwin':
  244. parent.SetToolBar(self)
  245. self.InitToolbar(self._toolbarData())
  246. # realize the toolbar
  247. self.Realize()
  248. def _toolbarData(self):
  249. """Toolbar data"""
  250. return self._getToolbarData((('addraster', BaseIcons["addRast"],
  251. self.parent.OnSelectRaster),
  252. (None, ),
  253. ('draw', PlotIcons["draw"],
  254. self.parent.OnCreateScatter),
  255. ('erase', BaseIcons["erase"],
  256. self.parent.OnErase),
  257. ('drag', BaseIcons['pan'],
  258. self.parent.OnDrag),
  259. ('zoom', BaseIcons['zoomIn'],
  260. self.parent.OnZoom),
  261. ('unzoom', BaseIcons['zoomBack'],
  262. self.parent.OnRedraw),
  263. (None, ),
  264. ('statistics', PlotIcons['statistics'],
  265. self.parent.OnRegression),
  266. ('image', BaseIcons["saveFile"],
  267. self.parent.SaveToFile),
  268. ('print', BaseIcons["print"],
  269. self.parent.PrintMenu),
  270. (None, ),
  271. ('settings', PlotIcons["options"],
  272. self.parent.PlotOptionsMenu),
  273. ('quit', PlotIcons["quit"],
  274. self.parent.OnQuit),
  275. ))