wxlibplot.py 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557
  1. #-----------------------------------------------------------------------------
  2. # Name: wx.lib.plot.py
  3. # Purpose: Line, Bar and Scatter Graphs
  4. #
  5. # Author: Gordon Williams
  6. #
  7. # Created: 2003/11/03
  8. # RCS-ID: $Id$
  9. # Copyright: (c) 2002
  10. # Licence: Use as you wish.
  11. #-----------------------------------------------------------------------------
  12. # Included in GRASS GIS instead of importing it from wxPython
  13. # to overcome bug present in wxPython 3.0.2.
  14. # See #2558 and #3112
  15. #
  16. # 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net)
  17. #
  18. # o 2.5 compatability update.
  19. # o Renamed to plot.py in the wx.lib directory.
  20. # o Reworked test frame to work with wx demo framework. This saves a bit
  21. # of tedious cut and paste, and the test app is excellent.
  22. #
  23. # 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
  24. #
  25. # o wxScrolledMessageDialog -> ScrolledMessageDialog
  26. #
  27. # Oct 6, 2004 Gordon Williams (g_will@cyberus.ca)
  28. # - Added bar graph demo
  29. # - Modified line end shape from round to square.
  30. # - Removed FloatDCWrapper for conversion to ints and ints in arguments
  31. #
  32. # Oct 15, 2004 Gordon Williams (g_will@cyberus.ca)
  33. # - Imported modules given leading underscore to name.
  34. # - Added Cursor Line Tracking and User Point Labels.
  35. # - Demo for Cursor Line Tracking and Point Labels.
  36. # - Size of plot preview frame adjusted to show page better.
  37. # - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas.
  38. # - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve)
  39. # can be in either user coords or screen coords.
  40. #
  41. # Jun 22, 2009 Florian Hoech (florian.hoech@gmx.de)
  42. # - Fixed exception when drawing empty plots on Mac OS X
  43. # - Fixed exception when trying to draw point labels on Mac OS X (Mac OS X
  44. # point label drawing code is still slow and only supports wx.COPY)
  45. # - Moved label positions away from axis lines a bit
  46. # - Added PolySpline class and modified demo 1 and 2 to use it
  47. # - Added center and diagonal lines option (Set/GetEnableCenterLines,
  48. # Set/GetEnableDiagonals)
  49. # - Added anti-aliasing option with optional high-resolution mode
  50. # (Set/GetEnableAntiAliasing, Set/GetEnableHiRes) and demo
  51. # - Added option to specify exact number of tick marks to use for each axis
  52. # (SetXSpec(<number>, SetYSpec(<number>) -- work like 'min', but with
  53. # <number> tick marks)
  54. # - Added support for background and foreground colours (enabled via
  55. # SetBackgroundColour/SetForegroundColour on a PlotCanvas instance)
  56. # - Changed PlotCanvas printing initialization from occuring in __init__ to
  57. # occur on access. This will postpone any IPP and / or CUPS warnings
  58. # which appear on stderr on some Linux systems until printing functionality
  59. # is actually used.
  60. #
  61. #
  62. """
  63. This is a simple light weight plotting module that can be used with
  64. Boa or easily integrated into your own wxPython application. The
  65. emphasis is on small size and fast plotting for large data sets. It
  66. has a reasonable number of features to do line and scatter graphs
  67. easily as well as simple bar graphs. It is not as sophisticated or
  68. as powerful as SciPy Plt or Chaco. Both of these are great packages
  69. but consume huge amounts of computer resources for simple plots.
  70. They can be found at http://scipy.com
  71. This file contains two parts; first the re-usable library stuff, then,
  72. after a "if __name__=='__main__'" test, a simple frame and a few default
  73. plots for examples and testing.
  74. Based on wxPlotCanvas
  75. Written by K.Hinsen, R. Srinivasan;
  76. Ported to wxPython Harm van der Heijden, feb 1999
  77. Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
  78. -More style options
  79. -Zooming using mouse "rubber band"
  80. -Scroll left, right
  81. -Grid(graticule)
  82. -Printing, preview, and page set up (margins)
  83. -Axis and title labels
  84. -Cursor xy axis values
  85. -Doc strings and lots of comments
  86. -Optimizations for large number of points
  87. -Legends
  88. Did a lot of work here to speed markers up. Only a factor of 4
  89. improvement though. Lines are much faster than markers, especially
  90. filled markers. Stay away from circles and triangles unless you
  91. only have a few thousand points.
  92. Times for 25,000 points
  93. Line - 0.078 sec
  94. Markers
  95. Square - 0.22 sec
  96. dot - 0.10
  97. circle - 0.87
  98. cross,plus - 0.28
  99. triangle, triangle_down - 0.90
  100. Thanks to Chris Barker for getting this version working on Linux.
  101. Zooming controls with mouse (when enabled):
  102. Left mouse drag - Zoom box.
  103. Left mouse double click - reset zoom.
  104. Right mouse click - zoom out centred on click location.
  105. """
  106. import string as _string
  107. import time as _time
  108. import sys
  109. import wx
  110. # Needs NumPy
  111. try:
  112. import numpy as np
  113. except:
  114. msg = """
  115. This module requires the NumPy module, which could not be
  116. imported. It probably is not installed (it's not part of the
  117. standard Python distribution). See the Numeric Python site
  118. (http://numpy.scipy.org) for information on downloading source or
  119. binaries."""
  120. raise ImportError("NumPy not found.\n" + msg)
  121. #
  122. # Plotting classes...
  123. #
  124. class PolyPoints:
  125. """Base Class for lines and markers
  126. - All methods are private.
  127. """
  128. def __init__(self, points, attr):
  129. self._points = np.array(points).astype(np.float64)
  130. self._logscale = (False, False)
  131. self._pointSize = (1.0, 1.0)
  132. self.currentScale = (1, 1)
  133. self.currentShift = (0, 0)
  134. self.scaled = self.points
  135. self.attributes = {}
  136. self.attributes.update(self._attributes)
  137. for name, value in attr.items():
  138. if name not in self._attributes.keys():
  139. raise KeyError(
  140. "Style attribute incorrect. Should be one of %s" % self._attributes.keys())
  141. self.attributes[name] = value
  142. def setLogScale(self, logscale):
  143. self._logscale = logscale
  144. def __getattr__(self, name):
  145. if name == 'points':
  146. if len(self._points) > 0:
  147. data = np.array(self._points, copy=True)
  148. if self._logscale[0]:
  149. data = self.log10(data, 0)
  150. if self._logscale[1]:
  151. data = self.log10(data, 1)
  152. return data
  153. else:
  154. return self._points
  155. else:
  156. raise AttributeError(name)
  157. def log10(self, data, ind):
  158. data = np.compress(data[:, ind] > 0, data, 0)
  159. data[:, ind] = np.log10(data[:, ind])
  160. return data
  161. def boundingBox(self):
  162. if len(self.points) == 0:
  163. # no curves to draw
  164. # defaults to (-1,-1) and (1,1) but axis can be set in Draw
  165. minXY = np.array([-1.0, -1.0])
  166. maxXY = np.array([1.0, 1.0])
  167. else:
  168. minXY = np.minimum.reduce(self.points)
  169. maxXY = np.maximum.reduce(self.points)
  170. return minXY, maxXY
  171. def scaleAndShift(self, scale=(1, 1), shift=(0, 0)):
  172. if len(self.points) == 0:
  173. # no curves to draw
  174. return
  175. if (scale is not self.currentScale) or (shift is not self.currentShift):
  176. # update point scaling
  177. self.scaled = scale * self.points + shift
  178. self.currentScale = scale
  179. self.currentShift = shift
  180. # else unchanged use the current scaling
  181. def getLegend(self):
  182. return self.attributes['legend']
  183. def getClosestPoint(self, pntXY, pointScaled=True):
  184. """Returns the index of closest point on the curve, pointXY, scaledXY, distance
  185. x, y in user coords
  186. if pointScaled == True based on screen coords
  187. if pointScaled == False based on user coords
  188. """
  189. if pointScaled == True:
  190. # Using screen coords
  191. p = self.scaled
  192. pxy = self.currentScale * np.array(pntXY) + self.currentShift
  193. else:
  194. # Using user coords
  195. p = self.points
  196. pxy = np.array(pntXY)
  197. # determine distance for each point
  198. d = np.sqrt(np.add.reduce((p - pxy) ** 2, 1)) # sqrt(dx^2+dy^2)
  199. pntIndex = np.argmin(d)
  200. dist = d[pntIndex]
  201. return [pntIndex, self.points[pntIndex], self.scaled[pntIndex] / self._pointSize, dist]
  202. class PolyLine(PolyPoints):
  203. """Class to define line type and style
  204. - All methods except __init__ are private.
  205. """
  206. _attributes = {'colour': 'black',
  207. 'width': 1,
  208. 'style': wx.PENSTYLE_SOLID,
  209. 'legend': ''}
  210. def __init__(self, points, **attr):
  211. """
  212. Creates PolyLine object
  213. :param `points`: sequence (array, tuple or list) of (x,y) points making up line
  214. :keyword `attr`: keyword attributes, default to:
  215. ========================== ================================
  216. 'colour'= 'black' wx.Pen Colour any wx.Colour
  217. 'width'= 1 Pen width
  218. 'style'= wx.PENSTYLE_SOLID wx.Pen style
  219. 'legend'= '' Line Legend to display
  220. ========================== ================================
  221. """
  222. PolyPoints.__init__(self, points, attr)
  223. def draw(self, dc, printerScale, coord=None):
  224. colour = self.attributes['colour']
  225. width = self.attributes['width'] * printerScale * self._pointSize[0]
  226. style = self.attributes['style']
  227. if not isinstance(colour, wx.Colour):
  228. colour = wx.NamedColour(colour)
  229. pen = wx.Pen(colour, width, style)
  230. pen.SetCap(wx.CAP_BUTT)
  231. dc.SetPen(pen)
  232. if coord is None:
  233. if len(self.scaled): # bugfix for Mac OS X
  234. dc.DrawLines(self.scaled)
  235. else:
  236. dc.DrawLines(coord) # draw legend line
  237. def getSymExtent(self, printerScale):
  238. """Width and Height of Marker"""
  239. h = self.attributes['width'] * printerScale * self._pointSize[0]
  240. w = 5 * h
  241. return (w, h)
  242. class PolySpline(PolyLine):
  243. """Class to define line type and style
  244. - All methods except __init__ are private.
  245. """
  246. _attributes = {'colour': 'black',
  247. 'width': 1,
  248. 'style': wx.PENSTYLE_SOLID,
  249. 'legend': ''}
  250. def __init__(self, points, **attr):
  251. """
  252. Creates PolyLine object
  253. :param `points`: sequence (array, tuple or list) of (x,y) points making up spline
  254. :keyword `attr`: keyword attributes, default to:
  255. ========================== ================================
  256. 'colour'= 'black' wx.Pen Colour any wx.Colour
  257. 'width'= 1 Pen width
  258. 'style'= wx.PENSTYLE_SOLID wx.Pen style
  259. 'legend'= '' Line Legend to display
  260. ========================== ================================
  261. """
  262. PolyLine.__init__(self, points, **attr)
  263. def draw(self, dc, printerScale, coord=None):
  264. colour = self.attributes['colour']
  265. width = self.attributes['width'] * printerScale * self._pointSize[0]
  266. style = self.attributes['style']
  267. if not isinstance(colour, wx.Colour):
  268. colour = wx.NamedColour(colour)
  269. pen = wx.Pen(colour, width, style)
  270. pen.SetCap(wx.CAP_ROUND)
  271. dc.SetPen(pen)
  272. if coord is None:
  273. if len(self.scaled): # bugfix for Mac OS X
  274. dc.DrawSpline(self.scaled)
  275. else:
  276. dc.DrawLines(coord) # draw legend line
  277. class PolyMarker(PolyPoints):
  278. """Class to define marker type and style
  279. - All methods except __init__ are private.
  280. """
  281. _attributes = {'colour': 'black',
  282. 'width': 1,
  283. 'size': 2,
  284. 'fillcolour': None,
  285. 'fillstyle': wx.BRUSHSTYLE_SOLID,
  286. 'marker': 'circle',
  287. 'legend': ''}
  288. def __init__(self, points, **attr):
  289. """
  290. Creates PolyMarker object
  291. :param `points`: sequence (array, tuple or list) of (x,y) points
  292. :keyword `attr`: keyword attributes, default to:
  293. ================================ ================================
  294. 'colour'= 'black' wx.Pen Colour any wx.Colour
  295. 'width'= 1 Pen width
  296. 'size'= 2 Marker size
  297. 'fillcolour'= same as colour wx.Brush Colour any wx.Colour
  298. 'fillstyle'= wx.BRUSHSTYLE_SOLID wx.Brush fill style (use wx.BRUSHSTYLE_TRANSPARENT for no fill)
  299. 'style'= wx.FONTFAMILY_SOLID wx.Pen style
  300. 'marker'= 'circle' Marker shape
  301. 'legend'= '' Line Legend to display
  302. ================================ ================================
  303. Marker Shapes:
  304. - 'circle'
  305. - 'dot'
  306. - 'square'
  307. - 'triangle'
  308. - 'triangle_down'
  309. - 'cross'
  310. - 'plus'
  311. """
  312. PolyPoints.__init__(self, points, attr)
  313. def draw(self, dc, printerScale, coord=None):
  314. colour = self.attributes['colour']
  315. width = self.attributes['width'] * printerScale * self._pointSize[0]
  316. size = self.attributes['size'] * printerScale * self._pointSize[0]
  317. fillcolour = self.attributes['fillcolour']
  318. fillstyle = self.attributes['fillstyle']
  319. marker = self.attributes['marker']
  320. if colour and not isinstance(colour, wx.Colour):
  321. colour = wx.NamedColour(colour)
  322. if fillcolour and not isinstance(fillcolour, wx.Colour):
  323. fillcolour = wx.NamedColour(fillcolour)
  324. dc.SetPen(wx.Pen(colour, width))
  325. if fillcolour:
  326. dc.SetBrush(wx.Brush(fillcolour, fillstyle))
  327. else:
  328. dc.SetBrush(wx.Brush(colour, fillstyle))
  329. if coord is None:
  330. if len(self.scaled): # bugfix for Mac OS X
  331. self._drawmarkers(dc, self.scaled, marker, size)
  332. else:
  333. self._drawmarkers(dc, coord, marker, size) # draw legend marker
  334. def getSymExtent(self, printerScale):
  335. """Width and Height of Marker"""
  336. s = 5 * self.attributes['size'] * printerScale * self._pointSize[0]
  337. return (s, s)
  338. def _drawmarkers(self, dc, coords, marker, size=1):
  339. f = eval('self._' + marker)
  340. f(dc, coords, size)
  341. def _circle(self, dc, coords, size=1):
  342. fact = 2.5 * size
  343. wh = 5.0 * size
  344. rect = np.zeros((len(coords), 4), np.float) + [0.0, 0.0, wh, wh]
  345. rect[:, 0:2] = coords - [fact, fact]
  346. dc.DrawEllipseList(rect.astype(np.int32))
  347. def _dot(self, dc, coords, size=1):
  348. dc.DrawPointList(coords)
  349. def _square(self, dc, coords, size=1):
  350. fact = 2.5 * size
  351. wh = 5.0 * size
  352. rect = np.zeros((len(coords), 4), np.float) + [0.0, 0.0, wh, wh]
  353. rect[:, 0:2] = coords - [fact, fact]
  354. dc.DrawRectangleList(rect.astype(np.int32))
  355. def _triangle(self, dc, coords, size=1):
  356. shape = [(-2.5 * size, 1.44 * size),
  357. (2.5 * size, 1.44 * size), (0.0, -2.88 * size)]
  358. poly = np.repeat(coords, 3, 0)
  359. poly.shape = (len(coords), 3, 2)
  360. poly += shape
  361. dc.DrawPolygonList(poly.astype(np.int32))
  362. def _triangle_down(self, dc, coords, size=1):
  363. shape = [(-2.5 * size, -1.44 * size),
  364. (2.5 * size, -1.44 * size), (0.0, 2.88 * size)]
  365. poly = np.repeat(coords, 3, 0)
  366. poly.shape = (len(coords), 3, 2)
  367. poly += shape
  368. dc.DrawPolygonList(poly.astype(np.int32))
  369. def _cross(self, dc, coords, size=1):
  370. fact = 2.5 * size
  371. for f in [[-fact, -fact, fact, fact], [-fact, fact, fact, -fact]]:
  372. lines = np.concatenate((coords, coords), axis=1) + f
  373. dc.DrawLineList(lines.astype(np.int32))
  374. def _plus(self, dc, coords, size=1):
  375. fact = 2.5 * size
  376. for f in [[-fact, 0, fact, 0], [0, -fact, 0, fact]]:
  377. lines = np.concatenate((coords, coords), axis=1) + f
  378. dc.DrawLineList(lines.astype(np.int32))
  379. class PlotGraphics:
  380. """Container to hold PolyXXX objects and graph labels
  381. - All methods except __init__ are private.
  382. """
  383. def __init__(self, objects, title='', xLabel='', yLabel=''):
  384. """Creates PlotGraphics object
  385. objects - list of PolyXXX objects to make graph
  386. title - title shown at top of graph
  387. xLabel - label shown on x-axis
  388. yLabel - label shown on y-axis
  389. """
  390. if type(objects) not in [list, tuple]:
  391. raise TypeError("objects argument should be list or tuple")
  392. self.objects = objects
  393. self.title = title
  394. self.xLabel = xLabel
  395. self.yLabel = yLabel
  396. self._pointSize = (1.0, 1.0)
  397. def setLogScale(self, logscale):
  398. if type(logscale) != tuple:
  399. raise TypeError(
  400. 'logscale must be a tuple of bools, e.g. (False, False)')
  401. if len(self.objects) == 0:
  402. return
  403. for o in self.objects:
  404. o.setLogScale(logscale)
  405. def boundingBox(self):
  406. p1, p2 = self.objects[0].boundingBox()
  407. for o in self.objects[1:]:
  408. p1o, p2o = o.boundingBox()
  409. p1 = np.minimum(p1, p1o)
  410. p2 = np.maximum(p2, p2o)
  411. return p1, p2
  412. def scaleAndShift(self, scale=(1, 1), shift=(0, 0)):
  413. for o in self.objects:
  414. o.scaleAndShift(scale, shift)
  415. def setPrinterScale(self, scale):
  416. """Thickens up lines and markers only for printing"""
  417. self.printerScale = scale
  418. def setXLabel(self, xLabel=''):
  419. """Set the X axis label on the graph"""
  420. self.xLabel = xLabel
  421. def setYLabel(self, yLabel=''):
  422. """Set the Y axis label on the graph"""
  423. self.yLabel = yLabel
  424. def setTitle(self, title=''):
  425. """Set the title at the top of graph"""
  426. self.title = title
  427. def getXLabel(self):
  428. """Get x axis label string"""
  429. return self.xLabel
  430. def getYLabel(self):
  431. """Get y axis label string"""
  432. return self.yLabel
  433. def getTitle(self, title=''):
  434. """Get the title at the top of graph"""
  435. return self.title
  436. def draw(self, dc):
  437. for o in self.objects:
  438. # t=_time.clock() # profile info
  439. o._pointSize = self._pointSize
  440. o.draw(dc, self.printerScale)
  441. #dt= _time.clock()-t
  442. #print(o, "time=", dt)
  443. def getSymExtent(self, printerScale):
  444. """Get max width and height of lines and markers symbols for legend"""
  445. self.objects[0]._pointSize = self._pointSize
  446. symExt = self.objects[0].getSymExtent(printerScale)
  447. for o in self.objects[1:]:
  448. o._pointSize = self._pointSize
  449. oSymExt = o.getSymExtent(printerScale)
  450. symExt = np.maximum(symExt, oSymExt)
  451. return symExt
  452. def getLegendNames(self):
  453. """Returns list of legend names"""
  454. lst = [None] * len(self)
  455. for i in range(len(self)):
  456. lst[i] = self.objects[i].getLegend()
  457. return lst
  458. def __len__(self):
  459. return len(self.objects)
  460. def __getitem__(self, item):
  461. return self.objects[item]
  462. #-------------------------------------------------------------------------
  463. # Main window that you will want to import into your application.
  464. class PlotCanvas(wx.Panel):
  465. """
  466. Subclass of a wx.Panel which holds two scrollbars and the actual
  467. plotting canvas (self.canvas). It allows for simple general plotting
  468. of data with zoom, labels, and automatic axis scaling."""
  469. def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
  470. size=wx.DefaultSize, style=0, name="plotCanvas"):
  471. """Constructs a panel, which can be a child of a frame or
  472. any other non-control window"""
  473. wx.Panel.__init__(self, parent, id, pos, size, style, name)
  474. self._isWindowCreated = False
  475. sizer = wx.FlexGridSizer(2, 2, 0, 0)
  476. self.canvas = wx.Window(self, -1)
  477. self.sb_vert = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL)
  478. self.sb_vert.SetScrollbar(0, 1000, 1000, 1000)
  479. self.sb_hor = wx.ScrollBar(self, -1, style=wx.SB_HORIZONTAL)
  480. self.sb_hor.SetScrollbar(0, 1000, 1000, 1000)
  481. sizer.Add(self.canvas, 1, wx.EXPAND)
  482. sizer.Add(self.sb_vert, 0, wx.EXPAND)
  483. sizer.Add(self.sb_hor, 0, wx.EXPAND)
  484. sizer.Add((0, 0))
  485. sizer.AddGrowableRow(0, 1)
  486. sizer.AddGrowableCol(0, 1)
  487. self.sb_vert.Show(False)
  488. self.sb_hor.Show(False)
  489. self.SetSizer(sizer)
  490. self.Fit()
  491. self.border = (1, 1)
  492. self.SetBackgroundColour("white")
  493. # Create some mouse events for zooming
  494. self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
  495. self.canvas.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
  496. self.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
  497. self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
  498. self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
  499. # scrollbar events
  500. self.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScroll)
  501. self.Bind(wx.EVT_SCROLL_PAGEUP, self.OnScroll)
  502. self.Bind(wx.EVT_SCROLL_PAGEDOWN, self.OnScroll)
  503. self.Bind(wx.EVT_SCROLL_LINEUP, self.OnScroll)
  504. self.Bind(wx.EVT_SCROLL_LINEDOWN, self.OnScroll)
  505. # set curser as cross-hairs
  506. self.canvas.SetCursor(wx.CROSS_CURSOR)
  507. ## self.HandCursor = wx.Cursor(Hand.GetImage())
  508. self.HandCursor = wx.CursorFromImage(Hand.GetImage())
  509. self.GrabHandCursor = wx.CursorFromImage(GrabHand.GetImage())
  510. ## self.GrabHandCursor = wx.Cursor(GrabHand.GetImage())
  511. ## self.MagCursor = wx.Cursor(MagPlus.GetImage())
  512. self.MagCursor = wx.CursorFromImage(MagPlus.GetImage())
  513. # Things for printing
  514. self._print_data = None
  515. self._pageSetupData = None
  516. self.printerScale = 1
  517. self.parent = parent
  518. # scrollbar variables
  519. self._sb_ignore = False
  520. self._adjustingSB = False
  521. self._sb_xfullrange = 0
  522. self._sb_yfullrange = 0
  523. self._sb_xunit = 0
  524. self._sb_yunit = 0
  525. self._dragEnabled = False
  526. self._screenCoordinates = np.array([0.0, 0.0])
  527. self._logscale = (False, False)
  528. # Zooming variables
  529. self._zoomInFactor = 0.5
  530. self._zoomOutFactor = 2
  531. self._zoomCorner1 = np.array([0.0, 0.0]) # left mouse down corner
  532. self._zoomCorner2 = np.array([0.0, 0.0]) # left mouse up corner
  533. self._zoomEnabled = False
  534. self._hasDragged = False
  535. # Drawing Variables
  536. self.last_draw = None
  537. self._pointScale = 1
  538. self._pointShift = 0
  539. self._xSpec = 'auto'
  540. self._ySpec = 'auto'
  541. self._gridEnabled = False
  542. self._legendEnabled = False
  543. self._titleEnabled = True
  544. self._centerLinesEnabled = False
  545. self._diagonalsEnabled = False
  546. # Fonts
  547. self._fontCache = {}
  548. self._fontSizeAxis = 10
  549. self._fontSizeTitle = 15
  550. self._fontSizeLegend = 7
  551. # pointLabels
  552. self._pointLabelEnabled = False
  553. self.last_PointLabel = None
  554. self._pointLabelFunc = None
  555. self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
  556. if sys.platform != "darwin":
  557. self._logicalFunction = wx.EQUIV # (NOT src) XOR dst
  558. else:
  559. # wx.EQUIV not supported on Mac OS X
  560. self._logicalFunction = wx.COPY
  561. self._useScientificNotation = False
  562. self._antiAliasingEnabled = False
  563. self._hiResEnabled = False
  564. self._pointSize = (1.0, 1.0)
  565. self._fontScale = 1.0
  566. self.canvas.Bind(wx.EVT_PAINT, self.OnPaint)
  567. self.canvas.Bind(wx.EVT_SIZE, self.OnSize)
  568. # OnSize called to make sure the buffer is initialized.
  569. # This might result in OnSize getting called twice on some
  570. # platforms at initialization, but little harm done.
  571. self.OnSize(None) # sets the initial size based on client size
  572. self._gridColour = wx.BLACK
  573. if '__WXGTK__' in wx.PlatformInfo:
  574. self.Bind(wx.EVT_WINDOW_CREATE, self.doSetWindowCreated)
  575. else:
  576. self.doSetWindowCreated(None)
  577. def doSetWindowCreated(self, evt):
  578. # OnSize called to make sure the buffer is initialized.
  579. # This might result in OnSize getting called twice on some
  580. # platforms at initialization, but little harm done.
  581. self._isWindowCreated = True
  582. self.OnSize(None)
  583. def SetCursor(self, cursor):
  584. self.canvas.SetCursor(cursor)
  585. def GetGridColour(self):
  586. return self._gridColour
  587. def SetGridColour(self, colour):
  588. if isinstance(colour, wx.Colour):
  589. self._gridColour = colour
  590. else:
  591. self._gridColour = wx.Colour(colour)
  592. # SaveFile
  593. def SaveFile(self, fileName=''):
  594. """Saves the file to the type specified in the extension. If no file
  595. name is specified a dialog box is provided. Returns True if sucessful,
  596. otherwise False.
  597. .bmp Save a Windows bitmap file.
  598. .xbm Save an X bitmap file.
  599. .xpm Save an XPM bitmap file.
  600. .png Save a Portable Network Graphics file.
  601. .jpg Save a Joint Photographic Experts Group file.
  602. """
  603. extensions = {
  604. "bmp": wx.BITMAP_TYPE_BMP, # Save a Windows bitmap file.
  605. "xbm": wx.BITMAP_TYPE_XBM, # Save an X bitmap file.
  606. "xpm": wx.BITMAP_TYPE_XPM, # Save an XPM bitmap file.
  607. "jpg": wx.BITMAP_TYPE_JPEG, # Save a JPG file.
  608. "png": wx.BITMAP_TYPE_PNG, # Save a PNG file.
  609. }
  610. fType = _string.lower(fileName[-3:])
  611. dlg1 = None
  612. while fType not in extensions:
  613. if dlg1: # FileDialog exists: Check for extension
  614. dlg2 = wx.MessageDialog(self, 'File name extension\n'
  615. 'must be one of\nbmp, xbm, xpm, png, or jpg',
  616. 'File Name Error', wx.OK | wx.ICON_ERROR)
  617. try:
  618. dlg2.ShowModal()
  619. finally:
  620. dlg2.Destroy()
  621. # FileDialog doesn't exist: just check one
  622. else:
  623. dlg1 = wx.FileDialog(
  624. self,
  625. "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
  626. "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
  627. wx.SAVE | wx.OVERWRITE_PROMPT
  628. )
  629. if dlg1.ShowModal() == wx.ID_OK:
  630. fileName = dlg1.GetPath()
  631. fType = _string.lower(fileName[-3:])
  632. else: # exit without saving
  633. dlg1.Destroy()
  634. return False
  635. if dlg1:
  636. dlg1.Destroy()
  637. # Save Bitmap
  638. res = self._Buffer.SaveFile(fileName, extensions[fType])
  639. return res
  640. @property
  641. def print_data(self):
  642. if not self._print_data:
  643. self._print_data = wx.PrintData()
  644. self._print_data.SetPaperId(wx.PAPER_LETTER)
  645. self._print_data.SetOrientation(wx.LANDSCAPE)
  646. return self._print_data
  647. @property
  648. def pageSetupData(self):
  649. if not self._pageSetupData:
  650. self._pageSetupData = wx.PageSetupDialogData()
  651. self._pageSetupData.SetMarginBottomRight((25, 25))
  652. self._pageSetupData.SetMarginTopLeft((25, 25))
  653. self._pageSetupData.SetPrintData(self.print_data)
  654. return self._pageSetupData
  655. def PageSetup(self):
  656. """Brings up the page setup dialog"""
  657. data = self.pageSetupData
  658. data.SetPrintData(self.print_data)
  659. dlg = wx.PageSetupDialog(self.parent, data)
  660. try:
  661. if dlg.ShowModal() == wx.ID_OK:
  662. data = dlg.GetPageSetupData() # returns wx.PageSetupDialogData
  663. # updates page parameters from dialog
  664. self.pageSetupData.SetMarginBottomRight(
  665. data.GetMarginBottomRight())
  666. self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft())
  667. self.pageSetupData.SetPrintData(data.GetPrintData())
  668. self._print_data = wx.PrintData(
  669. data.GetPrintData()) # updates print_data
  670. finally:
  671. dlg.Destroy()
  672. def Printout(self, paper=None):
  673. """Print current plot."""
  674. if paper != None:
  675. self.print_data.SetPaperId(paper)
  676. pdd = wx.PrintDialogData(self.print_data)
  677. printer = wx.Printer(pdd)
  678. out = PlotPrintout(self)
  679. print_ok = printer.Print(self.parent, out)
  680. if print_ok:
  681. self._print_data = wx.PrintData(
  682. printer.GetPrintDialogData().GetPrintData())
  683. out.Destroy()
  684. def PrintPreview(self):
  685. """Print-preview current plot."""
  686. printout = PlotPrintout(self)
  687. printout2 = PlotPrintout(self)
  688. self.preview = wx.PrintPreview(printout, printout2, self.print_data)
  689. if not self.preview.IsOk():
  690. wx.MessageDialog(self, "Print Preview failed.\n"
  691. "Check that default printer is configured\n",
  692. "Print error", wx.OK | wx.CENTRE).ShowModal()
  693. self.preview.SetZoom(40)
  694. # search up tree to find frame instance
  695. frameInst = self
  696. while not isinstance(frameInst, wx.Frame):
  697. frameInst = frameInst.GetParent()
  698. frame = wx.PreviewFrame(self.preview, frameInst, "Preview")
  699. frame.Initialize()
  700. frame.SetPosition(self.GetPosition())
  701. frame.SetSize((600, 550))
  702. frame.Centre(wx.BOTH)
  703. frame.Show(True)
  704. def setLogScale(self, logscale):
  705. if type(logscale) != tuple:
  706. raise TypeError(
  707. 'logscale must be a tuple of bools, e.g. (False, False)')
  708. if self.last_draw is not None:
  709. graphics, xAxis, yAxis = self.last_draw
  710. graphics.setLogScale(logscale)
  711. self.last_draw = (graphics, None, None)
  712. self.SetXSpec('min')
  713. self.SetYSpec('min')
  714. self._logscale = logscale
  715. def getLogScale(self):
  716. return self._logscale
  717. def SetFontSizeAxis(self, point=10):
  718. """Set the tick and axis label font size (default is 10 point)"""
  719. self._fontSizeAxis = point
  720. def GetFontSizeAxis(self):
  721. """Get current tick and axis label font size in points"""
  722. return self._fontSizeAxis
  723. def SetFontSizeTitle(self, point=15):
  724. """Set Title font size (default is 15 point)"""
  725. self._fontSizeTitle = point
  726. def GetFontSizeTitle(self):
  727. """Get current Title font size in points"""
  728. return self._fontSizeTitle
  729. def SetFontSizeLegend(self, point=7):
  730. """Set Legend font size (default is 7 point)"""
  731. self._fontSizeLegend = point
  732. def GetFontSizeLegend(self):
  733. """Get current Legend font size in points"""
  734. return self._fontSizeLegend
  735. def SetShowScrollbars(self, value):
  736. """Set True to show scrollbars"""
  737. if value not in [True, False]:
  738. raise TypeError("Value should be True or False")
  739. if value == self.GetShowScrollbars():
  740. return
  741. self.sb_vert.Show(value)
  742. self.sb_hor.Show(value)
  743. wx.CallAfter(self.Layout)
  744. def GetShowScrollbars(self):
  745. """Set True to show scrollbars"""
  746. return self.sb_vert.IsShown()
  747. def SetUseScientificNotation(self, useScientificNotation):
  748. self._useScientificNotation = useScientificNotation
  749. def GetUseScientificNotation(self):
  750. return self._useScientificNotation
  751. def SetEnableAntiAliasing(self, enableAntiAliasing):
  752. """Set True to enable anti-aliasing."""
  753. self._antiAliasingEnabled = enableAntiAliasing
  754. self.Redraw()
  755. def GetEnableAntiAliasing(self):
  756. return self._antiAliasingEnabled
  757. def SetEnableHiRes(self, enableHiRes):
  758. """Set True to enable high-resolution mode when using anti-aliasing."""
  759. self._hiResEnabled = enableHiRes
  760. self.Redraw()
  761. def GetEnableHiRes(self):
  762. return self._hiResEnabled
  763. def SetEnableDrag(self, value):
  764. """Set True to enable drag."""
  765. if value not in [True, False]:
  766. raise TypeError("Value should be True or False")
  767. if value:
  768. if self.GetEnableZoom():
  769. self.SetEnableZoom(False)
  770. self.SetCursor(self.HandCursor)
  771. else:
  772. self.SetCursor(wx.CROSS_CURSOR)
  773. self._dragEnabled = value
  774. def GetEnableDrag(self):
  775. return self._dragEnabled
  776. def SetEnableZoom(self, value):
  777. """Set True to enable zooming."""
  778. if value not in [True, False]:
  779. raise TypeError("Value should be True or False")
  780. if value:
  781. if self.GetEnableDrag():
  782. self.SetEnableDrag(False)
  783. self.SetCursor(self.MagCursor)
  784. else:
  785. self.SetCursor(wx.CROSS_CURSOR)
  786. self._zoomEnabled = value
  787. def GetEnableZoom(self):
  788. """True if zooming enabled."""
  789. return self._zoomEnabled
  790. def SetEnableGrid(self, value):
  791. """Set True, 'Horizontal' or 'Vertical' to enable grid."""
  792. if value not in [True, False, 'Horizontal', 'Vertical']:
  793. raise TypeError(
  794. "Value should be True, False, Horizontal or Vertical")
  795. self._gridEnabled = value
  796. self.Redraw()
  797. def GetEnableGrid(self):
  798. """True if grid enabled."""
  799. return self._gridEnabled
  800. def SetEnableCenterLines(self, value):
  801. """Set True, 'Horizontal' or 'Vertical' to enable center line(s)."""
  802. if value not in [True, False, 'Horizontal', 'Vertical']:
  803. raise TypeError(
  804. "Value should be True, False, Horizontal or Vertical")
  805. self._centerLinesEnabled = value
  806. self.Redraw()
  807. def GetEnableCenterLines(self):
  808. """True if grid enabled."""
  809. return self._centerLinesEnabled
  810. def SetEnableDiagonals(self, value):
  811. """Set True, 'Bottomleft-Topright' or 'Bottomright-Topleft' to enable
  812. center line(s)."""
  813. if value not in [True, False, 'Bottomleft-Topright', 'Bottomright-Topleft']:
  814. raise TypeError(
  815. "Value should be True, False, Bottomleft-Topright or Bottomright-Topleft")
  816. self._diagonalsEnabled = value
  817. self.Redraw()
  818. def GetEnableDiagonals(self):
  819. """True if grid enabled."""
  820. return self._diagonalsEnabled
  821. def SetEnableLegend(self, value):
  822. """Set True to enable legend."""
  823. if value not in [True, False]:
  824. raise TypeError("Value should be True or False")
  825. self._legendEnabled = value
  826. self.Redraw()
  827. def GetEnableLegend(self):
  828. """True if Legend enabled."""
  829. return self._legendEnabled
  830. def SetEnableTitle(self, value):
  831. """Set True to enable title."""
  832. if value not in [True, False]:
  833. raise TypeError("Value should be True or False")
  834. self._titleEnabled = value
  835. self.Redraw()
  836. def GetEnableTitle(self):
  837. """True if title enabled."""
  838. return self._titleEnabled
  839. def SetEnablePointLabel(self, value):
  840. """Set True to enable pointLabel."""
  841. if value not in [True, False]:
  842. raise TypeError("Value should be True or False")
  843. self._pointLabelEnabled = value
  844. self.Redraw() # will erase existing pointLabel if present
  845. self.last_PointLabel = None
  846. def GetEnablePointLabel(self):
  847. """True if pointLabel enabled."""
  848. return self._pointLabelEnabled
  849. def SetPointLabelFunc(self, func):
  850. """Sets the function with custom code for pointLabel drawing
  851. ******** more info needed ***************
  852. """
  853. self._pointLabelFunc = func
  854. def GetPointLabelFunc(self):
  855. """Returns pointLabel Drawing Function"""
  856. return self._pointLabelFunc
  857. def Reset(self):
  858. """Unzoom the plot."""
  859. self.last_PointLabel = None # reset pointLabel
  860. if self.last_draw is not None:
  861. self._Draw(self.last_draw[0])
  862. def ScrollRight(self, units):
  863. """Move view right number of axis units."""
  864. self.last_PointLabel = None # reset pointLabel
  865. if self.last_draw is not None:
  866. graphics, xAxis, yAxis = self.last_draw
  867. xAxis = (xAxis[0] + units, xAxis[1] + units)
  868. self._Draw(graphics, xAxis, yAxis)
  869. def ScrollUp(self, units):
  870. """Move view up number of axis units."""
  871. self.last_PointLabel = None # reset pointLabel
  872. if self.last_draw is not None:
  873. graphics, xAxis, yAxis = self.last_draw
  874. yAxis = (yAxis[0] + units, yAxis[1] + units)
  875. self._Draw(graphics, xAxis, yAxis)
  876. def GetXY(self, event):
  877. """Wrapper around _getXY, which handles log scales"""
  878. x, y = self._getXY(event)
  879. if self.getLogScale()[0]:
  880. x = np.power(10, x)
  881. if self.getLogScale()[1]:
  882. y = np.power(10, y)
  883. return x, y
  884. def _getXY(self, event):
  885. """Takes a mouse event and returns the XY user axis values."""
  886. x, y = self.PositionScreenToUser(event.GetPosition())
  887. return x, y
  888. def PositionUserToScreen(self, pntXY):
  889. """Converts User position to Screen Coordinates"""
  890. userPos = np.array(pntXY)
  891. x, y = userPos * self._pointScale + self._pointShift
  892. return x, y
  893. def PositionScreenToUser(self, pntXY):
  894. """Converts Screen position to User Coordinates"""
  895. screenPos = np.array(pntXY)
  896. x, y = (screenPos - self._pointShift) / self._pointScale
  897. return x, y
  898. def SetXSpec(self, type='auto'):
  899. """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
  900. where:
  901. * 'none' - shows no axis or tick mark values
  902. * 'min' - shows min bounding box values
  903. * 'auto' - rounds axis range to sensible values
  904. * <number> - like 'min', but with <number> tick marks
  905. """
  906. self._xSpec = type
  907. def SetYSpec(self, type='auto'):
  908. """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
  909. where:
  910. * 'none' - shows no axis or tick mark values
  911. * 'min' - shows min bounding box values
  912. * 'auto' - rounds axis range to sensible values
  913. * <number> - like 'min', but with <number> tick marks
  914. """
  915. self._ySpec = type
  916. def GetXSpec(self):
  917. """Returns current XSpec for axis"""
  918. return self._xSpec
  919. def GetYSpec(self):
  920. """Returns current YSpec for axis"""
  921. return self._ySpec
  922. def GetXMaxRange(self):
  923. xAxis = self._getXMaxRange()
  924. if self.getLogScale()[0]:
  925. xAxis = np.power(10, xAxis)
  926. return xAxis
  927. def _getXMaxRange(self):
  928. """Returns (minX, maxX) x-axis range for displayed graph"""
  929. graphics = self.last_draw[0]
  930. p1, p2 = graphics.boundingBox() # min, max points of graphics
  931. xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
  932. return xAxis
  933. def GetYMaxRange(self):
  934. yAxis = self._getYMaxRange()
  935. if self.getLogScale()[1]:
  936. yAxis = np.power(10, yAxis)
  937. return yAxis
  938. def _getYMaxRange(self):
  939. """Returns (minY, maxY) y-axis range for displayed graph"""
  940. graphics = self.last_draw[0]
  941. p1, p2 = graphics.boundingBox() # min, max points of graphics
  942. yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
  943. return yAxis
  944. def GetXCurrentRange(self):
  945. xAxis = self._getXCurrentRange()
  946. if self.getLogScale()[0]:
  947. xAxis = np.power(10, xAxis)
  948. return xAxis
  949. def _getXCurrentRange(self):
  950. """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
  951. return self.last_draw[1]
  952. def GetYCurrentRange(self):
  953. yAxis = self._getYCurrentRange()
  954. if self.getLogScale()[1]:
  955. yAxis = np.power(10, yAxis)
  956. return yAxis
  957. def _getYCurrentRange(self):
  958. """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
  959. return self.last_draw[2]
  960. def Draw(self, graphics, xAxis=None, yAxis=None, dc=None):
  961. """Wrapper around _Draw, which handles log axes"""
  962. graphics.setLogScale(self.getLogScale())
  963. # check Axis is either tuple or none
  964. if type(xAxis) not in [type(None), tuple]:
  965. raise TypeError(
  966. "xAxis should be None or (minX,maxX)" + str(type(xAxis)))
  967. if type(yAxis) not in [type(None), tuple]:
  968. raise TypeError(
  969. "yAxis should be None or (minY,maxY)" + str(type(xAxis)))
  970. # check case for axis = (a,b) where a==b caused by improper zooms
  971. if xAxis != None:
  972. if xAxis[0] == xAxis[1]:
  973. return
  974. if self.getLogScale()[0]:
  975. xAxis = np.log10(xAxis)
  976. if yAxis != None:
  977. if yAxis[0] == yAxis[1]:
  978. return
  979. if self.getLogScale()[1]:
  980. yAxis = np.log10(yAxis)
  981. self._Draw(graphics, xAxis, yAxis, dc)
  982. def _Draw(self, graphics, xAxis=None, yAxis=None, dc=None):
  983. """\
  984. Draw objects in graphics with specified x and y axis.
  985. graphics- instance of PlotGraphics with list of PolyXXX objects
  986. xAxis - tuple with (min, max) axis range to view
  987. yAxis - same as xAxis
  988. dc - drawing context - doesn't have to be specified.
  989. If it's not, the offscreen buffer is used
  990. """
  991. if dc is None:
  992. # sets new dc and clears it
  993. dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
  994. bbr = wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)
  995. dc.SetBackground(bbr)
  996. dc.SetBackgroundMode(wx.SOLID)
  997. dc.Clear()
  998. if self._antiAliasingEnabled:
  999. if not isinstance(dc, wx.GCDC):
  1000. try:
  1001. dc = wx.GCDC(dc)
  1002. except Exception:
  1003. pass
  1004. else:
  1005. if self._hiResEnabled:
  1006. # high precision - each logical unit is 1/20 of a point
  1007. dc.SetMapMode(wx.MM_TWIPS)
  1008. self._pointSize = tuple(
  1009. 1.0 / lscale for lscale in dc.GetLogicalScale())
  1010. self._setSize()
  1011. elif self._pointSize != (1.0, 1.0):
  1012. self._pointSize = (1.0, 1.0)
  1013. self._setSize()
  1014. if (sys.platform in ("darwin", "win32") or not isinstance(dc, wx.GCDC) or wx.VERSION >= (2, 9)):
  1015. self._fontScale = sum(self._pointSize) / 2.0
  1016. else:
  1017. # on Linux, we need to correct the font size by a certain factor if wx.GCDC is used,
  1018. # to make text the same size as if wx.GCDC weren't used
  1019. screenppi = map(float, wx.ScreenDC().GetPPI())
  1020. ppi = dc.GetPPI()
  1021. self._fontScale = (screenppi[
  1022. 0] / ppi[0] * self._pointSize[0] + screenppi[1] / ppi[1] * self._pointSize[1]) / 2.0
  1023. graphics._pointSize = self._pointSize
  1024. dc.SetTextForeground(self.GetForegroundColour())
  1025. dc.SetTextBackground(self.GetBackgroundColour())
  1026. # dc.Clear()
  1027. # set font size for every thing but title and legend
  1028. dc.SetFont(self._getFont(self._fontSizeAxis))
  1029. # sizes axis to axis type, create lower left and upper right corners of
  1030. # plot
  1031. if xAxis is None or yAxis is None:
  1032. # One or both axis not specified in Draw
  1033. p1, p2 = graphics.boundingBox() # min, max points of graphics
  1034. if xAxis is None:
  1035. xAxis = self._axisInterval(
  1036. self._xSpec, p1[0], p2[0]) # in user units
  1037. if yAxis is None:
  1038. yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
  1039. # Adjust bounding box for axis spec
  1040. # lower left corner user scale (xmin,ymin)
  1041. p1[0], p1[1] = xAxis[0], yAxis[0]
  1042. # upper right corner user scale (xmax,ymax)
  1043. p2[0], p2[1] = xAxis[1], yAxis[1]
  1044. else:
  1045. # Both axis specified in Draw
  1046. # lower left corner user scale (xmin,ymin)
  1047. p1 = np.array([xAxis[0], yAxis[0]])
  1048. # upper right corner user scale (xmax,ymax)
  1049. p2 = np.array([xAxis[1], yAxis[1]])
  1050. # saves most recient values
  1051. self.last_draw = (graphics, np.array(xAxis), np.array(yAxis))
  1052. # Get ticks and textExtents for axis if required
  1053. if self._xSpec is not 'none':
  1054. xticks = self._xticks(xAxis[0], xAxis[1])
  1055. else:
  1056. xticks = None
  1057. if xticks:
  1058. # w h of x axis text last number on axis
  1059. xTextExtent = dc.GetTextExtent(xticks[-1][1])
  1060. else:
  1061. xTextExtent = (0, 0) # No text for ticks
  1062. if self._ySpec is not 'none':
  1063. yticks = self._yticks(yAxis[0], yAxis[1])
  1064. else:
  1065. yticks = None
  1066. if yticks:
  1067. if self.getLogScale()[1]:
  1068. yTextExtent = dc.GetTextExtent('-2e-2')
  1069. else:
  1070. yTextExtentBottom = dc.GetTextExtent(yticks[0][1])
  1071. yTextExtentTop = dc.GetTextExtent(yticks[-1][1])
  1072. yTextExtent = (max(yTextExtentBottom[0], yTextExtentTop[0]),
  1073. max(yTextExtentBottom[1], yTextExtentTop[1]))
  1074. else:
  1075. yticks = None
  1076. yTextExtent = (0, 0) # No text for ticks
  1077. # TextExtents for Title and Axis Labels
  1078. titleWH, xLabelWH, yLabelWH = self._titleLablesWH(dc, graphics)
  1079. # TextExtents for Legend
  1080. legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
  1081. # room around graph area
  1082. # use larger of number width or legend width
  1083. rhsW = max(xTextExtent[0], legendBoxWH[0]) + 5 * self._pointSize[0]
  1084. lhsW = yTextExtent[0] + yLabelWH[1] + 3 * self._pointSize[0]
  1085. bottomH = max(
  1086. xTextExtent[1], yTextExtent[1] / 2.) + xLabelWH[1] + 2 * self._pointSize[1]
  1087. topH = yTextExtent[1] / 2. + titleWH[1]
  1088. # make plot area smaller by text size
  1089. textSize_scale = np.array([rhsW + lhsW, bottomH + topH])
  1090. # shift plot area by this amount
  1091. textSize_shift = np.array([lhsW, bottomH])
  1092. # draw title if requested
  1093. if self._titleEnabled:
  1094. dc.SetFont(self._getFont(self._fontSizeTitle))
  1095. titlePos = (self.plotbox_origin[0] + lhsW + (self.plotbox_size[0] - lhsW - rhsW) / 2. - titleWH[0] / 2.,
  1096. self.plotbox_origin[1] - self.plotbox_size[1])
  1097. dc.DrawText(graphics.getTitle(), titlePos[0], titlePos[1])
  1098. # draw label text
  1099. dc.SetFont(self._getFont(self._fontSizeAxis))
  1100. xLabelPos = (self.plotbox_origin[0] + lhsW + (self.plotbox_size[0] - lhsW - rhsW) / 2. - xLabelWH[0] / 2.,
  1101. self.plotbox_origin[1] - xLabelWH[1])
  1102. dc.DrawText(graphics.getXLabel(), xLabelPos[0], xLabelPos[1])
  1103. yLabelPos = (self.plotbox_origin[0] - 3 * self._pointSize[0],
  1104. self.plotbox_origin[1] - bottomH - (self.plotbox_size[1] - bottomH - topH) / 2. + yLabelWH[0] / 2.)
  1105. if graphics.getYLabel(): # bug fix for Linux
  1106. dc.DrawRotatedText(
  1107. graphics.getYLabel(), yLabelPos[0], yLabelPos[1], 90)
  1108. # drawing legend makers and text
  1109. if self._legendEnabled:
  1110. self._drawLegend(
  1111. dc, graphics, rhsW, topH, legendBoxWH, legendSymExt, legendTextExt)
  1112. # allow for scaling and shifting plotted points
  1113. scale = (self.plotbox_size - textSize_scale) / \
  1114. (p2 - p1) * np.array((1, -1))
  1115. shift = -p1 * scale + self.plotbox_origin + \
  1116. textSize_shift * np.array((1, -1))
  1117. # make available for mouse events
  1118. self._pointScale = scale / self._pointSize
  1119. self._pointShift = shift / self._pointSize
  1120. self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
  1121. graphics.scaleAndShift(scale, shift)
  1122. # thicken up lines and markers if printing
  1123. graphics.setPrinterScale(self.printerScale)
  1124. # set clipping area so drawing does not occur outside axis box
  1125. ptx, pty, rectWidth, rectHeight = self._point2ClientCoord(p1, p2)
  1126. # allow graph to overlap axis lines by adding units to width and height
  1127. dc.SetClippingRegion(ptx * self._pointSize[0], pty * self._pointSize[
  1128. 1], rectWidth * self._pointSize[0] + 2, rectHeight * self._pointSize[1] + 1)
  1129. # Draw the lines and markers
  1130. #start = _time.clock()
  1131. graphics.draw(dc)
  1132. # print("entire graphics drawing took: %f second"%(_time.clock() - start))
  1133. # remove the clipping region
  1134. dc.DestroyClippingRegion()
  1135. self._adjustScrollbars()
  1136. def Redraw(self, dc=None):
  1137. """Redraw the existing plot."""
  1138. if self.last_draw is not None:
  1139. graphics, xAxis, yAxis = self.last_draw
  1140. self._Draw(graphics, xAxis, yAxis, dc)
  1141. def Clear(self):
  1142. """Erase the window."""
  1143. self.last_PointLabel = None # reset pointLabel
  1144. dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
  1145. bbr = wx.Brush(self.GetBackgroundColour(), wx.SOLID)
  1146. dc.SetBackground(bbr)
  1147. dc.SetBackgroundMode(wx.SOLID)
  1148. dc.Clear()
  1149. if self._antiAliasingEnabled:
  1150. try:
  1151. dc = wx.GCDC(dc)
  1152. except Exception:
  1153. pass
  1154. dc.SetTextForeground(self.GetForegroundColour())
  1155. dc.SetTextBackground(self.GetBackgroundColour())
  1156. self.last_draw = None
  1157. def Zoom(self, Center, Ratio):
  1158. """ Zoom on the plot
  1159. Centers on the X,Y coords given in Center
  1160. Zooms by the Ratio = (Xratio, Yratio) given
  1161. """
  1162. self.last_PointLabel = None # reset maker
  1163. x, y = Center
  1164. if self.last_draw != None:
  1165. (graphics, xAxis, yAxis) = self.last_draw
  1166. w = (xAxis[1] - xAxis[0]) * Ratio[0]
  1167. h = (yAxis[1] - yAxis[0]) * Ratio[1]
  1168. xAxis = (x - w / 2, x + w / 2)
  1169. yAxis = (y - h / 2, y + h / 2)
  1170. self._Draw(graphics, xAxis, yAxis)
  1171. def GetClosestPoints(self, pntXY, pointScaled=True):
  1172. """Returns list with
  1173. [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
  1174. list for each curve.
  1175. Returns [] if no curves are being plotted.
  1176. x, y in user coords
  1177. if pointScaled == True based on screen coords
  1178. if pointScaled == False based on user coords
  1179. """
  1180. if self.last_draw is None:
  1181. # no graph available
  1182. return []
  1183. graphics, xAxis, yAxis = self.last_draw
  1184. l = []
  1185. for curveNum, obj in enumerate(graphics):
  1186. # check there are points in the curve
  1187. if len(obj.points) == 0:
  1188. continue # go to next obj
  1189. #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
  1190. cn = [curveNum] + \
  1191. [obj.getLegend()] + obj.getClosestPoint(pntXY, pointScaled)
  1192. l.append(cn)
  1193. return l
  1194. def GetClosestPoint(self, pntXY, pointScaled=True):
  1195. """Returns list with
  1196. [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
  1197. list for only the closest curve.
  1198. Returns [] if no curves are being plotted.
  1199. x, y in user coords
  1200. if pointScaled == True based on screen coords
  1201. if pointScaled == False based on user coords
  1202. """
  1203. # closest points on screen based on screen scaling (pointScaled= True)
  1204. # list [curveNumber, index, pointXY, scaledXY, distance] for each curve
  1205. closestPts = self.GetClosestPoints(pntXY, pointScaled)
  1206. if closestPts == []:
  1207. return [] # no graph present
  1208. # find one with least distance
  1209. dists = [c[-1] for c in closestPts]
  1210. mdist = min(dists) # Min dist
  1211. i = dists.index(mdist) # index for min dist
  1212. return closestPts[i] # this is the closest point on closest curve
  1213. GetClosetPoint = GetClosestPoint
  1214. def UpdatePointLabel(self, mDataDict):
  1215. """Updates the pointLabel point on screen with data contained in
  1216. mDataDict.
  1217. mDataDict will be passed to your function set by
  1218. SetPointLabelFunc. It can contain anything you
  1219. want to display on the screen at the scaledXY point
  1220. you specify.
  1221. This function can be called from parent window with onClick,
  1222. onMotion events etc.
  1223. """
  1224. if self.last_PointLabel != None:
  1225. # compare pointXY
  1226. if np.sometrue(mDataDict["pointXY"] != self.last_PointLabel["pointXY"]):
  1227. # closest changed
  1228. self._drawPointLabel(self.last_PointLabel) # erase old
  1229. self._drawPointLabel(mDataDict) # plot new
  1230. else:
  1231. # just plot new with no erase
  1232. self._drawPointLabel(mDataDict) # plot new
  1233. # save for next erase
  1234. self.last_PointLabel = mDataDict
  1235. # event handlers **********************************
  1236. def OnMotion(self, event):
  1237. if self._zoomEnabled and event.LeftIsDown():
  1238. if self._hasDragged:
  1239. self._drawRubberBand(
  1240. self._zoomCorner1, self._zoomCorner2) # remove old
  1241. else:
  1242. self._hasDragged = True
  1243. self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event)
  1244. self._drawRubberBand(
  1245. self._zoomCorner1, self._zoomCorner2) # add new
  1246. elif self._dragEnabled and event.LeftIsDown():
  1247. coordinates = event.GetPosition()
  1248. newpos, oldpos = map(np.array, map(
  1249. self.PositionScreenToUser, [coordinates, self._screenCoordinates]))
  1250. dist = newpos - oldpos
  1251. self._screenCoordinates = coordinates
  1252. if self.last_draw is not None:
  1253. graphics, xAxis, yAxis = self.last_draw
  1254. yAxis -= dist[1]
  1255. xAxis -= dist[0]
  1256. self._Draw(graphics, xAxis, yAxis)
  1257. def OnMouseLeftDown(self, event):
  1258. self._zoomCorner1[0], self._zoomCorner1[1] = self._getXY(event)
  1259. self._screenCoordinates = np.array(event.GetPosition())
  1260. if self._dragEnabled:
  1261. self.SetCursor(self.GrabHandCursor)
  1262. self.canvas.CaptureMouse()
  1263. def OnMouseLeftUp(self, event):
  1264. if self._zoomEnabled:
  1265. if self._hasDragged == True:
  1266. self._drawRubberBand(
  1267. self._zoomCorner1, self._zoomCorner2) # remove old
  1268. self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event)
  1269. self._hasDragged = False # reset flag
  1270. minX, minY = np.minimum(self._zoomCorner1, self._zoomCorner2)
  1271. maxX, maxY = np.maximum(self._zoomCorner1, self._zoomCorner2)
  1272. self.last_PointLabel = None # reset pointLabel
  1273. if self.last_draw != None:
  1274. self._Draw(
  1275. self.last_draw[0], xAxis=(minX, maxX), yAxis = (minY, maxY), dc = None)
  1276. # else: # A box has not been drawn, zoom in on a point
  1277. # this interfered with the double click, so I've disables it.
  1278. # X,Y = self._getXY(event)
  1279. # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
  1280. if self._dragEnabled:
  1281. self.SetCursor(self.HandCursor)
  1282. if self.canvas.HasCapture():
  1283. self.canvas.ReleaseMouse()
  1284. def OnMouseDoubleClick(self, event):
  1285. if self._zoomEnabled:
  1286. # Give a little time for the click to be totally finished
  1287. # before (possibly) removing the scrollbars and trigering
  1288. # size events, etc.
  1289. wx.CallLater(200, self.Reset)
  1290. def OnMouseRightDown(self, event):
  1291. if self._zoomEnabled:
  1292. X, Y = self._getXY(event)
  1293. self.Zoom((X, Y), (self._zoomOutFactor, self._zoomOutFactor))
  1294. def OnPaint(self, event):
  1295. # All that is needed here is to draw the buffer to screen
  1296. if self.last_PointLabel != None:
  1297. self._drawPointLabel(self.last_PointLabel) # erase old
  1298. self.last_PointLabel = None
  1299. dc = wx.BufferedPaintDC(self.canvas, self._Buffer)
  1300. if self._antiAliasingEnabled:
  1301. try:
  1302. dc = wx.GCDC(dc)
  1303. except Exception:
  1304. pass
  1305. def OnSize(self, event):
  1306. # The Buffer init is done here, to make sure the buffer is always
  1307. # the same size as the Window
  1308. Size = self.canvas.GetClientSize()
  1309. Size.width = max(1, Size.width)
  1310. Size.height = max(1, Size.height)
  1311. # Make new offscreen bitmap: this bitmap will always have the
  1312. # current drawing in it, so it can be used to save the image to
  1313. # a file, or whatever.
  1314. self._Buffer = wx.EmptyBitmap(Size.width, Size.height)
  1315. self._setSize()
  1316. if not self._isWindowCreated:
  1317. return
  1318. self.last_PointLabel = None # reset pointLabel
  1319. if self.last_draw is None:
  1320. self.Clear()
  1321. else:
  1322. graphics, xSpec, ySpec = self.last_draw
  1323. self._Draw(graphics, xSpec, ySpec)
  1324. def OnLeave(self, event):
  1325. """Used to erase pointLabel when mouse outside window"""
  1326. if self.last_PointLabel != None:
  1327. self._drawPointLabel(self.last_PointLabel) # erase old
  1328. self.last_PointLabel = None
  1329. def OnScroll(self, evt):
  1330. if not self._adjustingSB:
  1331. self._sb_ignore = True
  1332. sbpos = evt.GetPosition()
  1333. if evt.GetOrientation() == wx.VERTICAL:
  1334. fullrange, pagesize = self.sb_vert.GetRange(
  1335. ), self.sb_vert.GetPageSize()
  1336. sbpos = fullrange - pagesize - sbpos
  1337. dist = sbpos * self._sb_xunit - \
  1338. (self._getXCurrentRange()[0] - self._sb_xfullrange)
  1339. self.ScrollUp(dist)
  1340. if evt.GetOrientation() == wx.HORIZONTAL:
  1341. dist = sbpos * self._sb_xunit - \
  1342. (self._getXCurrentRange()[0] - self._sb_xfullrange[0])
  1343. self.ScrollRight(dist)
  1344. # Private Methods **************************************************
  1345. def _setSize(self, width=None, height=None):
  1346. """DC width and height."""
  1347. if width is None:
  1348. (self.width, self.height) = self.canvas.GetClientSize()
  1349. else:
  1350. self.width, self.height = width, height
  1351. self.width *= self._pointSize[0] # high precision
  1352. self.height *= self._pointSize[1] # high precision
  1353. self.plotbox_size = 0.97 * np.array([self.width, self.height])
  1354. xo = 0.5 * (self.width - self.plotbox_size[0])
  1355. yo = self.height - 0.5 * (self.height - self.plotbox_size[1])
  1356. self.plotbox_origin = np.array([xo, yo])
  1357. def _setPrinterScale(self, scale):
  1358. """Used to thicken lines and increase marker size for print out."""
  1359. # line thickness on printer is very thin at 600 dot/in. Markers small
  1360. self.printerScale = scale
  1361. def _printDraw(self, printDC):
  1362. """Used for printing."""
  1363. if self.last_draw != None:
  1364. graphics, xSpec, ySpec = self.last_draw
  1365. self._Draw(graphics, xSpec, ySpec, printDC)
  1366. def _drawPointLabel(self, mDataDict):
  1367. """Draws and erases pointLabels"""
  1368. width = self._Buffer.GetWidth()
  1369. height = self._Buffer.GetHeight()
  1370. if sys.platform != "darwin":
  1371. tmp_Buffer = wx.EmptyBitmap(width, height)
  1372. dcs = wx.MemoryDC()
  1373. dcs.SelectObject(tmp_Buffer)
  1374. dcs.Clear()
  1375. else:
  1376. tmp_Buffer = self._Buffer.GetSubBitmap((0, 0, width, height))
  1377. dcs = wx.MemoryDC(self._Buffer)
  1378. self._pointLabelFunc(dcs, mDataDict) # custom user pointLabel function
  1379. dc = wx.ClientDC(self.canvas)
  1380. dc = wx.BufferedDC(dc, self._Buffer)
  1381. # this will erase if called twice
  1382. dc.Blit(0, 0, width, height, dcs, 0, 0, self._logicalFunction)
  1383. if sys.platform == "darwin":
  1384. self._Buffer = tmp_Buffer
  1385. def _drawLegend(self, dc, graphics, rhsW, topH, legendBoxWH, legendSymExt, legendTextExt):
  1386. """Draws legend symbols and text"""
  1387. # top right hand corner of graph box is ref corner
  1388. trhc = self.plotbox_origin + \
  1389. (self.plotbox_size - [rhsW, topH]) * [1, -1]
  1390. # border space between legend sym and graph box
  1391. legendLHS = .091 * legendBoxWH[0]
  1392. # 1.1 used as space between lines
  1393. lineHeight = max(legendSymExt[1], legendTextExt[1]) * 1.1
  1394. dc.SetFont(self._getFont(self._fontSizeLegend))
  1395. for i in range(len(graphics)):
  1396. o = graphics[i]
  1397. s = i * lineHeight
  1398. if isinstance(o, PolyMarker):
  1399. # draw marker with legend
  1400. pnt = (trhc[0] + legendLHS + legendSymExt[0] / 2.,
  1401. trhc[1] + s + lineHeight / 2.)
  1402. o.draw(dc, self.printerScale, coord=np.array([pnt]))
  1403. elif isinstance(o, PolyLine):
  1404. # draw line with legend
  1405. pnt1 = (trhc[0] + legendLHS, trhc[1] + s + lineHeight / 2.)
  1406. pnt2 = (trhc[0] + legendLHS + legendSymExt[0],
  1407. trhc[1] + s + lineHeight / 2.)
  1408. o.draw(dc, self.printerScale, coord=np.array([pnt1, pnt2]))
  1409. else:
  1410. raise TypeError(
  1411. "object is neither PolyMarker or PolyLine instance")
  1412. # draw legend txt
  1413. pnt = (trhc[0] + legendLHS + legendSymExt[0] + 5 * self._pointSize[0],
  1414. trhc[1] + s + lineHeight / 2. - legendTextExt[1] / 2)
  1415. dc.DrawText(o.getLegend(), pnt[0], pnt[1])
  1416. dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
  1417. def _titleLablesWH(self, dc, graphics):
  1418. """Draws Title and labels and returns width and height for each"""
  1419. # TextExtents for Title and Axis Labels
  1420. dc.SetFont(self._getFont(self._fontSizeTitle))
  1421. if self._titleEnabled:
  1422. title = graphics.getTitle()
  1423. titleWH = dc.GetTextExtent(title)
  1424. else:
  1425. titleWH = (0, 0)
  1426. dc.SetFont(self._getFont(self._fontSizeAxis))
  1427. xLabel, yLabel = graphics.getXLabel(), graphics.getYLabel()
  1428. xLabelWH = dc.GetTextExtent(xLabel)
  1429. yLabelWH = dc.GetTextExtent(yLabel)
  1430. return titleWH, xLabelWH, yLabelWH
  1431. def _legendWH(self, dc, graphics):
  1432. """Returns the size in screen units for legend box"""
  1433. if self._legendEnabled != True:
  1434. legendBoxWH = symExt = txtExt = (0, 0)
  1435. else:
  1436. # find max symbol size
  1437. symExt = graphics.getSymExtent(self.printerScale)
  1438. # find max legend text extent
  1439. dc.SetFont(self._getFont(self._fontSizeLegend))
  1440. txtList = graphics.getLegendNames()
  1441. txtExt = dc.GetTextExtent(txtList[0])
  1442. for txt in graphics.getLegendNames()[1:]:
  1443. txtExt = np.maximum(txtExt, dc.GetTextExtent(txt))
  1444. maxW = symExt[0] + txtExt[0]
  1445. maxH = max(symExt[1], txtExt[1])
  1446. # padding .1 for lhs of legend box and space between lines
  1447. maxW = maxW * 1.1
  1448. maxH = maxH * 1.1 * len(txtList)
  1449. dc.SetFont(self._getFont(self._fontSizeAxis))
  1450. legendBoxWH = (maxW, maxH)
  1451. return (legendBoxWH, symExt, txtExt)
  1452. def _drawRubberBand(self, corner1, corner2):
  1453. """Draws/erases rect box from corner1 to corner2"""
  1454. ptx, pty, rectWidth, rectHeight = self._point2ClientCoord(
  1455. corner1, corner2)
  1456. # draw rectangle
  1457. dc = wx.ClientDC(self.canvas)
  1458. dc.SetPen(wx.Pen(wx.BLACK))
  1459. dc.SetBrush(wx.Brush(wx.WHITE, wx.BRUSHSTYLE_TRANSPARENT))
  1460. dc.SetLogicalFunction(wx.INVERT)
  1461. dc.DrawRectangle(ptx, pty, rectWidth, rectHeight)
  1462. dc.SetLogicalFunction(wx.COPY)
  1463. def _getFont(self, size):
  1464. """Take font size, adjusts if printing and returns wx.Font"""
  1465. s = size * self.printerScale * self._fontScale
  1466. of = self.GetFont()
  1467. # Linux speed up to get font from cache rather than X font server
  1468. key = (int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
  1469. font = self._fontCache.get(key, None)
  1470. if font:
  1471. return font # yeah! cache hit
  1472. else:
  1473. font = wx.Font(
  1474. int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
  1475. self._fontCache[key] = font
  1476. return font
  1477. def _point2ClientCoord(self, corner1, corner2):
  1478. """Converts user point coords to client screen int coords x,y,width,height"""
  1479. c1 = np.array(corner1)
  1480. c2 = np.array(corner2)
  1481. # convert to screen coords
  1482. pt1 = c1 * self._pointScale + self._pointShift
  1483. pt2 = c2 * self._pointScale + self._pointShift
  1484. # make height and width positive
  1485. pul = np.minimum(pt1, pt2) # Upper left corner
  1486. plr = np.maximum(pt1, pt2) # Lower right corner
  1487. rectWidth, rectHeight = plr - pul
  1488. ptx, pty = pul
  1489. return ptx, pty, rectWidth, rectHeight
  1490. def _axisInterval(self, spec, lower, upper):
  1491. """Returns sensible axis range for given spec"""
  1492. if spec == 'none' or spec == 'min' or isinstance(spec, (float, int)):
  1493. if lower == upper:
  1494. return lower - 0.5, upper + 0.5
  1495. else:
  1496. return lower, upper
  1497. elif spec == 'auto':
  1498. range = upper - lower
  1499. if range == 0.:
  1500. return lower - 0.5, upper + 0.5
  1501. log = np.log10(range)
  1502. power = np.floor(log)
  1503. fraction = log - power
  1504. if fraction <= 0.05:
  1505. power = power - 1
  1506. grid = 10. ** power
  1507. lower = lower - lower % grid
  1508. mod = upper % grid
  1509. if mod != 0:
  1510. upper = upper - mod + grid
  1511. return lower, upper
  1512. elif type(spec) == type(()):
  1513. lower, upper = spec
  1514. if lower <= upper:
  1515. return lower, upper
  1516. else:
  1517. return upper, lower
  1518. else:
  1519. raise ValueError(str(spec) + ': illegal axis specification')
  1520. def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
  1521. # increases thickness for printing only
  1522. penWidth = self.printerScale * self._pointSize[0]
  1523. dc.SetPen(wx.Pen(self._gridColour, penWidth))
  1524. # set length of tick marks--long ones make grid
  1525. if self._gridEnabled:
  1526. x, y, width, height = self._point2ClientCoord(p1, p2)
  1527. if self._gridEnabled == 'Horizontal':
  1528. yTickLength = (width / 2.0 + 1) * self._pointSize[1]
  1529. xTickLength = 3 * self.printerScale * self._pointSize[0]
  1530. elif self._gridEnabled == 'Vertical':
  1531. yTickLength = 3 * self.printerScale * self._pointSize[1]
  1532. xTickLength = (height / 2.0 + 1) * self._pointSize[0]
  1533. else:
  1534. yTickLength = (width / 2.0 + 1) * self._pointSize[1]
  1535. xTickLength = (height / 2.0 + 1) * self._pointSize[0]
  1536. else:
  1537. # lengthens lines for printing
  1538. yTickLength = 3 * self.printerScale * self._pointSize[1]
  1539. xTickLength = 3 * self.printerScale * self._pointSize[0]
  1540. if self._xSpec is not 'none':
  1541. lower, upper = p1[0], p2[0]
  1542. text = 1
  1543. # miny, maxy and tick lengths
  1544. for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]:
  1545. for x, label in xticks:
  1546. pt = scale * np.array([x, y]) + shift
  1547. # draws tick mark d units
  1548. dc.DrawLine(pt[0], pt[1], pt[0], pt[1] + d)
  1549. if text:
  1550. dc.DrawText(
  1551. label, pt[0], pt[1] + 2 * self._pointSize[1])
  1552. a1 = scale * np.array([lower, y]) + shift
  1553. a2 = scale * np.array([upper, y]) + shift
  1554. # draws upper and lower axis line
  1555. dc.DrawLine(a1[0], a1[1], a2[0], a2[1])
  1556. text = 0 # axis values not drawn on top side
  1557. if self._ySpec is not 'none':
  1558. lower, upper = p1[1], p2[1]
  1559. text = 1
  1560. h = dc.GetCharHeight()
  1561. for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
  1562. for y, label in yticks:
  1563. pt = scale * np.array([x, y]) + shift
  1564. dc.DrawLine(pt[0], pt[1], pt[0] - d, pt[1])
  1565. if text:
  1566. dc.DrawText(label, pt[0] - dc.GetTextExtent(label)[0] - 3 * self._pointSize[0],
  1567. pt[1] - 0.75 * h)
  1568. a1 = scale * np.array([x, lower]) + shift
  1569. a2 = scale * np.array([x, upper]) + shift
  1570. dc.DrawLine(a1[0], a1[1], a2[0], a2[1])
  1571. text = 0 # axis values not drawn on right side
  1572. if self._centerLinesEnabled:
  1573. if self._centerLinesEnabled in ('Horizontal', True):
  1574. y1 = scale[1] * p1[1] + shift[1]
  1575. y2 = scale[1] * p2[1] + shift[1]
  1576. y = (y1 - y2) / 2.0 + y2
  1577. dc.DrawLine(
  1578. scale[0] * p1[0] + shift[0], y, scale[0] * p2[0] + shift[0], y)
  1579. if self._centerLinesEnabled in ('Vertical', True):
  1580. x1 = scale[0] * p1[0] + shift[0]
  1581. x2 = scale[0] * p2[0] + shift[0]
  1582. x = (x1 - x2) / 2.0 + x2
  1583. dc.DrawLine(
  1584. x, scale[1] * p1[1] + shift[1], x, scale[1] * p2[1] + shift[1])
  1585. if self._diagonalsEnabled:
  1586. if self._diagonalsEnabled in ('Bottomleft-Topright', True):
  1587. dc.DrawLine(scale[0] * p1[0] + shift[0], scale[1] * p1[1] +
  1588. shift[1], scale[0] * p2[0] + shift[0], scale[1] * p2[1] + shift[1])
  1589. if self._diagonalsEnabled in ('Bottomright-Topleft', True):
  1590. dc.DrawLine(scale[0] * p1[0] + shift[0], scale[1] * p2[1] +
  1591. shift[1], scale[0] * p2[0] + shift[0], scale[1] * p1[1] + shift[1])
  1592. def _xticks(self, *args):
  1593. if self._logscale[0]:
  1594. return self._logticks(*args)
  1595. else:
  1596. attr = {'numticks': self._xSpec}
  1597. return self._ticks(*args, **attr)
  1598. def _yticks(self, *args):
  1599. if self._logscale[1]:
  1600. return self._logticks(*args)
  1601. else:
  1602. attr = {'numticks': self._ySpec}
  1603. return self._ticks(*args, **attr)
  1604. def _logticks(self, lower, upper):
  1605. #lower,upper = map(np.log10,[lower,upper])
  1606. # print('logticks',lower,upper)
  1607. ticks = []
  1608. mag = np.power(10, np.floor(lower))
  1609. if upper - lower > 6:
  1610. t = np.power(10, np.ceil(lower))
  1611. base = np.power(10, np.floor((upper - lower) / 6))
  1612. def inc(t):
  1613. return t * base - t
  1614. else:
  1615. t = np.ceil(np.power(10, lower) / mag) * mag
  1616. def inc(t):
  1617. return 10 ** int(np.floor(np.log10(t) + 1e-16))
  1618. majortick = int(np.log10(mag))
  1619. while t <= pow(10, upper):
  1620. if majortick != int(np.floor(np.log10(t) + 1e-16)):
  1621. majortick = int(np.floor(np.log10(t) + 1e-16))
  1622. ticklabel = '1e%d' % majortick
  1623. else:
  1624. if upper - lower < 2:
  1625. minortick = int(t / pow(10, majortick) + .5)
  1626. ticklabel = '%de%d' % (minortick, majortick)
  1627. else:
  1628. ticklabel = ''
  1629. ticks.append((np.log10(t), ticklabel))
  1630. t += inc(t)
  1631. if len(ticks) == 0:
  1632. ticks = [(0, '')]
  1633. return ticks
  1634. def _ticks(self, lower, upper, numticks=None):
  1635. if isinstance(numticks, (float, int)):
  1636. ideal = (upper - lower) / float(numticks)
  1637. else:
  1638. ideal = (upper - lower) / 7.
  1639. log = np.log10(ideal)
  1640. power = np.floor(log)
  1641. if isinstance(numticks, (float, int)):
  1642. grid = ideal
  1643. else:
  1644. fraction = log - power
  1645. factor = 1.
  1646. error = fraction
  1647. for f, lf in self._multiples:
  1648. e = np.fabs(fraction - lf)
  1649. if e < error:
  1650. error = e
  1651. factor = f
  1652. grid = factor * 10. ** power
  1653. if self._useScientificNotation and (power > 4 or power < -4):
  1654. format = '%+7.1e'
  1655. elif power >= 0:
  1656. digits = max(1, int(power))
  1657. format = '%' + repr(digits) + '.0f'
  1658. else:
  1659. digits = -int(power)
  1660. format = '%' + repr(digits + 2) + '.' + repr(digits) + 'f'
  1661. ticks = []
  1662. t = -grid * np.floor(-lower / grid)
  1663. while t <= upper:
  1664. if t == -0:
  1665. t = 0
  1666. ticks.append((t, format % (t,)))
  1667. t = t + grid
  1668. return ticks
  1669. _multiples = [(2., np.log10(2.)), (5., np.log10(5.))]
  1670. def _adjustScrollbars(self):
  1671. if self._sb_ignore:
  1672. self._sb_ignore = False
  1673. return
  1674. if not self.GetShowScrollbars():
  1675. return
  1676. self._adjustingSB = True
  1677. needScrollbars = False
  1678. # horizontal scrollbar
  1679. r_current = self._getXCurrentRange()
  1680. r_max = list(self._getXMaxRange())
  1681. sbfullrange = float(self.sb_hor.GetRange())
  1682. r_max[0] = min(r_max[0], r_current[0])
  1683. r_max[1] = max(r_max[1], r_current[1])
  1684. self._sb_xfullrange = r_max
  1685. unit = (r_max[1] - r_max[0]) / float(self.sb_hor.GetRange())
  1686. pos = int((r_current[0] - r_max[0]) / unit)
  1687. if pos >= 0:
  1688. pagesize = int((r_current[1] - r_current[0]) / unit)
  1689. self.sb_hor.SetScrollbar(pos, pagesize, sbfullrange, pagesize)
  1690. self._sb_xunit = unit
  1691. needScrollbars = needScrollbars or (pagesize != sbfullrange)
  1692. else:
  1693. self.sb_hor.SetScrollbar(0, 1000, 1000, 1000)
  1694. # vertical scrollbar
  1695. r_current = self._getYCurrentRange()
  1696. r_max = list(self._getYMaxRange())
  1697. sbfullrange = float(self.sb_vert.GetRange())
  1698. r_max[0] = min(r_max[0], r_current[0])
  1699. r_max[1] = max(r_max[1], r_current[1])
  1700. self._sb_yfullrange = r_max
  1701. unit = (r_max[1] - r_max[0]) / sbfullrange
  1702. pos = int((r_current[0] - r_max[0]) / unit)
  1703. if pos >= 0:
  1704. pagesize = int((r_current[1] - r_current[0]) / unit)
  1705. pos = (sbfullrange - 1 - pos - pagesize)
  1706. self.sb_vert.SetScrollbar(pos, pagesize, sbfullrange, pagesize)
  1707. self._sb_yunit = unit
  1708. needScrollbars = needScrollbars or (pagesize != sbfullrange)
  1709. else:
  1710. self.sb_vert.SetScrollbar(0, 1000, 1000, 1000)
  1711. self.SetShowScrollbars(needScrollbars)
  1712. self._adjustingSB = False
  1713. #-------------------------------------------------------------------------
  1714. # Used to layout the printer page
  1715. class PlotPrintout(wx.Printout):
  1716. """Controls how the plot is made in printing and previewing"""
  1717. # Do not change method names in this class,
  1718. # we have to override wx.Printout methods here!
  1719. def __init__(self, graph):
  1720. """graph is instance of plotCanvas to be printed or previewed"""
  1721. wx.Printout.__init__(self)
  1722. self.graph = graph
  1723. def HasPage(self, page):
  1724. if page == 1:
  1725. return True
  1726. else:
  1727. return False
  1728. def GetPageInfo(self):
  1729. return (1, 1, 1, 1) # disable page numbers
  1730. def OnPrintPage(self, page):
  1731. dc = self.GetDC() # allows using floats for certain functions
  1732. ## print("PPI Printer",self.GetPPIPrinter())
  1733. ## print("PPI Screen", self.GetPPIScreen())
  1734. ## print("DC GetSize", dc.GetSize())
  1735. ## print("GetPageSizePixels", self.GetPageSizePixels())
  1736. # Note PPIScreen does not give the correct number
  1737. # Calulate everything for printer and then scale for preview
  1738. PPIPrinter = self.GetPPIPrinter() # printer dots/inch (w,h)
  1739. # PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
  1740. dcSize = dc.GetSize() # DC size
  1741. if self.graph._antiAliasingEnabled and not isinstance(dc, wx.GCDC):
  1742. try:
  1743. dc = wx.GCDC(dc)
  1744. except Exception:
  1745. pass
  1746. else:
  1747. if self.graph._hiResEnabled:
  1748. # high precision - each logical unit is 1/20 of a point
  1749. dc.SetMapMode(wx.MM_TWIPS)
  1750. pageSize = self.GetPageSizePixels() # page size in terms of pixcels
  1751. clientDcSize = self.graph.GetClientSize()
  1752. # find what the margins are (mm)
  1753. margLeftSize, margTopSize = self.graph.pageSetupData.GetMarginTopLeft()
  1754. margRightSize, margBottomSize = self.graph.pageSetupData.GetMarginBottomRight()
  1755. # calculate offset and scale for dc
  1756. pixLeft = margLeftSize * PPIPrinter[0] / 25.4 # mm*(dots/in)/(mm/in)
  1757. pixRight = margRightSize * PPIPrinter[0] / 25.4
  1758. pixTop = margTopSize * PPIPrinter[1] / 25.4
  1759. pixBottom = margBottomSize * PPIPrinter[1] / 25.4
  1760. plotAreaW = pageSize[0] - (pixLeft + pixRight)
  1761. plotAreaH = pageSize[1] - (pixTop + pixBottom)
  1762. # ratio offset and scale to screen size if preview
  1763. if self.IsPreview():
  1764. ratioW = float(dcSize[0]) / pageSize[0]
  1765. ratioH = float(dcSize[1]) / pageSize[1]
  1766. pixLeft *= ratioW
  1767. pixTop *= ratioH
  1768. plotAreaW *= ratioW
  1769. plotAreaH *= ratioH
  1770. # rescale plot to page or preview plot area
  1771. self.graph._setSize(plotAreaW, plotAreaH)
  1772. # Set offset and scale
  1773. dc.SetDeviceOrigin(pixLeft, pixTop)
  1774. # Thicken up pens and increase marker size for printing
  1775. ratioW = float(plotAreaW) / clientDcSize[0]
  1776. ratioH = float(plotAreaH) / clientDcSize[1]
  1777. aveScale = (ratioW + ratioH) / 2
  1778. if self.graph._antiAliasingEnabled and not self.IsPreview():
  1779. scale = dc.GetUserScale()
  1780. dc.SetUserScale(
  1781. scale[0] / self.graph._pointSize[0], scale[1] / self.graph._pointSize[1])
  1782. self.graph._setPrinterScale(aveScale) # tickens up pens for printing
  1783. self.graph._printDraw(dc)
  1784. # rescale back to original
  1785. self.graph._setSize()
  1786. self.graph._setPrinterScale(1)
  1787. self.graph.Redraw() # to get point label scale and shift correct
  1788. return True
  1789. #----------------------------------------------------------------------
  1790. from wx.lib.embeddedimage import PyEmbeddedImage
  1791. MagPlus = PyEmbeddedImage(
  1792. "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAOFJ"
  1793. "REFUeJy1VdEOxCAIo27//8XbuKfuPASGZ0Zisoi2FJABbZM3bY8c13lo5GvbjioBPAUEB0Yc"
  1794. "VZ0iGRRc56Ee8DcikEgrJD8EFpzRegQASiRtBtzuA0hrdRPYQxaEKyJPG6IHyiK3xnNZvUSS"
  1795. "NvUuzgYh0il4y14nCFPk5XgmNbRbQbVotGo9msj47G3UXJ7fuz8Q8FAGEu0/PbZh2D3NoshU"
  1796. "1VUydBGVZKMimlGeErdNGUmf/x7YpjMjcf8HVYvS2adr6aFVlCy/5Ijk9q8SeCR9isJR8SeJ"
  1797. "8pv7S0Wu2Acr0qdj3w7DRAAAAABJRU5ErkJggg==")
  1798. GrabHand = PyEmbeddedImage(
  1799. "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAARFJ"
  1800. "REFUeJy1VdESgzAIS2j//4s3s5fRQ6Rad5M7H0oxCZhWSpK1TjwUBCBJAIBItL1fijlfe1yJ"
  1801. "8noCGC9KgrXO7f0SyZEDAF/H2opsAHv9V/548nplT5Jo7YAFQKQ1RMWzmHUS96suqdBrHkuV"
  1802. "uxpdJjCS8CfGXWdJ2glzcquKSR5c46QOtCpgNyIHj6oieAXg3282QvMX45hy8a8H0VonJZUO"
  1803. "clesjOPg/dhBTq64o1Kacz4Ri2x5RKsf8+wcWQaJJL+A+xRcZHeQeBKjK+5EFiVJ4xy4x2Mn"
  1804. "1Vk4U5/DWmfPieiqbye7a3tV/cCsWKu76K76KUFFchVnhigJ/hmktelm/m3e3b8k+Ec8PqLH"
  1805. "CT4JRfyK9o1xYwAAAABJRU5ErkJggg==")
  1806. Hand = PyEmbeddedImage(
  1807. "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAARBJ"
  1808. "REFUeJytluECwiAIhDn1/Z942/UnjCGoq+6XNeWDC1xAqbKr6zyo61Ibds60J8GBT0yS3IEM"
  1809. "ABuIpJTa4IOLiAAQksuKyixLH1ShHgTgZl8KiALxOsODPoEMkgJ25Su6zoO3ZrjRnI96OLIq"
  1810. "k7dsqOCboDa4XV/nwQEQVeFtmMnvbSJja+oagKBUaLn9hzd7VipRa9ostIv0O1uhzzaqNJxk"
  1811. "hViwDVxqg51kksMg9r2rDDIFwHCap130FBhdMzeAfWg//6Ki5WWQrHSv6EIUeVs0g3wT3J7r"
  1812. "FmWQp/JJDXeRh2TXcJa91zAH2uN2mvXFsrIrsjS8rnftWmWfAiLIStuD9m9h9belvzgS/1fP"
  1813. "X7075IwDENteAAAAAElFTkSuQmCC")
  1814. #---------------------------------------------------------------------------
  1815. # if running standalone...
  1816. #
  1817. # ...a sample implementation using the above
  1818. #
  1819. def _draw1Objects():
  1820. # 100 points sin function, plotted as green circles
  1821. data1 = 2. * np.pi * np.arange(200) / 200.
  1822. data1.shape = (100, 2)
  1823. data1[:, 1] = np.sin(data1[:, 0])
  1824. markers1 = PolyMarker(
  1825. data1, legend='Green Markers', colour='green', marker='circle', size=1)
  1826. # 50 points cos function, plotted as red line
  1827. data1 = 2. * np.pi * np.arange(100) / 100.
  1828. data1.shape = (50, 2)
  1829. data1[:, 1] = np.cos(data1[:, 0])
  1830. lines = PolySpline(data1, legend='Red Line', colour='red')
  1831. # A few more points...
  1832. pi = np.pi
  1833. markers2 = PolyMarker([(0., 0.), (pi / 4., 1.), (pi / 2, 0.),
  1834. (3. * pi / 4., -1)], legend='Cross Legend', colour='blue',
  1835. marker='cross')
  1836. return PlotGraphics([markers1, lines, markers2], "Graph Title", "X Axis", "Y Axis")
  1837. def _draw2Objects():
  1838. # 100 points sin function, plotted as green dots
  1839. data1 = 2. * np.pi * np.arange(200) / 200.
  1840. data1.shape = (100, 2)
  1841. data1[:, 1] = np.sin(data1[:, 0])
  1842. line1 = PolySpline(
  1843. data1, legend='Green Line', colour='green', width=6, style=wx.PENSTYLE_DOT)
  1844. # 50 points cos function, plotted as red dot-dash
  1845. data1 = 2. * np.pi * np.arange(100) / 100.
  1846. data1.shape = (50, 2)
  1847. data1[:, 1] = np.cos(data1[:, 0])
  1848. line2 = PolySpline(
  1849. data1, legend='Red Line', colour='red', width=3, style=wx.PENSTYLE_DOT_DASH)
  1850. # A few more points...
  1851. pi = np.pi
  1852. markers1 = PolyMarker([(0., 0.), (pi / 4., 1.), (pi / 2, 0.),
  1853. (3. * pi / 4., -1)], legend='Cross Hatch Square', colour='blue', width=3, size=6,
  1854. fillcolour='red', fillstyle=wx.CROSSDIAG_HATCH,
  1855. marker='square')
  1856. return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles")
  1857. def _draw3Objects():
  1858. markerList = ['circle', 'dot', 'square', 'triangle', 'triangle_down',
  1859. 'cross', 'plus', 'circle']
  1860. m = []
  1861. for i in range(len(markerList)):
  1862. m.append(PolyMarker([(2 * i + .5, i + .5)], legend=markerList[i], colour='blue',
  1863. marker=markerList[i]))
  1864. return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis")
  1865. def _draw4Objects():
  1866. # 25,000 point line
  1867. data1 = np.arange(5e5, 1e6, 10)
  1868. data1.shape = (25000, 2)
  1869. line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
  1870. # A few more points...
  1871. markers2 = PolyMarker(data1, legend='Square', colour='blue',
  1872. marker='square')
  1873. return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "")
  1874. def _draw5Objects():
  1875. # Empty graph with axis defined but no points/lines
  1876. points = []
  1877. line1 = PolyLine(points, legend='Wide Line', colour='green', width=5)
  1878. return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y")
  1879. def _draw6Objects():
  1880. # Bar graph
  1881. points1 = [(1, 0), (1, 10)]
  1882. line1 = PolyLine(points1, colour='green', legend='Feb.', width=10)
  1883. points1g = [(2, 0), (2, 4)]
  1884. line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10)
  1885. points1b = [(3, 0), (3, 6)]
  1886. line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10)
  1887. points2 = [(4, 0), (4, 12)]
  1888. line2 = PolyLine(points2, colour='Yellow', legend='May', width=10)
  1889. points2g = [(5, 0), (5, 8)]
  1890. line2g = PolyLine(points2g, colour='orange', legend='June', width=10)
  1891. points2b = [(6, 0), (6, 4)]
  1892. line2b = PolyLine(points2b, colour='brown', legend='July', width=10)
  1893. return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
  1894. "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
  1895. def _draw7Objects():
  1896. # Empty graph with axis defined but no points/lines
  1897. x = np.arange(1, 1000, 1)
  1898. y1 = 4.5 * x ** 2
  1899. y2 = 2.2 * x ** 3
  1900. points1 = np.transpose([x, y1])
  1901. points2 = np.transpose([x, y2])
  1902. line1 = PolyLine(points1, legend='quadratic', colour='blue', width=1)
  1903. line2 = PolyLine(points2, legend='cubic', colour='red', width=1)
  1904. return PlotGraphics([line1, line2], "double log plot", "Value X", "Value Y")
  1905. class TestFrame(wx.Frame):
  1906. def __init__(self, parent, id, title):
  1907. wx.Frame.__init__(self, parent, id, title,
  1908. wx.DefaultPosition, (600, 400))
  1909. # Now Create the menu bar and items
  1910. self.mainmenu = wx.MenuBar()
  1911. menu = wx.Menu()
  1912. menu.Append(200, 'Page Setup...', 'Setup the printer page')
  1913. self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200)
  1914. menu.Append(201, 'Print Preview...', 'Show the current plot on page')
  1915. self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201)
  1916. menu.Append(202, 'Print...', 'Print the current plot')
  1917. self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202)
  1918. menu.Append(203, 'Save Plot...', 'Save current plot')
  1919. self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203)
  1920. menu.Append(205, 'E&xit', 'Enough of this already!')
  1921. self.Bind(wx.EVT_MENU, self.OnFileExit, id=205)
  1922. self.mainmenu.Append(menu, '&File')
  1923. menu = wx.Menu()
  1924. menu.Append(206, 'Draw1', 'Draw plots1')
  1925. self.Bind(wx.EVT_MENU, self.OnPlotDraw1, id=206)
  1926. menu.Append(207, 'Draw2', 'Draw plots2')
  1927. self.Bind(wx.EVT_MENU, self.OnPlotDraw2, id=207)
  1928. menu.Append(208, 'Draw3', 'Draw plots3')
  1929. self.Bind(wx.EVT_MENU, self.OnPlotDraw3, id=208)
  1930. menu.Append(209, 'Draw4', 'Draw plots4')
  1931. self.Bind(wx.EVT_MENU, self.OnPlotDraw4, id=209)
  1932. menu.Append(210, 'Draw5', 'Draw plots5')
  1933. self.Bind(wx.EVT_MENU, self.OnPlotDraw5, id=210)
  1934. menu.Append(260, 'Draw6', 'Draw plots6')
  1935. self.Bind(wx.EVT_MENU, self.OnPlotDraw6, id=260)
  1936. menu.Append(261, 'Draw7', 'Draw plots7')
  1937. self.Bind(wx.EVT_MENU, self.OnPlotDraw7, id=261)
  1938. menu.Append(211, '&Redraw', 'Redraw plots')
  1939. self.Bind(wx.EVT_MENU, self.OnPlotRedraw, id=211)
  1940. menu.Append(212, '&Clear', 'Clear canvas')
  1941. self.Bind(wx.EVT_MENU, self.OnPlotClear, id=212)
  1942. menu.Append(213, '&Scale', 'Scale canvas')
  1943. self.Bind(wx.EVT_MENU, self.OnPlotScale, id=213)
  1944. menu.Append(
  1945. 214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK)
  1946. self.Bind(wx.EVT_MENU, self.OnEnableZoom, id=214)
  1947. menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
  1948. self.Bind(wx.EVT_MENU, self.OnEnableGrid, id=215)
  1949. menu.Append(
  1950. 217, 'Enable &Drag', 'Activates dragging mode', kind=wx.ITEM_CHECK)
  1951. self.Bind(wx.EVT_MENU, self.OnEnableDrag, id=217)
  1952. menu.Append(
  1953. 220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
  1954. self.Bind(wx.EVT_MENU, self.OnEnableLegend, id=220)
  1955. menu.Append(
  1956. 222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK)
  1957. self.Bind(wx.EVT_MENU, self.OnEnablePointLabel, id=222)
  1958. menu.Append(
  1959. 223, 'Enable &Anti-Aliasing', 'Smooth output', kind=wx.ITEM_CHECK)
  1960. self.Bind(wx.EVT_MENU, self.OnEnableAntiAliasing, id=223)
  1961. menu.Append(224, 'Enable &High-Resolution AA',
  1962. 'Draw in higher resolution', kind=wx.ITEM_CHECK)
  1963. self.Bind(wx.EVT_MENU, self.OnEnableHiRes, id=224)
  1964. menu.Append(
  1965. 226, 'Enable Center Lines', 'Draw center lines', kind=wx.ITEM_CHECK)
  1966. self.Bind(wx.EVT_MENU, self.OnEnableCenterLines, id=226)
  1967. menu.Append(
  1968. 227, 'Enable Diagonal Lines', 'Draw diagonal lines', kind=wx.ITEM_CHECK)
  1969. self.Bind(wx.EVT_MENU, self.OnEnableDiagonals, id=227)
  1970. menu.Append(
  1971. 231, 'Set Gray Background', 'Change background colour to gray')
  1972. self.Bind(wx.EVT_MENU, self.OnBackgroundGray, id=231)
  1973. menu.Append(
  1974. 232, 'Set &White Background', 'Change background colour to white')
  1975. self.Bind(wx.EVT_MENU, self.OnBackgroundWhite, id=232)
  1976. menu.Append(
  1977. 233, 'Set Red Label Text', 'Change label text colour to red')
  1978. self.Bind(wx.EVT_MENU, self.OnForegroundRed, id=233)
  1979. menu.Append(
  1980. 234, 'Set &Black Label Text', 'Change label text colour to black')
  1981. self.Bind(wx.EVT_MENU, self.OnForegroundBlack, id=234)
  1982. menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
  1983. self.Bind(wx.EVT_MENU, self.OnScrUp, id=225)
  1984. menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
  1985. self.Bind(wx.EVT_MENU, self.OnScrRt, id=230)
  1986. menu.Append(235, '&Plot Reset', 'Reset to original plot')
  1987. self.Bind(wx.EVT_MENU, self.OnReset, id=235)
  1988. self.mainmenu.Append(menu, '&Plot')
  1989. menu = wx.Menu()
  1990. menu.Append(300, '&About', 'About this thing...')
  1991. self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300)
  1992. self.mainmenu.Append(menu, '&Help')
  1993. self.SetMenuBar(self.mainmenu)
  1994. # A status bar to tell people what's happening
  1995. self.CreateStatusBar(1)
  1996. self.client = PlotCanvas(self)
  1997. # define the function for drawing pointLabels
  1998. self.client.SetPointLabelFunc(self.DrawPointLabel)
  1999. # Create mouse event for showing cursor coords in status bar
  2000. self.client.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
  2001. # Show closest point when enabled
  2002. self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
  2003. self.Show(True)
  2004. def DrawPointLabel(self, dc, mDataDict):
  2005. """This is the fuction that defines how the pointLabels are plotted
  2006. dc - DC that will be passed
  2007. mDataDict - Dictionary of data that you want to use for the pointLabel
  2008. As an example I have decided I want a box at the curve point
  2009. with some text information about the curve plotted below.
  2010. Any wxDC method can be used.
  2011. """
  2012. # ----------
  2013. dc.SetPen(wx.Pen(wx.BLACK))
  2014. dc.SetBrush(wx.Brush(wx.BLACK, wx.BRUSHSTYLE_SOLID))
  2015. sx, sy = mDataDict["scaledXY"] # scaled x,y of closest point
  2016. # 10by10 square centered on point
  2017. dc.DrawRectangle(sx - 5, sy - 5, 10, 10)
  2018. px, py = mDataDict["pointXY"]
  2019. cNum = mDataDict["curveNum"]
  2020. pntIn = mDataDict["pIndex"]
  2021. legend = mDataDict["legend"]
  2022. # make a string to display
  2023. s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" % (
  2024. cNum, legend, px, py, pntIn)
  2025. dc.DrawText(s, sx, sy + 1)
  2026. # -----------
  2027. def OnMouseLeftDown(self, event):
  2028. s = "Left Mouse Down at Point: (%.4f, %.4f)" % self.client._getXY(
  2029. event)
  2030. self.SetStatusText(s)
  2031. event.Skip() # allows plotCanvas OnMouseLeftDown to be called
  2032. def OnMotion(self, event):
  2033. # show closest point (when enbled)
  2034. if self.client.GetEnablePointLabel() == True:
  2035. # make up dict with info for the pointLabel
  2036. # I've decided to mark the closest point on the closest curve
  2037. dlst = self.client.GetClosestPoint(
  2038. self.client._getXY(event), pointScaled=True)
  2039. if dlst != []: # returns [] if none
  2040. curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
  2041. # make up dictionary to pass to my user function (see
  2042. # DrawPointLabel)
  2043. mDataDict = {"curveNum": curveNum, "legend": legend, "pIndex": pIndex,
  2044. "pointXY": pointXY, "scaledXY": scaledXY}
  2045. # pass dict to update the pointLabel
  2046. self.client.UpdatePointLabel(mDataDict)
  2047. event.Skip() # go to next handler
  2048. def OnFilePageSetup(self, event):
  2049. self.client.PageSetup()
  2050. def OnFilePrintPreview(self, event):
  2051. self.client.PrintPreview()
  2052. def OnFilePrint(self, event):
  2053. self.client.Printout()
  2054. def OnSaveFile(self, event):
  2055. self.client.SaveFile()
  2056. def OnFileExit(self, event):
  2057. self.Close()
  2058. def OnPlotDraw1(self, event):
  2059. self.resetDefaults()
  2060. self.client.Draw(_draw1Objects())
  2061. def OnPlotDraw2(self, event):
  2062. self.resetDefaults()
  2063. self.client.Draw(_draw2Objects())
  2064. def OnPlotDraw3(self, event):
  2065. self.resetDefaults()
  2066. self.client.SetFont(
  2067. wx.Font(10, wx.FONTFAMILY_SCRIPT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
  2068. self.client.SetFontSizeAxis(20)
  2069. self.client.SetFontSizeLegend(12)
  2070. self.client.SetXSpec('min')
  2071. self.client.SetYSpec('none')
  2072. self.client.Draw(_draw3Objects())
  2073. def OnPlotDraw4(self, event):
  2074. self.resetDefaults()
  2075. drawObj = _draw4Objects()
  2076. self.client.Draw(drawObj)
  2077. # profile
  2078. ## start = _time.clock()
  2079. # for x in range(10):
  2080. # self.client.Draw(drawObj)
  2081. ## print("10 plots of Draw4 took: %f sec."%(_time.clock() - start))
  2082. # profile end
  2083. def OnPlotDraw5(self, event):
  2084. # Empty plot with just axes
  2085. self.resetDefaults()
  2086. drawObj = _draw5Objects()
  2087. # make the axis X= (0,5), Y=(0,10)
  2088. # (default with None is X= (-1,1), Y= (-1,1))
  2089. self.client.Draw(drawObj, xAxis=(0, 5), yAxis= (0, 10))
  2090. def OnPlotDraw6(self, event):
  2091. # Bar Graph Example
  2092. self.resetDefaults()
  2093. # self.client.SetEnableLegend(True) #turn on Legend
  2094. # self.client.SetEnableGrid(True) #turn on Grid
  2095. self.client.SetXSpec('none') # turns off x-axis scale
  2096. self.client.SetYSpec('auto')
  2097. self.client.Draw(_draw6Objects(), xAxis=(0, 7))
  2098. def OnPlotDraw7(self, event):
  2099. # log scale example
  2100. self.resetDefaults()
  2101. self.client.setLogScale((True, True))
  2102. self.client.Draw(_draw7Objects())
  2103. def OnPlotRedraw(self, event):
  2104. self.client.Redraw()
  2105. def OnPlotClear(self, event):
  2106. self.client.Clear()
  2107. def OnPlotScale(self, event):
  2108. if self.client.last_draw != None:
  2109. graphics, xAxis, yAxis = self.client.last_draw
  2110. self.client.Draw(graphics, (1, 3.05), (0, 1))
  2111. def OnEnableZoom(self, event):
  2112. self.client.SetEnableZoom(event.IsChecked())
  2113. self.mainmenu.Check(217, not event.IsChecked())
  2114. def OnEnableGrid(self, event):
  2115. self.client.SetEnableGrid(event.IsChecked())
  2116. def OnEnableDrag(self, event):
  2117. self.client.SetEnableDrag(event.IsChecked())
  2118. self.mainmenu.Check(214, not event.IsChecked())
  2119. def OnEnableLegend(self, event):
  2120. self.client.SetEnableLegend(event.IsChecked())
  2121. def OnEnablePointLabel(self, event):
  2122. self.client.SetEnablePointLabel(event.IsChecked())
  2123. def OnEnableAntiAliasing(self, event):
  2124. self.client.SetEnableAntiAliasing(event.IsChecked())
  2125. def OnEnableHiRes(self, event):
  2126. self.client.SetEnableHiRes(event.IsChecked())
  2127. def OnEnableCenterLines(self, event):
  2128. self.client.SetEnableCenterLines(event.IsChecked())
  2129. def OnEnableDiagonals(self, event):
  2130. self.client.SetEnableDiagonals(event.IsChecked())
  2131. def OnBackgroundGray(self, event):
  2132. self.client.SetBackgroundColour("#CCCCCC")
  2133. self.client.Redraw()
  2134. def OnBackgroundWhite(self, event):
  2135. self.client.SetBackgroundColour("white")
  2136. self.client.Redraw()
  2137. def OnForegroundRed(self, event):
  2138. self.client.SetForegroundColour("red")
  2139. self.client.Redraw()
  2140. def OnForegroundBlack(self, event):
  2141. self.client.SetForegroundColour("black")
  2142. self.client.Redraw()
  2143. def OnScrUp(self, event):
  2144. self.client.ScrollUp(1)
  2145. def OnScrRt(self, event):
  2146. self.client.ScrollRight(2)
  2147. def OnReset(self, event):
  2148. self.client.Reset()
  2149. def OnHelpAbout(self, event):
  2150. from wx.lib.dialogs import ScrolledMessageDialog
  2151. about = ScrolledMessageDialog(self, __doc__, "About...")
  2152. about.ShowModal()
  2153. def resetDefaults(self):
  2154. """Just to reset the fonts back to the PlotCanvas defaults"""
  2155. self.client.SetFont(
  2156. wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
  2157. self.client.SetFontSizeAxis(10)
  2158. self.client.SetFontSizeLegend(7)
  2159. self.client.setLogScale((False, False))
  2160. self.client.SetXSpec('auto')
  2161. self.client.SetYSpec('auto')
  2162. def __test():
  2163. class MyApp(wx.App):
  2164. def OnInit(self):
  2165. wx.InitAllImageHandlers()
  2166. frame = TestFrame(None, -1, "PlotCanvas")
  2167. # frame.Show(True)
  2168. self.SetTopWindow(frame)
  2169. return True
  2170. app = MyApp(0)
  2171. app.MainLoop()
  2172. if __name__ == '__main__':
  2173. __test()