wxlibplot.py 96 KB

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