scatter.py 12 KB

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