frame.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. #!/usr/bin/env python
  2. """
  3. @package frame
  4. @brief Temporal Plot Tool
  5. Classes:
  6. - frame::DataCursor
  7. - frame::TplotFrame
  8. - frame::LookUp
  9. (C) 2012-2016 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Luca Delucchi
  13. @author start stvds support Matej Krejci
  14. """
  15. import os
  16. import six
  17. from itertools import cycle
  18. import numpy as np
  19. import wx
  20. from grass.pygrass.modules import Module
  21. import grass.script as grass
  22. from core.utils import _
  23. from functools import reduce
  24. try:
  25. import matplotlib
  26. # The recommended way to use wx with mpl is with the WXAgg
  27. # backend.
  28. matplotlib.use('WXAgg')
  29. from matplotlib.figure import Figure
  30. from matplotlib.backends.backend_wxagg import \
  31. FigureCanvasWxAgg as FigCanvas, \
  32. NavigationToolbar2WxAgg as NavigationToolbar
  33. import matplotlib.dates as mdates
  34. from matplotlib import cbook
  35. except ImportError as e:
  36. raise ImportError(_('The Temporal Plot Tool needs the "matplotlib" '
  37. '(python-matplotlib) package to be installed. {0}').format(e))
  38. from core.utils import _
  39. import grass.temporal as tgis
  40. from core.gcmd import GMessage, GError, GException, RunCommand
  41. from gui_core.widgets import CoordinatesValidator
  42. from gui_core import gselect
  43. from core import globalvar
  44. from grass.pygrass.vector.geometry import Point
  45. from grass.pygrass.raster import RasterRow
  46. from grass.pygrass.gis.region import Region
  47. from collections import OrderedDict
  48. from subprocess import PIPE
  49. try:
  50. import wx.lib.agw.flatnotebook as FN
  51. except ImportError:
  52. import wx.lib.flatnotebook as FN
  53. import wx.lib.filebrowsebutton as filebrowse
  54. from gui_core.widgets import GNotebook
  55. from gui_core.wrap import TextCtrl, Button, StaticText
  56. ALPHA = 0.5
  57. COLORS = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
  58. def check_version(*version):
  59. """Checks if given version or newer is installed"""
  60. versionInstalled = []
  61. for i in matplotlib.__version__.split('.'):
  62. try:
  63. v = int(i)
  64. versionInstalled.append(v)
  65. except ValueError:
  66. versionInstalled.append(0)
  67. if versionInstalled < list(version):
  68. return False
  69. else:
  70. return True
  71. def findBetween(s, first, last):
  72. try:
  73. start = s.rindex(first) + len(first)
  74. end = s.rindex(last, start)
  75. return s[start:end]
  76. except ValueError:
  77. return ""
  78. class TplotFrame(wx.Frame):
  79. """The main frame of the application"""
  80. def __init__(self, parent, giface):
  81. wx.Frame.__init__(self, parent, id=wx.ID_ANY,
  82. title=_("GRASS GIS Temporal Plot Tool"))
  83. tgis.init(True)
  84. self._giface = giface
  85. self.datasetsV = None
  86. self.datasetsR = None
  87. self.overwrite = False
  88. # self.vectorDraw=False
  89. # self.rasterDraw=False
  90. self.init()
  91. self._layout()
  92. # We create a database interface here to speedup the GUI
  93. self.dbif = tgis.SQLDatabaseInterfaceConnection()
  94. self.dbif.connect()
  95. self.Bind(wx.EVT_CLOSE, self.onClose)
  96. self.region = Region()
  97. def init(self):
  98. self.timeDataR = OrderedDict()
  99. self.timeDataV = OrderedDict()
  100. self.temporalType = None
  101. self.unit = None
  102. self.listWhereConditions = []
  103. self.plotNameListR = []
  104. self.plotNameListV = []
  105. self.poi = None
  106. self.csvpath = None
  107. def __del__(self):
  108. """Close the database interface and stop the messenger and C-interface
  109. subprocesses.
  110. """
  111. if self.dbif.connected is True:
  112. self.dbif.close()
  113. tgis.stop_subprocesses()
  114. def onClose(self, evt):
  115. if self._giface.GetMapDisplay():
  116. self.coorval.OnClose()
  117. self.cats.OnClose()
  118. self.__del__()
  119. self.Destroy()
  120. def _layout(self):
  121. """Creates the main panel with all the controls on it:
  122. * mpl canvas
  123. * mpl navigation toolbar
  124. * Control panel for interaction
  125. """
  126. self.mainPanel = wx.Panel(self)
  127. # Create the mpl Figure and FigCanvas objects.
  128. # 5x4 inches, 100 dots-per-inch
  129. #
  130. # color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
  131. # ------------CANVAS AND TOOLBAR------------
  132. self.fig = Figure((5.0, 4.0), facecolor=(1, 1, 1))
  133. self.canvas = FigCanvas(self.mainPanel, wx.ID_ANY, self.fig)
  134. # axes are initialized later
  135. self.axes2d = None
  136. # Create the navigation toolbar, tied to the canvas
  137. #
  138. self.toolbar = NavigationToolbar(self.canvas)
  139. #
  140. # Layout
  141. #
  142. # ------------MAIN VERTICAL SIZER------------
  143. self.vbox = wx.BoxSizer(wx.VERTICAL)
  144. self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
  145. self.vbox.Add(self.toolbar, 0, wx.EXPAND)
  146. # self.vbox.AddSpacer(10)
  147. # ------------ADD NOTEBOOK------------
  148. self.ntb = GNotebook(parent=self.mainPanel, style=FN.FNB_FANCY_TABS)
  149. # ------------ITEMS IN NOTEBOOK PAGE (RASTER)------------------------
  150. self.controlPanelRaster = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  151. self.datasetSelectLabelR = StaticText(
  152. parent=self.controlPanelRaster,
  153. id=wx.ID_ANY,
  154. label=_(
  155. 'Raster temporal '
  156. 'dataset (strds)\n'
  157. 'Press ENTER after'
  158. ' typing the name or select'
  159. ' with the combobox'))
  160. self.datasetSelectR = gselect.Select(
  161. parent=self.controlPanelRaster, id=wx.ID_ANY,
  162. size=globalvar.DIALOG_GSELECT_SIZE, type='strds', multiple=True)
  163. self.coor = StaticText(parent=self.controlPanelRaster, id=wx.ID_ANY,
  164. label=_('X and Y coordinates separated by '
  165. 'comma:'))
  166. try:
  167. self._giface.GetMapWindow()
  168. self.coorval = gselect.CoordinatesSelect(
  169. parent=self.controlPanelRaster, giface=self._giface)
  170. except:
  171. self.coorval = TextCtrl(parent=self.controlPanelRaster,
  172. id=wx.ID_ANY,
  173. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  174. validator=CoordinatesValidator())
  175. self.coorval.SetToolTip(_("Coordinates can be obtained for example"
  176. " by right-clicking on Map Display."))
  177. self.controlPanelSizerRaster = wx.BoxSizer(wx.VERTICAL)
  178. # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
  179. # label=_("Select space time raster dataset(s):")),
  180. # pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  181. self.controlPanelSizerRaster.Add(self.datasetSelectLabelR,
  182. flag=wx.EXPAND)
  183. self.controlPanelSizerRaster.Add(self.datasetSelectR, flag=wx.EXPAND)
  184. self.controlPanelSizerRaster.Add(self.coor, flag=wx.EXPAND)
  185. self.controlPanelSizerRaster.Add(self.coorval, flag=wx.EXPAND)
  186. self.controlPanelRaster.SetSizer(self.controlPanelSizerRaster)
  187. self.controlPanelSizerRaster.Fit(self)
  188. self.ntb.AddPage(page=self.controlPanelRaster, text=_('STRDS'),
  189. name='STRDS')
  190. # ------------ITEMS IN NOTEBOOK PAGE (VECTOR)------------------------
  191. self.controlPanelVector = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  192. self.datasetSelectLabelV = StaticText(
  193. parent=self.controlPanelVector, id=wx.ID_ANY,
  194. label=_(
  195. 'Vector temporal '
  196. 'dataset (stvds)\n'
  197. 'Press ENTER after'
  198. ' typing the name or select'
  199. ' with the combobox'))
  200. self.datasetSelectV = gselect.Select(
  201. parent=self.controlPanelVector, id=wx.ID_ANY,
  202. size=globalvar.DIALOG_GSELECT_SIZE, type='stvds', multiple=True)
  203. self.datasetSelectV.Bind(wx.EVT_TEXT,
  204. self.OnVectorSelected)
  205. self.attribute = gselect.ColumnSelect(parent=self.controlPanelVector)
  206. self.attributeLabel = StaticText(parent=self.controlPanelVector,
  207. id=wx.ID_ANY,
  208. label=_('Select attribute column'))
  209. # TODO fix the category selection as done for coordinates
  210. try:
  211. self._giface.GetMapWindow()
  212. self.cats = gselect.VectorCategorySelect(
  213. parent=self.controlPanelVector, giface=self._giface)
  214. except:
  215. self.cats = TextCtrl(
  216. parent=self.controlPanelVector,
  217. id=wx.ID_ANY,
  218. size=globalvar.DIALOG_TEXTCTRL_SIZE)
  219. self.catsLabel = StaticText(parent=self.controlPanelVector,
  220. id=wx.ID_ANY,
  221. label=_('Select category of vector(s)'))
  222. self.controlPanelSizerVector = wx.BoxSizer(wx.VERTICAL)
  223. # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
  224. # label=_("Select space time raster dataset(s):")),
  225. # pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  226. self.controlPanelSizerVector.Add(self.datasetSelectLabelV,
  227. flag=wx.EXPAND)
  228. self.controlPanelSizerVector.Add(self.datasetSelectV, flag=wx.EXPAND)
  229. self.controlPanelSizerVector.Add(self.attributeLabel, flag=wx.EXPAND)
  230. self.controlPanelSizerVector.Add(self.attribute, flag=wx.EXPAND)
  231. self.controlPanelSizerVector.Add(self.catsLabel, flag=wx.EXPAND)
  232. self.controlPanelSizerVector.Add(self.cats, flag=wx.EXPAND)
  233. self.controlPanelVector.SetSizer(self.controlPanelSizerVector)
  234. self.controlPanelSizerVector.Fit(self)
  235. self.ntb.AddPage(page=self.controlPanelVector, text=_('STVDS'),
  236. name='STVDS')
  237. # ------------ITEMS IN NOTEBOOK PAGE (LABELS)------------------------
  238. self.controlPanelLabels = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  239. self.titleLabel = StaticText(parent=self.controlPanelLabels,
  240. id=wx.ID_ANY,
  241. label=_('Set title for the plot'))
  242. self.title = TextCtrl(parent=self.controlPanelLabels, id=wx.ID_ANY,
  243. size=globalvar.DIALOG_TEXTCTRL_SIZE)
  244. self.xLabel = StaticText(parent=self.controlPanelLabels,
  245. id=wx.ID_ANY,
  246. label=_('Set label for X axis'))
  247. self.x = TextCtrl(parent=self.controlPanelLabels, id=wx.ID_ANY,
  248. size=globalvar.DIALOG_TEXTCTRL_SIZE)
  249. self.yLabel = StaticText(parent=self.controlPanelLabels,
  250. id=wx.ID_ANY,
  251. label=_('Set label for Y axis'))
  252. self.y = TextCtrl(parent=self.controlPanelLabels, id=wx.ID_ANY,
  253. size=globalvar.DIALOG_TEXTCTRL_SIZE)
  254. self.controlPanelSizerLabels = wx.BoxSizer(wx.VERTICAL)
  255. self.controlPanelSizerLabels.Add(self.titleLabel, flag=wx.EXPAND)
  256. self.controlPanelSizerLabels.Add(self.title, flag=wx.EXPAND)
  257. self.controlPanelSizerLabels.Add(self.xLabel, flag=wx.EXPAND)
  258. self.controlPanelSizerLabels.Add(self.x, flag=wx.EXPAND)
  259. self.controlPanelSizerLabels.Add(self.yLabel, flag=wx.EXPAND)
  260. self.controlPanelSizerLabels.Add(self.y, flag=wx.EXPAND)
  261. self.controlPanelLabels.SetSizer(self.controlPanelSizerLabels)
  262. self.controlPanelSizerLabels.Fit(self)
  263. self.ntb.AddPage(page=self.controlPanelLabels, text=_('Labels'),
  264. name='Labels')
  265. # ------------ITEMS IN NOTEBOOK PAGE (EXPORT)------------------------
  266. self.controlPanelExport = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  267. self.csvLabel = StaticText(parent=self.controlPanelExport,
  268. id=wx.ID_ANY,
  269. label=_('Path for output CSV file '
  270. 'with plotted data'))
  271. self.csvButton = filebrowse.FileBrowseButton(parent=self.controlPanelExport,
  272. id=wx.ID_ANY,
  273. size=globalvar.DIALOG_GSELECT_SIZE,
  274. labelText='',
  275. dialogTitle=_('CVS path'),
  276. buttonText=_('Browse'),
  277. startDirectory=os.getcwd(),
  278. fileMode=wx.FD_SAVE)
  279. self.headerLabel = StaticText(parent=self.controlPanelExport,
  280. id=wx.ID_ANY,
  281. label=_('Do you want the CSV header?'))
  282. self.headerCheck = wx.CheckBox(parent=self.controlPanelExport,
  283. id=wx.ID_ANY)
  284. self.controlPanelSizerCheck = wx.BoxSizer(wx.HORIZONTAL)
  285. self.controlPanelSizerCheck.Add(self.headerCheck)
  286. self.controlPanelSizerCheck.Add(self.headerLabel)
  287. self.controlPanelSizerExport = wx.BoxSizer(wx.VERTICAL)
  288. self.controlPanelSizerExport.Add(self.csvLabel)
  289. self.controlPanelSizerExport.Add(self.csvButton)
  290. self.controlPanelSizerExport.Add(self.controlPanelSizerCheck)
  291. self.controlPanelExport.SetSizer(self.controlPanelSizerExport)
  292. self.controlPanelSizerCheck.Fit(self)
  293. self.controlPanelSizerExport.Fit(self)
  294. self.ntb.AddPage(page=self.controlPanelExport, text=_('Export'),
  295. name='Export')
  296. # ------------Buttons on the bottom(draw,help)------------
  297. self.vButtPanel = wx.Panel(self.mainPanel, id=wx.ID_ANY)
  298. self.vButtSizer = wx.BoxSizer(wx.HORIZONTAL)
  299. self.drawButton = Button(self.vButtPanel, id=wx.ID_ANY,
  300. label=_("Draw"))
  301. self.drawButton.Bind(wx.EVT_BUTTON, self.OnRedraw)
  302. self.helpButton = Button(self.vButtPanel, id=wx.ID_ANY,
  303. label=_("Help"))
  304. self.helpButton.Bind(wx.EVT_BUTTON, self.OnHelp)
  305. self.vButtSizer.Add(self.drawButton)
  306. self.vButtSizer.Add(self.helpButton)
  307. self.vButtPanel.SetSizer(self.vButtSizer)
  308. self.mainPanel.SetSizer(self.vbox)
  309. self.vbox.Add(self.ntb, flag=wx.EXPAND)
  310. self.vbox.Add(self.vButtPanel, flag=wx.EXPAND)
  311. self.vbox.Fit(self)
  312. self.mainPanel.Fit()
  313. def _getSTRDdata(self, timeseries):
  314. """Load data and read properties
  315. :param list timeseries: a list of timeseries
  316. """
  317. if not self.poi:
  318. GError(parent=self, message=_("Invalid input coordinates"),
  319. showTraceback=False)
  320. return
  321. mode = None
  322. unit = None
  323. columns = ','.join(['name', 'start_time', 'end_time'])
  324. for series in timeseries:
  325. name = series[0]
  326. fullname = name + '@' + series[1]
  327. etype = series[2]
  328. sp = tgis.dataset_factory(etype, fullname)
  329. if not sp.is_in_db(dbif=self.dbif):
  330. GError(message=_("Dataset <%s> not found in temporal "
  331. "database") % (fullname), parent=self)
  332. return
  333. sp.select(dbif=self.dbif)
  334. minmin = sp.metadata.get_min_min()
  335. self.plotNameListR.append(name)
  336. self.timeDataR[name] = OrderedDict()
  337. self.timeDataR[name]['temporalDataType'] = etype
  338. self.timeDataR[name]['temporalType'] = sp.get_temporal_type()
  339. self.timeDataR[name]['granularity'] = sp.get_granularity()
  340. if mode is None:
  341. mode = self.timeDataR[name]['temporalType']
  342. elif self.timeDataR[name]['temporalType'] != mode:
  343. GError(
  344. parent=self, message=_(
  345. "Datasets have different temporal"
  346. " type (absolute x relative), "
  347. "which is not allowed."))
  348. return
  349. # check topology
  350. maps = sp.get_registered_maps_as_objects(dbif=self.dbif)
  351. self.timeDataR[name]['validTopology'] = sp.check_temporal_topology(
  352. maps=maps, dbif=self.dbif)
  353. self.timeDataR[name]['unit'] = None # only with relative
  354. if self.timeDataR[name]['temporalType'] == 'relative':
  355. start, end, self.timeDataR[name][
  356. 'unit'] = sp.get_relative_time()
  357. if unit is None:
  358. unit = self.timeDataR[name]['unit']
  359. elif self.timeDataR[name]['unit'] != unit:
  360. GError(parent=self, message=_("Datasets have different "
  361. "time unit which is not "
  362. "allowed."))
  363. return
  364. rows = sp.get_registered_maps(columns=columns, where=None,
  365. order='start_time', dbif=self.dbif)
  366. for row in rows:
  367. self.timeDataR[name][row[0]] = {}
  368. self.timeDataR[name][row[0]]['start_datetime'] = row[1]
  369. self.timeDataR[name][row[0]]['end_datetime'] = row[2]
  370. r = RasterRow(row[0])
  371. r.open()
  372. val = r.get_value(self.poi)
  373. r.close()
  374. if val == -2147483648 and val < minmin:
  375. self.timeDataR[name][row[0]]['value'] = None
  376. else:
  377. self.timeDataR[name][row[0]]['value'] = val
  378. self.unit = unit
  379. self.temporalType = mode
  380. return
  381. def _parseVDbConn(self, mapp, layerInp):
  382. '''find attribute key according to layer of input map'''
  383. vdb = Module('v.db.connect', map=mapp, flags='g', stdout_=PIPE)
  384. vdb = vdb.outputs.stdout
  385. for line in vdb.splitlines():
  386. lsplit = line.split('|')
  387. layer = lsplit[0].split('/')[0]
  388. if str(layer) == str(layerInp):
  389. return lsplit[2]
  390. return None
  391. def _getExistingCategories(self, mapp, cats):
  392. """Get a list of categories for a vector map"""
  393. vdb = grass.read_command('v.category', input=mapp, option='print')
  394. categories = vdb.splitlines()
  395. if not cats:
  396. return categories
  397. for cat in cats:
  398. if str(cat) not in categories:
  399. GMessage(message=_("Category {ca} is not on vector map"
  400. " {ma} and it will be not used").format(ma=mapp,
  401. ca=cat),
  402. parent=self)
  403. cats.remove(cat)
  404. return cats
  405. def _getSTVDData(self, timeseries):
  406. """Load data and read properties
  407. :param list timeseries: a list of timeseries
  408. """
  409. mode = None
  410. unit = None
  411. cats = None
  412. attribute = self.attribute.GetValue()
  413. if self.cats.GetValue() != '':
  414. cats = self.cats.GetValue().split(',')
  415. if cats and self.poi:
  416. GMessage(message=_("Both coordinates and categories are set, "
  417. "coordinates will be used. The use categories "
  418. "remove text from coordinate form"))
  419. if not attribute or attribute == '':
  420. GError(parent=self, showTraceback=False,
  421. message=_("With Vector temporal dataset you have to select"
  422. " an attribute column"))
  423. return
  424. columns = ','.join(['name', 'start_time', 'end_time', 'id', 'layer'])
  425. for series in timeseries:
  426. name = series[0]
  427. fullname = name + '@' + series[1]
  428. etype = series[2]
  429. sp = tgis.dataset_factory(etype, fullname)
  430. if not sp.is_in_db(dbif=self.dbif):
  431. GError(message=_("Dataset <%s> not found in temporal "
  432. "database") % (fullname), parent=self,
  433. showTraceback=False)
  434. return
  435. sp.select(dbif=self.dbif)
  436. rows = sp.get_registered_maps(dbif=self.dbif, order="start_time",
  437. columns=columns, where=None)
  438. self.timeDataV[name] = OrderedDict()
  439. self.timeDataV[name]['temporalDataType'] = etype
  440. self.timeDataV[name]['temporalType'] = sp.get_temporal_type()
  441. self.timeDataV[name]['granularity'] = sp.get_granularity()
  442. if mode is None:
  443. mode = self.timeDataV[name]['temporalType']
  444. elif self.timeDataV[name]['temporalType'] != mode:
  445. GError(
  446. parent=self, showTraceback=False, message=_(
  447. "Datasets have different temporal type ("
  448. "absolute x relative), which is not allowed."))
  449. return
  450. self.timeDataV[name]['unit'] = None # only with relative
  451. if self.timeDataV[name]['temporalType'] == 'relative':
  452. start, end, self.timeDataV[name][
  453. 'unit'] = sp.get_relative_time()
  454. if unit is None:
  455. unit = self.timeDataV[name]['unit']
  456. elif self.timeDataV[name]['unit'] != unit:
  457. GError(message=_("Datasets have different time unit which"
  458. " is not allowed."), parent=self,
  459. showTraceback=False)
  460. return
  461. if self.poi:
  462. self.plotNameListV.append(name)
  463. # TODO set an appropriate distance, right now a big one is set
  464. # to return the closer point to the selected one
  465. out = grass.vector_what(map='pois_srvds',
  466. coord=self.poi.coords(),
  467. distance=10000000000000000)
  468. if len(out) != len(rows):
  469. GError(parent=self, showTraceback=False,
  470. message=_("Difference number of vector layers and "
  471. "maps in the vector temporal dataset"))
  472. return
  473. for i in range(len(rows)):
  474. row = rows[i]
  475. values = out[i]
  476. if str(row['layer']) == str(values['Layer']):
  477. lay = "{map}_{layer}".format(map=row['name'],
  478. layer=values['Layer'])
  479. self.timeDataV[name][lay] = {}
  480. self.timeDataV[name][lay][
  481. 'start_datetime'] = row['start_time']
  482. self.timeDataV[name][lay][
  483. 'end_datetime'] = row['start_time']
  484. self.timeDataV[name][lay]['value'] = values[
  485. 'Attributes'][attribute]
  486. else:
  487. wherequery = ''
  488. cats = self._getExistingCategories(rows[0]['name'], cats)
  489. totcat = len(cats)
  490. ncat = 1
  491. for cat in cats:
  492. if ncat == 1 and totcat != 1:
  493. wherequery += '{k}={c} or'.format(c=cat, k="{key}")
  494. elif ncat == 1 and totcat == 1:
  495. wherequery += '{k}={c}'.format(c=cat, k="{key}")
  496. elif ncat == totcat:
  497. wherequery += ' {k}={c}'.format(c=cat, k="{key}")
  498. else:
  499. wherequery += ' {k}={c} or'.format(c=cat, k="{key}")
  500. catn = "cat{num}".format(num=cat)
  501. self.plotNameListV.append("{na}+{cat}".format(na=name,
  502. cat=catn))
  503. self.timeDataV[name][catn] = OrderedDict()
  504. ncat += 1
  505. for row in rows:
  506. lay = int(row['layer'])
  507. catkey = self._parseVDbConn(row['name'], lay)
  508. if not catkey:
  509. GError(
  510. parent=self, showTraceback=False, message=_(
  511. "No connection between vector map {vmap} "
  512. "and layer {la}".format(
  513. vmap=row['name'], la=lay)))
  514. return
  515. vals = grass.vector_db_select(
  516. map=row['name'], layer=lay, where=wherequery.format(
  517. key=catkey), columns=attribute)
  518. layn = "lay{num}".format(num=lay)
  519. for cat in cats:
  520. catn = "cat{num}".format(num=cat)
  521. if layn not in self.timeDataV[name][catn].keys():
  522. self.timeDataV[name][catn][layn] = {}
  523. self.timeDataV[name][catn][layn][
  524. 'start_datetime'] = row['start_time']
  525. self.timeDataV[name][catn][layn][
  526. 'end_datetime'] = row['end_time']
  527. self.timeDataV[name][catn][layn]['value'] = vals['values'][int(cat)][
  528. 0]
  529. self.unit = unit
  530. self.temporalType = mode
  531. return
  532. def _drawFigure(self):
  533. """Draws or print 2D plot (temporal extents)"""
  534. self.axes2d.clear()
  535. self.axes2d.grid(False)
  536. if self.temporalType == 'absolute':
  537. self.axes2d.xaxis_date()
  538. self.fig.autofmt_xdate()
  539. self.convert = mdates.date2num
  540. self.invconvert = mdates.num2date
  541. else:
  542. self.convert = lambda x: x
  543. self.invconvert = self.convert
  544. self.colors = cycle(COLORS)
  545. self.yticksNames = []
  546. self.yticksPos = []
  547. self.plots = []
  548. self.drawTitle = self.title.GetValue()
  549. self.drawX = self.x.GetValue()
  550. self.drawY = self.y.GetValue()
  551. if self.datasetsR:
  552. self.lookUp = LookUp(self.timeDataR, self.invconvert)
  553. else:
  554. self.lookUp = LookUp(self.timeDataV, self.invconvert)
  555. if self.datasetsR:
  556. self.drawR()
  557. if self.datasetsV:
  558. if self.poi:
  559. self.drawV()
  560. elif self.cats:
  561. self.drawVCats()
  562. self.canvas.draw()
  563. DataCursor(self.plots, self.lookUp, InfoFormat, self.convert)
  564. def _setLabels(self, x):
  565. """Function to set the right labels"""
  566. if self.drawX != '':
  567. self.axes2d.set_xlabel(self.drawX)
  568. else:
  569. if self.temporalType == 'absolute':
  570. self.axes2d.set_xlabel(_("Temporal resolution: %s" % x ))
  571. else:
  572. self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
  573. if self.drawY != '':
  574. self.axes2d.set_ylabel(self.drawY)
  575. else:
  576. self.axes2d.set_ylabel(', '.join(self.yticksNames))
  577. if self.drawTitle != '':
  578. self.axes2d.set_title(self.drawTitle)
  579. def _writeCSV(self, x, y):
  580. """Used to write CSV file of plotted data"""
  581. import csv
  582. if isinstance(y[0], list):
  583. zipped = zip(x, *y)
  584. else:
  585. zipped = zip(x, y)
  586. with open(self.csvpath, "wb") as fi:
  587. writer = csv.writer(fi)
  588. if self.header:
  589. head = ["Time"]
  590. head.extend(self.yticksNames)
  591. writer.writerow(head)
  592. writer.writerows(zipped)
  593. def drawR(self):
  594. ycsv = []
  595. xcsv = []
  596. for i, name in enumerate(self.datasetsR):
  597. name = name[0]
  598. # just name; with mapset it would be long
  599. self.yticksNames.append(name)
  600. self.yticksPos.append(1) # TODO
  601. xdata = []
  602. ydata = []
  603. for keys, values in six.iteritems(self.timeDataR[name]):
  604. if keys in ['temporalType', 'granularity', 'validTopology',
  605. 'unit', 'temporalDataType']:
  606. continue
  607. xdata.append(self.convert(values['start_datetime']))
  608. ydata.append(values['value'])
  609. xcsv.append(values['start_datetime'])
  610. if len(ydata) == ydata.count(None):
  611. GError(parent=self, showTraceback=False,
  612. message=_("Problem getting data from raster temporal"
  613. " dataset. Empty list of values."))
  614. return
  615. self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
  616. datasetName=name)
  617. color = self.colors.next()
  618. self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
  619. color=color,
  620. label=self.plotNameListR[i])[0])
  621. if self.csvpath:
  622. ycsv.append(ydata)
  623. if self.csvpath:
  624. self._writeCSV(xcsv, ycsv)
  625. self._setLabels(self.timeDataR[name]['granularity'])
  626. # legend
  627. handles, labels = self.axes2d.get_legend_handles_labels()
  628. self.axes2d.legend(loc=0)
  629. def drawVCats(self):
  630. ycsv = []
  631. for i, name in enumerate(self.plotNameListV):
  632. # just name; with mapset it would be long
  633. labelname = name.replace('+', ' ')
  634. self.yticksNames.append(labelname)
  635. name_cat = name.split('+')
  636. name = name_cat[0]
  637. self.yticksPos.append(1) # TODO
  638. xdata = []
  639. ydata = []
  640. xcsv = []
  641. for keys, values in six.iteritems(self.timeDataV[name_cat[0]]
  642. [name_cat[1]]):
  643. if keys in ['temporalType', 'granularity', 'validTopology',
  644. 'unit', 'temporalDataType']:
  645. continue
  646. xdata.append(self.convert(values['start_datetime']))
  647. if values['value'] == '':
  648. ydata.append(None)
  649. else:
  650. ydata.append(values['value'])
  651. xcsv.append(values['start_datetime'])
  652. if len(ydata) == ydata.count(None):
  653. GError(parent=self, showTraceback=False,
  654. message=_("Problem getting data from vector temporal"
  655. " dataset. Empty list of values for cat "
  656. "{ca}.".format(ca=name_cat[1].replace('cat',
  657. ''))))
  658. continue
  659. self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
  660. datasetName=name)
  661. color = self.colors.next()
  662. self.plots.append(
  663. self.axes2d.plot(
  664. xdata,
  665. ydata,
  666. marker='o',
  667. color=color,
  668. label=labelname)[0])
  669. if self.csvpath:
  670. ycsv.append(ydata)
  671. if self.csvpath:
  672. self._writeCSV(xcsv, ycsv)
  673. self._setLabels(self.timeDataV[name]['granularity'])
  674. # legend
  675. handles, labels = self.axes2d.get_legend_handles_labels()
  676. self.axes2d.legend(loc=0)
  677. self.listWhereConditions = []
  678. def drawV(self):
  679. ycsv = []
  680. for i, name in enumerate(self.plotNameListV):
  681. # just name; with mapset it would be long
  682. self.yticksNames.append(self.attribute.GetValue())
  683. self.yticksPos.append(0) # TODO
  684. xdata = []
  685. ydata = []
  686. xcsv = []
  687. for keys, values in six.iteritems(self.timeDataV[name]):
  688. if keys in ['temporalType', 'granularity', 'validTopology',
  689. 'unit', 'temporalDataType']:
  690. continue
  691. xdata.append(self.convert(values['start_datetime']))
  692. ydata.append(values['value'])
  693. xcsv.append(values['start_datetime'])
  694. if len(ydata) == ydata.count(None):
  695. GError(parent=self, showTraceback=False,
  696. message=_("Problem getting data from vector temporal"
  697. " dataset. Empty list of values."))
  698. return
  699. self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
  700. datasetName=name)
  701. color = self.colors.next()
  702. self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
  703. color=color, label=name)[0])
  704. if self.csvpath:
  705. ycsv.append(ydata)
  706. if self.csvpath:
  707. self._writeCSV(xcsv, ycsv)
  708. self._setLabels(self.timeDataV[name]['granularity'])
  709. # legend
  710. handles, labels = self.axes2d.get_legend_handles_labels()
  711. self.axes2d.legend(loc=0)
  712. self.listWhereConditions = []
  713. def OnRedraw(self, event=None):
  714. """Required redrawing."""
  715. self.csvpath = self.csvButton.GetValue()
  716. self.header = self.headerCheck.IsChecked()
  717. if (os.path.exists(self.csvpath) and not self.overwrite):
  718. dlg = wx.MessageDialog(self, _("{pa} already exists, do you want "
  719. "to overwrite?".format(pa=self.csvpath)),
  720. _("File exists"),
  721. wx.OK | wx.CANCEL | wx.ICON_QUESTION)
  722. if dlg.ShowModal() != wx.ID_OK:
  723. dlg.Destroy()
  724. GError(parent=self, showTraceback=False,
  725. message=_("Please change name of output CSV file or "))
  726. return
  727. dlg.Destroy()
  728. self.init()
  729. datasetsR = self.datasetSelectR.GetValue().strip()
  730. datasetsV = self.datasetSelectV.GetValue().strip()
  731. if not datasetsR and not datasetsV:
  732. return
  733. try:
  734. getcoors = self.coorval.coordsField.GetValue()
  735. except:
  736. try:
  737. getcoors = self.coorval.GetValue()
  738. except:
  739. getcoors = None
  740. if getcoors and getcoors != '':
  741. try:
  742. coordx, coordy = getcoors.split(',')
  743. coordx, coordy = float(coordx), float(coordy)
  744. except (ValueError, AttributeError):
  745. try:
  746. coordx, coordy = self.coorval.GetValue().split(',')
  747. coordx, coordy = float(coordx), float(coordy)
  748. except (ValueError, AttributeError):
  749. GMessage(message=_("Incorrect coordinates format, should "
  750. "be: x,y"), parent=self)
  751. coors = [coordx, coordy]
  752. if coors:
  753. try:
  754. self.poi = Point(float(coors[0]), float(coors[1]))
  755. except GException:
  756. GError(parent=self, message=_("Invalid input coordinates"),
  757. showTraceback=False)
  758. return
  759. if not self.poi:
  760. GError(parent=self, message=_("Invalid input coordinates"),
  761. showTraceback=False)
  762. return
  763. bbox = self.region.get_bbox()
  764. if not bbox.contains(self.poi):
  765. GError(parent=self, message=_("Seed point outside the "
  766. "current region"),
  767. showTraceback=False)
  768. return
  769. # check raster dataset
  770. if datasetsR:
  771. datasetsR = datasetsR.split(',')
  772. try:
  773. datasetsR = self._checkDatasets(datasetsR, 'strds')
  774. if not datasetsR:
  775. return
  776. except GException:
  777. GError(parent=self, message=_("Invalid input raster dataset"),
  778. showTraceback=False)
  779. return
  780. if not self.poi:
  781. GError(parent=self, message=_("Invalid input coordinates"),
  782. showTraceback=False)
  783. return
  784. self.datasetsR = datasetsR
  785. # check vector dataset
  786. if datasetsV:
  787. datasetsV = datasetsV.split(',')
  788. try:
  789. datasetsV = self._checkDatasets(datasetsV, 'stvds')
  790. if not datasetsV:
  791. return
  792. except GException:
  793. GError(parent=self, message=_("Invalid input vector dataset"),
  794. showTraceback=False)
  795. return
  796. self.datasetsV = datasetsV
  797. self._redraw()
  798. def _redraw(self):
  799. """Readraw data.
  800. Decides if to draw also 3D and adjusts layout if needed.
  801. """
  802. if self.datasetsR:
  803. self._getSTRDdata(self.datasetsR)
  804. if self.datasetsV:
  805. self._getSTVDData(self.datasetsV)
  806. # axes3d are physically removed
  807. if not self.axes2d:
  808. self.axes2d = self.fig.add_subplot(1, 1, 1)
  809. self._drawFigure()
  810. def _checkDatasets(self, datasets, typ):
  811. """Checks and validates datasets.
  812. Reports also type of dataset (e.g. 'strds').
  813. :param list datasets: list of temporal dataset's name
  814. :return: (mapName, mapset, type)
  815. """
  816. validated = []
  817. tDict = tgis.tlist_grouped(type=typ, group_type=True, dbif=self.dbif)
  818. # nested list with '(map, mapset, etype)' items
  819. allDatasets = [[[(map, mapset, etype) for map in maps]
  820. for etype, maps in six.iteritems(etypesDict)]
  821. for mapset, etypesDict in six.iteritems(tDict)]
  822. # flatten this list
  823. if allDatasets:
  824. allDatasets = reduce(lambda x, y: x + y, reduce(lambda x, y: x + y,
  825. allDatasets))
  826. mapsets = tgis.get_tgis_c_library_interface().available_mapsets()
  827. allDatasets = [
  828. i
  829. for i in sorted(
  830. allDatasets, key=lambda l: mapsets.index(l[1]))]
  831. for dataset in datasets:
  832. errorMsg = _("Space time dataset <%s> not found.") % dataset
  833. if dataset.find("@") >= 0:
  834. nameShort, mapset = dataset.split('@', 1)
  835. indices = [n for n, (mapName, mapsetName, etype) in enumerate(
  836. allDatasets) if nameShort == mapName and mapsetName == mapset]
  837. else:
  838. indices = [n for n, (mapName, mapset, etype) in enumerate(
  839. allDatasets) if dataset == mapName]
  840. if len(indices) == 0:
  841. raise GException(errorMsg)
  842. elif len(indices) >= 2:
  843. dlg = wx.SingleChoiceDialog(
  844. self,
  845. message=_(
  846. "Please specify the "
  847. "space time dataset "
  848. "<%s>." % dataset),
  849. caption=_("Ambiguous dataset name"),
  850. choices=[
  851. ("%(map)s@%(mapset)s:"
  852. " %(etype)s" % {
  853. 'map': allDatasets[i][0],
  854. 'mapset': allDatasets[i][1],
  855. 'etype': allDatasets[i][2]}) for i in indices],
  856. style=wx.CHOICEDLG_STYLE | wx.OK)
  857. if dlg.ShowModal() == wx.ID_OK:
  858. index = dlg.GetSelection()
  859. validated.append(allDatasets[indices[index]])
  860. else:
  861. continue
  862. else:
  863. validated.append(allDatasets[indices[0]])
  864. return validated
  865. def OnHelp(self, event):
  866. """Function to show help"""
  867. RunCommand(prog='g.manual', quiet=True, entry='g.gui.tplot')
  868. def SetDatasets(self, rasters, vectors, coors, cats, attr, title, xlabel,
  869. ylabel, csvfile, head, overwrite):
  870. """Set the data
  871. :param list rasters: a list of temporal raster dataset's name
  872. :param list vectors: a list of temporal vector dataset's name
  873. :param list coors: a list with x/y coordinates
  874. :param list cats: a list with incld. categories of vector
  875. :param str attr: name of atribute of vectror data
  876. """
  877. if not (rasters or vectors) or not (coors or cats):
  878. return
  879. try:
  880. if rasters:
  881. self.datasetsR = self._checkDatasets(rasters, 'strds')
  882. if vectors:
  883. self.datasetsV = self._checkDatasets(vectors, 'stvds')
  884. if not (self.datasetsR or self.datasetsV):
  885. return
  886. except GException:
  887. GError(parent=self, message=_("Invalid input temporal dataset"),
  888. showTraceback=False)
  889. return
  890. if coors:
  891. try:
  892. self.poi = Point(float(coors[0]), float(coors[1]))
  893. except GException:
  894. GError(parent=self, message=_("Invalid input coordinates"),
  895. showTraceback=False)
  896. return
  897. try:
  898. self.coorval.coordsField.SetValue(','.join(coors))
  899. except:
  900. self.coorval.SetValue(','.join(coors))
  901. if self.datasetsV:
  902. vdatas = ','.join(map(lambda x: x[0] + '@' + x[1], self.datasetsV))
  903. self.datasetSelectV.SetValue(vdatas)
  904. if attr:
  905. self.attribute.SetValue(attr)
  906. if cats:
  907. self.cats.SetValue(cats)
  908. if self.datasetsR:
  909. self.datasetSelectR.SetValue(
  910. ','.join(map(lambda x: x[0] + '@' + x[1], self.datasetsR)))
  911. if title:
  912. self.title.SetValue(title)
  913. if xlabel:
  914. self.x.SetValue(xlabel)
  915. if ylabel:
  916. self.y.SetValue(ylabel)
  917. if csvfile:
  918. self.csvpath = csvfile
  919. self.header = head
  920. self.overwrite = overwrite
  921. self._redraw()
  922. def OnVectorSelected(self, event):
  923. """Update the controlbox related to stvds"""
  924. dataset = self.datasetSelectV.GetValue().strip()
  925. name = dataset.split('@')[0]
  926. mapset = dataset.split('@')[1] if len(dataset.split('@')) > 1 else ''
  927. found = False
  928. for each in tgis.tlist(type='stvds', dbif=self.dbif):
  929. each_name, each_mapset = each.split('@')
  930. if name == each_name:
  931. if mapset and mapset != each_mapset:
  932. continue
  933. dataset = name + '@' + each_mapset
  934. found = True
  935. break
  936. if found:
  937. try:
  938. vect_list = grass.read_command('t.vect.list', flags='u',
  939. input=dataset, column='name')
  940. except Exception:
  941. self.attribute.Clear()
  942. GError(
  943. parent=self,
  944. message=_("Invalid input temporal dataset"),
  945. showTraceback=False)
  946. return
  947. vect_list = list(set(sorted(vect_list.split())))
  948. for vec in vect_list:
  949. self.attribute.InsertColumns(vec, 1)
  950. else:
  951. self.attribute.Clear()
  952. class LookUp:
  953. """Helper class for searching info by coordinates"""
  954. def __init__(self, timeData, convert):
  955. self.data = {}
  956. self.timeData = timeData
  957. self.convert = convert
  958. def AddDataset(self, yranges, xranges, datasetName):
  959. if len(yranges) != len(xranges):
  960. GError(parent=self, showTraceback=False,
  961. message=_("Datasets have different number of values"))
  962. return
  963. self.data[datasetName] = {}
  964. for i in range(len(xranges)):
  965. self.data[datasetName][xranges[i]] = yranges[i]
  966. def GetInformation(self, x):
  967. values = {}
  968. for key, value in six.iteritems(self.data):
  969. if value[x]:
  970. values[key] = [self.convert(x), value[x]]
  971. if len(values) == 0:
  972. return None
  973. return self.timeData, values
  974. def InfoFormat(timeData, values):
  975. """Formats information about dataset"""
  976. text = []
  977. for key, val in six.iteritems(values):
  978. etype = timeData[key]['temporalDataType']
  979. if etype == 'strds':
  980. text.append(_("Space time raster dataset: %s") % key)
  981. elif etype == 'stvds':
  982. text.append(_("Space time vector dataset: %s") % key)
  983. elif etype == 'str3ds':
  984. text.append(_("Space time 3D raster dataset: %s") % key)
  985. text.append(_("Value for {date} is {val}".format(date=val[0],
  986. val=val[1])))
  987. text.append('\n')
  988. text.append(_("Press Del to dismiss."))
  989. return '\n'.join(text)
  990. class DataCursor(object):
  991. """A simple data cursor widget that displays the x,y location of a
  992. matplotlib artist when it is selected.
  993. Source: http://stackoverflow.com/questions/4652439/
  994. is-there-a-matplotlib-equivalent-of-matlabs-datacursormode/4674445
  995. """
  996. def __init__(self, artists, lookUp, formatFunction, convert,
  997. tolerance=5, offsets=(-30, 20), display_all=False):
  998. """Create the data cursor and connect it to the relevant figure.
  999. "artists" is the matplotlib artist or sequence of artists that will be
  1000. selected.
  1001. "tolerance" is the radius (in points) that the mouse click must be
  1002. within to select the artist.
  1003. "offsets" is a tuple of (x,y) offsets in points from the selected
  1004. point to the displayed annotation box
  1005. "display_all" controls whether more than one annotation box will
  1006. be shown if there are multiple axes. Only one will be shown
  1007. per-axis, regardless.
  1008. """
  1009. self.lookUp = lookUp
  1010. self.formatFunction = formatFunction
  1011. self.offsets = offsets
  1012. self.display_all = display_all
  1013. if not cbook.iterable(artists):
  1014. artists = [artists]
  1015. self.artists = artists
  1016. self.convert = convert
  1017. self.axes = tuple(set(art.axes for art in self.artists))
  1018. self.figures = tuple(set(ax.figure for ax in self.axes))
  1019. self.annotations = {}
  1020. for ax in self.axes:
  1021. self.annotations[ax] = self.annotate(ax)
  1022. for artist in self.artists:
  1023. artist.set_picker(tolerance)
  1024. for fig in self.figures:
  1025. fig.canvas.mpl_connect('pick_event', self)
  1026. fig.canvas.mpl_connect('key_press_event', self.keyPressed)
  1027. def keyPressed(self, event):
  1028. """Key pressed - hide annotation if Delete was pressed"""
  1029. if event.key != 'delete':
  1030. return
  1031. for ax in self.axes:
  1032. self.annotations[ax].set_visible(False)
  1033. event.canvas.draw()
  1034. def annotate(self, ax):
  1035. """Draws and hides the annotation box for the given axis "ax"."""
  1036. annotation = ax.annotate(self.formatFunction, xy=(0, 0), ha='center',
  1037. xytext=self.offsets, va='bottom',
  1038. textcoords='offset points',
  1039. bbox=dict(boxstyle='round,pad=0.5',
  1040. fc='yellow', alpha=0.7),
  1041. arrowprops=dict(arrowstyle='->',
  1042. connectionstyle='arc3,rad=0'),
  1043. annotation_clip=False, multialignment='left')
  1044. annotation.set_visible(False)
  1045. return annotation
  1046. def __call__(self, event):
  1047. """Intended to be called through "mpl_connect"."""
  1048. # Rather than trying to interpolate, just display the clicked coords
  1049. # This will only be called if it's within "tolerance", anyway.
  1050. x, y = event.mouseevent.xdata, event.mouseevent.ydata
  1051. annotation = self.annotations[event.artist.axes]
  1052. if x is not None:
  1053. if not self.display_all:
  1054. # Hide any other annotation boxes...
  1055. for ann in self.annotations.values():
  1056. ann.set_visible(False)
  1057. if 'Line2D' in str(type(event.artist)):
  1058. xData = []
  1059. for a in event.artist.get_xdata():
  1060. try:
  1061. d = self.convert(a)
  1062. except:
  1063. d = a
  1064. xData.append(d)
  1065. x = xData[np.argmin(abs(xData - x))]
  1066. info = self.lookUp.GetInformation(x)
  1067. ys = zip(*info[1].values())[1]
  1068. if not info:
  1069. return
  1070. # Update the annotation in the current axis..
  1071. annotation.xy = x, max(ys)
  1072. text = self.formatFunction(*info)
  1073. annotation.set_text(text)
  1074. annotation.set_visible(True)
  1075. event.canvas.draw()