wxlibplot.py 96 KB

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