PageRenderTime 36ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/v0_98_4/lib/matplotlib/backends/backend_macosx.py

https://github.com/ericliang/matplotlib
Python | 415 lines | 353 code | 36 blank | 26 comment | 14 complexity | 2ad72862c1ee3ef5202369642c7f8ec4 MD5 | raw file
  1. from __future__ import division
  2. import os
  3. import numpy
  4. from matplotlib._pylab_helpers import Gcf
  5. from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
  6. FigureManagerBase, FigureCanvasBase, NavigationToolbar2
  7. from matplotlib.cbook import maxdict
  8. from matplotlib.figure import Figure
  9. from matplotlib.path import Path
  10. from matplotlib.mathtext import MathTextParser
  11. from matplotlib.widgets import SubplotTool
  12. import matplotlib
  13. from matplotlib.backends import _macosx
  14. def show():
  15. """Show all the figures and enter the Cocoa mainloop.
  16. This function will not return until all windows are closed or
  17. the interpreter exits."""
  18. # Having a Python-level function "show" wrapping the built-in
  19. # function "show" in the _macosx extension module allows us to
  20. # to add attributes to "show". This is something ipython does.
  21. _macosx.show()
  22. class RendererMac(RendererBase):
  23. """
  24. The renderer handles drawing/rendering operations. Most of the renderer's
  25. methods forwards the command to the renderer's graphics context. The
  26. renderer does not wrap a C object and is written in pure Python.
  27. """
  28. texd = maxdict(50) # a cache of tex image rasters
  29. def __init__(self, dpi, width, height):
  30. RendererBase.__init__(self)
  31. self.dpi = dpi
  32. self.width = width
  33. self.height = height
  34. self.gc = GraphicsContextMac()
  35. self.mathtext_parser = MathTextParser('MacOSX')
  36. def set_width_height (self, width, height):
  37. self.width, self.height = width, height
  38. def draw_path(self, gc, path, transform, rgbFace=None):
  39. path = transform.transform_path(path)
  40. for points, code in path.iter_segments():
  41. if code == Path.MOVETO:
  42. gc.moveto(points)
  43. elif code == Path.LINETO:
  44. gc.lineto(points)
  45. elif code == Path.CURVE3:
  46. gc.curve3(points)
  47. elif code == Path.CURVE4:
  48. gc.curve4(points)
  49. elif code == Path.CLOSEPOLY:
  50. gc.closepoly()
  51. if rgbFace is not None:
  52. rgbFace = tuple(rgbFace)
  53. gc.stroke(rgbFace)
  54. def new_gc(self):
  55. self.gc.reset()
  56. return self.gc
  57. def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None):
  58. self.gc.set_clip_rectangle(bbox)
  59. im.flipud_out()
  60. nrows, ncols, data = im.as_rgba_str()
  61. self.gc.draw_image(x, y, nrows, ncols, data)
  62. im.flipud_out()
  63. def draw_tex(self, gc, x, y, s, prop, angle):
  64. # todo, handle props, angle, origins
  65. size = prop.get_size_in_points()
  66. texmanager = self.get_texmanager()
  67. key = s, size, self.dpi, angle, texmanager.get_font_config()
  68. im = self.texd.get(key) # Not sure what this does; just copied from backend_agg.py
  69. if im is None:
  70. Z = texmanager.get_grey(s, size, self.dpi)
  71. Z = numpy.array(255.0 - Z * 255.0, numpy.uint8)
  72. gc.draw_mathtext(x, y, angle, Z)
  73. def _draw_mathtext(self, gc, x, y, s, prop, angle):
  74. size = prop.get_size_in_points()
  75. ox, oy, width, height, descent, image, used_characters = \
  76. self.mathtext_parser.parse(s, self.dpi, prop)
  77. gc.draw_mathtext(x, y, angle, 255 - image.as_array())
  78. def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
  79. if ismath:
  80. self._draw_mathtext(gc, x, y, s, prop, angle)
  81. else:
  82. family = prop.get_family()
  83. size = prop.get_size_in_points()
  84. weight = prop.get_weight()
  85. style = prop.get_style()
  86. gc.draw_text(x, y, unicode(s), family, size, weight, style, angle)
  87. def get_text_width_height_descent(self, s, prop, ismath):
  88. if ismath=='TeX':
  89. # TODO: handle props
  90. size = prop.get_size_in_points()
  91. texmanager = self.get_texmanager()
  92. Z = texmanager.get_grey(s, size, self.dpi)
  93. m,n = Z.shape
  94. # TODO: handle descent; This is based on backend_agg.py
  95. return n, m, 0
  96. if ismath:
  97. ox, oy, width, height, descent, fonts, used_characters = \
  98. self.mathtext_parser.parse(s, self.dpi, prop)
  99. return width, height, descent
  100. family = prop.get_family()
  101. size = prop.get_size_in_points()
  102. weight = prop.get_weight()
  103. style = prop.get_style()
  104. return self.gc.get_text_width_height_descent(unicode(s), family, size, weight, style)
  105. def flipy(self):
  106. return False
  107. def points_to_pixels(self, points):
  108. return points/72.0 * self.dpi
  109. def option_image_nocomposite(self):
  110. return True
  111. class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase):
  112. """
  113. The GraphicsContext wraps a Quartz graphics context. All methods
  114. are implemented at the C-level in macosx.GraphicsContext. These
  115. methods set drawing properties such as the line style, fill color,
  116. etc. The actual drawing is done by the Renderer, which draws into
  117. the GraphicsContext.
  118. """
  119. def __init__(self):
  120. GraphicsContextBase.__init__(self)
  121. _macosx.GraphicsContext.__init__(self)
  122. def set_clip_rectangle(self, box):
  123. GraphicsContextBase.set_clip_rectangle(self, box)
  124. if not box: return
  125. _macosx.GraphicsContext.set_clip_rectangle(self, box.bounds)
  126. def set_clip_path(self, path):
  127. GraphicsContextBase.set_clip_path(self, path)
  128. if not path: return
  129. path = path.get_fully_transformed_path()
  130. for points, code in path.iter_segments():
  131. if code == Path.MOVETO:
  132. self.moveto(points)
  133. elif code == Path.LINETO:
  134. self.lineto(points)
  135. elif code == Path.CURVE3:
  136. self.curve3(points)
  137. elif code == Path.CURVE4:
  138. self.curve4(points)
  139. elif code == Path.CLOSEPOLY:
  140. self.closepoly()
  141. self.clip_path()
  142. ########################################################################
  143. #
  144. # The following functions and classes are for pylab and implement
  145. # window/figure managers, etc...
  146. #
  147. ########################################################################
  148. def draw_if_interactive():
  149. """
  150. For performance reasons, we don't want to redraw the figure after
  151. each draw command. Instead, we mark the figure as invalid, so that
  152. it will be redrawn as soon as the event loop resumes via PyOS_InputHook.
  153. This function should be called after each draw event, even if
  154. matplotlib is not running interactively.
  155. """
  156. figManager = Gcf.get_active()
  157. if figManager is not None:
  158. figManager.canvas.invalidate()
  159. def new_figure_manager(num, *args, **kwargs):
  160. """
  161. Create a new figure manager instance
  162. """
  163. FigureClass = kwargs.pop('FigureClass', Figure)
  164. figure = FigureClass(*args, **kwargs)
  165. canvas = FigureCanvasMac(figure)
  166. manager = FigureManagerMac(canvas, num)
  167. return manager
  168. class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase):
  169. """
  170. The canvas the figure renders into. Calls the draw and print fig
  171. methods, creates the renderers, etc...
  172. Public attribute
  173. figure - A Figure instance
  174. Events such as button presses, mouse movements, and key presses
  175. are handled in the C code and the base class methods
  176. button_press_event, button_release_event, motion_notify_event,
  177. key_press_event, and key_release_event are called from there.
  178. """
  179. def __init__(self, figure):
  180. FigureCanvasBase.__init__(self, figure)
  181. width, height = self.get_width_height()
  182. self.renderer = RendererMac(figure.dpi, width, height)
  183. _macosx.FigureCanvas.__init__(self, width, height)
  184. def resize(self, width, height):
  185. self.renderer.set_width_height(width, height)
  186. dpi = self.figure.dpi
  187. width /= dpi
  188. height /= dpi
  189. self.figure.set_size_inches(width, height)
  190. def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
  191. orientation='portrait', **kwargs):
  192. if dpi is None: dpi = matplotlib.rcParams['savefig.dpi']
  193. filename = unicode(filename)
  194. root, ext = os.path.splitext(filename)
  195. ext = ext[1:].lower()
  196. if not ext:
  197. ext = "png"
  198. filename = root + "." + ext
  199. if ext=="jpg": ext = "jpeg"
  200. # save the figure settings
  201. origfacecolor = self.figure.get_facecolor()
  202. origedgecolor = self.figure.get_edgecolor()
  203. # set the new parameters
  204. self.figure.set_facecolor(facecolor)
  205. self.figure.set_edgecolor(edgecolor)
  206. if ext in ('jpeg', 'png', 'tiff', 'gif', 'bmp'):
  207. width, height = self.figure.get_size_inches()
  208. width, height = width*dpi, height*dpi
  209. self.write_bitmap(filename, width, height)
  210. elif ext == 'pdf':
  211. self.write_pdf(filename)
  212. elif ext in ('ps', 'eps'):
  213. from backend_ps import FigureCanvasPS
  214. # Postscript backend changes figure.dpi, but doesn't change it back
  215. origDPI = self.figure.dpi
  216. fc = self.switch_backends(FigureCanvasPS)
  217. fc.print_figure(filename, dpi, facecolor, edgecolor,
  218. orientation, **kwargs)
  219. self.figure.dpi = origDPI
  220. self.figure.set_canvas(self)
  221. elif ext=='svg':
  222. from backend_svg import FigureCanvasSVG
  223. fc = self.switch_backends(FigureCanvasSVG)
  224. fc.print_figure(filename, dpi, facecolor, edgecolor,
  225. orientation, **kwargs)
  226. self.figure.set_canvas(self)
  227. else:
  228. raise ValueError("Figure format not available (extension %s)" % ext)
  229. # restore original figure settings
  230. self.figure.set_facecolor(origfacecolor)
  231. self.figure.set_edgecolor(origedgecolor)
  232. class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
  233. """
  234. Wrap everything up into a window for the pylab interface
  235. """
  236. def __init__(self, canvas, num):
  237. FigureManagerBase.__init__(self, canvas, num)
  238. title = "Figure %d" % num
  239. _macosx.FigureManager.__init__(self, canvas, title)
  240. if matplotlib.rcParams['toolbar']=='classic':
  241. self.toolbar = NavigationToolbarMac(canvas)
  242. elif matplotlib.rcParams['toolbar']=='toolbar2':
  243. self.toolbar = NavigationToolbar2Mac(canvas)
  244. else:
  245. self.toolbar = None
  246. if self.toolbar is not None:
  247. self.toolbar.update()
  248. def notify_axes_change(fig):
  249. 'this will be called whenever the current axes is changed'
  250. if self.toolbar != None: self.toolbar.update()
  251. self.canvas.figure.add_axobserver(notify_axes_change)
  252. # This is ugly, but this is what tkagg and gtk are doing.
  253. # It is needed to get ginput() working.
  254. self.canvas.figure.show = lambda *args: self.show()
  255. def show(self):
  256. self.canvas.draw()
  257. def close(self):
  258. Gcf.destroy(self.num)
  259. class NavigationToolbarMac(_macosx.NavigationToolbar):
  260. def __init__(self, canvas):
  261. self.canvas = canvas
  262. basedir = os.path.join(matplotlib.rcParams['datapath'], "images")
  263. images = {}
  264. for imagename in ("stock_left",
  265. "stock_right",
  266. "stock_up",
  267. "stock_down",
  268. "stock_zoom-in",
  269. "stock_zoom-out",
  270. "stock_save_as"):
  271. filename = os.path.join(basedir, imagename+".ppm")
  272. images[imagename] = self._read_ppm_image(filename)
  273. _macosx.NavigationToolbar.__init__(self, images)
  274. self.message = None
  275. def _read_ppm_image(self, filename):
  276. data = ""
  277. imagefile = open(filename)
  278. for line in imagefile:
  279. if "#" in line:
  280. i = line.index("#")
  281. line = line[:i] + "\n"
  282. data += line
  283. imagefile.close()
  284. magic, width, height, maxcolor, imagedata = data.split(None, 4)
  285. width, height = int(width), int(height)
  286. assert magic=="P6"
  287. assert len(imagedata)==width*height*3 # 3 colors in RGB
  288. return (width, height, imagedata)
  289. def panx(self, direction):
  290. axes = self.canvas.figure.axes
  291. selected = self.get_active()
  292. for i in selected:
  293. axes[i].xaxis.pan(direction)
  294. self.canvas.invalidate()
  295. def pany(self, direction):
  296. axes = self.canvas.figure.axes
  297. selected = self.get_active()
  298. for i in selected:
  299. axes[i].yaxis.pan(direction)
  300. self.canvas.invalidate()
  301. def zoomx(self, direction):
  302. axes = self.canvas.figure.axes
  303. selected = self.get_active()
  304. for i in selected:
  305. axes[i].xaxis.zoom(direction)
  306. self.canvas.invalidate()
  307. def zoomy(self, direction):
  308. axes = self.canvas.figure.axes
  309. selected = self.get_active()
  310. for i in selected:
  311. axes[i].yaxis.zoom(direction)
  312. self.canvas.invalidate()
  313. def save_figure(self):
  314. filename = _macosx.choose_save_file('Save the figure')
  315. if filename is None: # Cancel
  316. return
  317. self.canvas.print_figure(filename)
  318. class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
  319. def __init__(self, canvas):
  320. NavigationToolbar2.__init__(self, canvas)
  321. def _init_toolbar(self):
  322. basedir = os.path.join(matplotlib.rcParams['datapath'], "images")
  323. _macosx.NavigationToolbar2.__init__(self, basedir)
  324. def draw_rubberband(self, event, x0, y0, x1, y1):
  325. self.canvas.set_rubberband(x0, y0, x1, y1)
  326. def release(self, event):
  327. self.canvas.remove_rubberband()
  328. def set_cursor(self, cursor):
  329. _macosx.set_cursor(cursor)
  330. def save_figure(self):
  331. filename = _macosx.choose_save_file('Save the figure')
  332. if filename is None: # Cancel
  333. return
  334. self.canvas.print_figure(filename)
  335. def prepare_configure_subplots(self):
  336. toolfig = Figure(figsize=(6,3))
  337. canvas = FigureCanvasMac(toolfig)
  338. toolfig.subplots_adjust(top=0.9)
  339. tool = SubplotTool(self.canvas.figure, toolfig)
  340. return canvas
  341. def set_message(self, message):
  342. _macosx.NavigationToolbar2.set_message(self, message.encode('utf-8'))
  343. ########################################################################
  344. #
  345. # Now just provide the standard names that backend.__init__ is expecting
  346. #
  347. ########################################################################
  348. FigureManager = FigureManagerMac