On 3/22/06, [EMAIL PROTECTED] <[EMAIL PROTECTED]> wrote: > Does matplotlib work with AquaTerm as a backend?
Recently an AquaTerm backend was donated to matplotlib. I haven't had a chance to test or commit it to svn yet though. I am attaching it for anyone interested. - Charlie
""" An AquaTerm backend for Matplotlib and MacOS X Vserion: 1.0b1 Author: Chris Murphy (christophermdmurphy at gmail d o t c o m) See 'Installation' below before attempting to use this backend. Features: - Generate plots in MacOS X without using XWindows (X11) - Save your plots as PDF files! - Antialiased drawing built into AquaTerm (optional, adjust in AquaTerm Preferences) - You get all of the great benefits of MacOS X and AquaTerm and Python! Known Bugs: - Clipping is causing some polygons to lose their face color - Clipping is causing label and title text to get clipped - Cannot change fonts - No support for hex colors - No support for Greyscale - No support for images - No support for dashed lines - Draw arc is not optimized and is slow - Resolution issues... some lines are getting drawn at subpixel linewidths. Aquaterm seems confused. - PyObjC bridge does not allow passing of pointers - No support for mathtext Licensing: I borrowed a lot of code and concepts from backend_template.py and some of the other backends. Credits; Thanks to Per Persson for authoring AquaTerm and providing tons of support. Thanks to John Hunter et. al. for authoring matplotlib. Thanks to Tom Kornack for the idea of writing this backend. Notes: This is a fully functional AquaTerm backend. It is fully functional in that you can select it as a backend with import matplotlib matplotlib.use('AQT') Installtion: This backend requires that you first install AquaTerm 1.0 for Mac OS X. This backend also requires the installation of the PyObjC bridge. Before you use backend_aqt, follow these steps: Add 'AQT' to the switchyard in matplotlib/backends/__init__.py and 'AQT' to the _knownBackends dict in matplotlib/__init__.py and you're off. You can use your backend with import matplotlib matplotlib.use('AQT') from matplotlib.pylab import * plot([1,2,3]) savefig('My Plot Title') The files that are most relevant to backend_writers are matplotlib/backends/backend_your_backend.py matplotlib/backend_bases.py matplotlib/backends/__init__.py matplotlib/__init__.py matplotlib/_pylab_helpers.py Naming Conventions * classes MixedUpperCase * varables lowerUpper * functions underscore_separated """ from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ FigureManagerBase, FigureCanvasBase #,error_msg from matplotlib.cbook import enumerate#, True, False #check for Python version. If <2.3 import True, False from matplotlib.figure import Figure from matplotlib.transforms import Bbox from matplotlib.font_manager import fontManager from matplotlib.ft2font import FT2Font from matplotlib.mathtext import math_parse_s_ps, bakoma_fonts import sys,os,math from math import pi from math import cos from math import sin import objc from Foundation import * objc.loadBundle("AquaTerm", globals(), bundle_path='/Library/Frameworks/AquaTerm.framework') #####Globals##### _fontd = {} _clipd = {} #Toggle Debugging Output DEBUG = 1 RDEBUG = 1 #Constants that specify linecap styles. AQTButtLineCapStyle=0 AQTRoundLineCapStyle=1 AQTSquareLineCapStyle=2 #Constants that specify horizontal and vertical alignment for labels. See #addLabel:atPoint:angle:align: for definitions and use. AQTAlignLeft=0 AQTAlignCenter=1 AQTAlignRight=2 #Constants that specify vertical alignment for labels. AQTAlignMiddle=0 AQTAlignBaseline=4 AQTAlignBottom=8 AQTAlignTop=16 if DEBUG: print "\n\n####################NEW PLOT##################\n\n" #Create the adapater object in ObjC adapter = AQTAdapter.alloc().init() def error_msg_aqt(msg, *args): """ Signal an error condition -- in a GUI, popup a error dialog """ print >>sys.stderr, 'Error:', msg sys.exit() class RendererAQT(RendererBase): """ The renderer handles all the drawing primitives using a graphics context instance that controls the colors/styles """ if DEBUG: print "\nStarting RendererAQT" def __init__(self, width, height, adapter): if RDEBUG: print "RendererAQT __init__" self.width = width self.height = height self._groupd = {} self._imaged = {} self._adapter = adapter self._adapter.setPlotSize_((self.width, self.height)) if RDEBUG: print "width(x) = %(width)s" % {'width' : self.width} + " pixels" if RDEBUG: print "height(y) = %(height)s" % {'height' : self.height} + " pixels\n" def get_canvas_width_height(self): 'return the canvas width and height in display coords' if RDEBUG: print "get_canvas_width_height" return self.width, self.height def get_text_width_height(self, s, prop, ismath): """ get the width and height in display coords of the string s with fontsize in points """ if RDEBUG: print "get_text_width_height" #if RDEBUG: print "prop from get_text_width_height:" #if RDEBUG: print prop font = self._get_font(prop) font.set_text(s, 0.0) w, h = font.get_width_height() w /= 64.0 # convert from subpixels h /= 64.0 if RDEBUG: print "width = %(w)s" % {'w' : w} + " pixels" if RDEBUG: print "height = %(h)s" % {'h' : h} + " pixels" return w, h def _get_font(self, prop): if RDEBUG: print "_get_font" key = hash(prop) font = _fontd.get(key) if font is None: fname = fontManager.findfont(prop) try: font = FT2Font(str(fname)) except RuntimeError, msg: print >> sys.stderr, 'Could not load filename for text',fname return None else: _fontd[key] = font font.clear() size = prop.get_size_in_points() font.set_size(size, 72.0) return font def flipy(self): 'return true if y small numbers are top for renderer' if RDEBUG: print "flipsy" return False def draw_mathtext(self, gc, x, y, s, prop, angle): """ Draw the math text using matplotlib.mathtext """ if RDEBUG: print 'RendererAQT.draw_mathtext' fontsize = prop.get_size_in_points() width, height, pswriter = math_parse_s_ps(s, 72, fontsize) if 0: print 'RendererAQT.draw_mathtext:' print 'x, y: ', x, y print 's: ', s self.draw_rectangle(gc, (1, 1, 1), x, y, width, height) thetext = pswriter.getvalue() # ps = """gsave #%(x)f %(y)f translate #%(angle)f rotate #%(thetext)s #grestore #""" % locals() # self.draw_postscript(ps) def draw_text(self, gc, x, y, s, prop, angle, ismath=False): """ Render the matplotlib.text.Text instance at x, y in window coords using GraphicsContext gc """ if RDEBUG: print "draw_text: %(text)s" % {'text' : s} #if RDEBUG: print "prop from draw_text:" #if RDEBUG: print prop if ismath: return self.draw_mathtext(gc, x, y, s, prop, angle) font = self._get_font(prop) fontsize = prop.get_size_in_points() fontname = font.get_sfnt()[(1,0,0,6)] #if RDEBUG: print "fontsize = %(size)s" % {'size' : fontsize} + " point" #if RDEBUG: print "fontname = %(name)s" % {'name' : fontname} + " point" #FixMe #self._adapter.setFontname_(fontname) self._adapter.setFontsize_(fontsize) self._adapter.addLabel_atPoint_angle_align_(s, (x,y), angle, (0|8)) #Good place to beta test draw_arc #self.draw_arc(gc, None, 320, 240, 480, 10, 90, 225) def draw_arc(self, gcEdge, rgbFace, x, y, width, height, angle1, angle2): """ Draw an arc centered at x,y with width and height and angles from 0.0 to 360.0. If rgbFace is not None, fill the rectangle with it. gcEdge is a GraphicsContext instance """ if RDEBUG: print "draw_arc" #radius r=width/2.0 if width is 0: r=height/2.0 xorigin=x yorigin=y a=range(360) x=range(360) y=range(360) i=angle1 #Calculate all coordinate pairs in a circle, to draw arc for i in a: radians = i*pi/179.5 x[i] = xorigin + r*sin(radians) y[i] = yorigin + r*cos(radians) #Fill the arc with color rgbFace if rgbFace is not None: r,g,b=rgbFace self._adapter.setColorRed_green_blue_(r,g,b) adapter.moveToVertexPoint_((x[0],y[0])) i=1 for i in a: adapter.addEdgeToVertexPoint_((x[i],y[i])) #Get the edge color and linewidth r,g,b=gcEdge.get_rgb() self._adapter.setColorRed_green_blue_(r,g,b) self._adapter.setLinewidth_(gcEdge.get_linewidth()) #Draw the arc adapter.moveToPoint_((x[0], y[0])) #Only draw points up to angle2 a=range(angle2) #print "angle1 = %(angle1)s" % {'angle1' : angle1} + " degrees" i=angle1 #print "i = %(i)s" % {'i' : i} + " degrees" for i in a: #print "i = %(i)s" % {'i' : i} + " degrees" adapter.addLineToPoint_((x[i],y[i])) def draw_line(self, gc, x1, y1, x2, y2): """ Draw a single line from x1,y1 to x2,y2 """ if RDEBUG: print "draw_line" self._adapter.moveToPoint_((x1, y1)) self._adapter.addLineToPoint_((x2, y2)) def draw_lines(self, gc, x, y): """ x and y are equal length arrays, draw lines connecting each point in x, y """ if RDEBUG: print "draw_lines" #test for x array size if len(x)==0: return if len(x)!=len(y): error_msg_aqt('x and y must be the same length') r=range(len(x)) self._adapter.moveToPoint_((x[0], y[0])) for i in r: #if RDEBUG: print "draw_lines loop" self._adapter.addLineToPoint_((x[i], y[i])) def draw_polygon(self, gcEdge, rgbFace, points): """ Draw a polygon. points is a len vertices tuple, each element giving the x,y coords a vertex. If rgbFace is not None, fill the rectangle with it. gcEdge is a GraphicsContext instance """ if RDEBUG: print "draw_polygon" if RDEBUG: print points if RDEBUG: print rgbFace a=len(points) j=range(a) self._adapter.moveToPoint_(points[0]) for i in j: r,g,b=gcEdge.get_rgb() self._adapter.setColorRed_green_blue_(r,g,b) self._adapter.setLinewidth_(gcEdge.get_linewidth()) self._adapter.addLineToPoint_(points[i]) self._adapter.addLineToPoint_(points[0]) if rgbFace is not None: r,g,b=rgbFace self._adapter.setColorRed_green_blue_(r,g,b) a=len(points) j=range(a) self._adapter.moveToVertexPoint_(points[0]) for i in j: self._adapter.addEdgeToVertexPoint_(points[i]) def draw_rectangle(self, gcEdge, rgbFace, x, y, width, height): """ Draw a rectangle at lower left x,y with width and height. If rgbFace is not None, fill the rectangle with it. gcEdge is a GraphicsContext instance """ if RDEBUG: print "draw_rectangle" if rgbFace is not None: if RDEBUG: print "draw_rectangle with color" r,g,b=rgbFace self._adapter.setColorRed_green_blue_(r,g,b) self._adapter.addFilledRect_(((x, y), (width, height))) if RDEBUG: print "draw_rectangle add gcEdge to Rectangle" r,g,b=gcEdge.get_rgb() self._adapter.setColorRed_green_blue_(r,g,b) self._adapter.setLinewidth_(gcEdge.get_linewidth()) self._adapter.moveToPoint_((x, y)) x+=width self._adapter.addLineToPoint_((x, y)) y+=height self._adapter.addLineToPoint_((x, y)) x-=width self._adapter.addLineToPoint_((x, y)) y-=height self._adapter.addLineToPoint_((x, y)) def draw_point(self, gc, x, y): """ Draw a single point at x,y """ if RDEBUG: print "draw_point" #FixMe is there a better way to draw points using AquaTerm? self._adapter.moveToPoint_((x, y)) self._adapter.addLineToPoint_((x+1, y+1)) def new_gc(self): """ Return an instance of a GraphicsContextAQT """ if RDEBUG: print "\nnew_gc" return GraphicsContextAQT() def points_to_pixels(self, points): """ convert points to display units. Many imaging systems assume some value for pixels per inch. Eg, suppose yours is 96 and dpi = 300. Then points to pixels is """ if RDEBUG: print "points to pixels" return 100/72.0 * 80/72.0 * points def draw_image(self, x, y, im, origin, bbox): """ Draw the Image instance into the current axes; x is the distance in pixels from the left hand side of the canvas. y is the distance from the origin. That is, if origin is upper, y is the distance from top. If origin is lower, y is the distance from bottom """ if RDEBUG: print "draw_image broken" #need to calculate size. x,y are not a location, they are a NSsize. self._adapter.addImageWithBitmap_size_bounds_(im, (x, y), bbox) def _render(self): """ Render the plot in AquaTerm """ if RDEBUG: print "Render the plot in AquaTerm" self._adapter.renderPlot() def clear(self): self._adapter.clearPlot() class GraphicsContextAQT(GraphicsContextBase): """ The graphics context provides the color, line styles, etc... See the gtk and postscript backends for examples of mapping the graphics context attributes (cap styles, join styles, line widths, colors) to a particular backend. In GTK this is done by wrapping a gtk.gdk.GC object and forwarding the appropriate calls to it using a dictionary mapping styles to gdk constants. In Postscript, all the work is done by the renderer, mapping line styles to postscript calls. The base GraphicsContext stores colors as a RGB tuple on the unit interval, eg, (0.5, 0.0, 1.0). You will probably need to map this to colors appropriate for your backend. Eg, see the ColorManager class for the GTK backend. If it's more appropriate to do the mapping at the renderer level (as in the postscript backend), you don't need to override any of the GC methods. If it's more approritate to wrap an instance (as in the GTK backend) and do the mapping here, you'll need to override several of the setter methods. """ if DEBUG: print "Starting GraphicsContextAQT" #line cap styles dictionary _capd = { 'butt' : 0, 'projecting' : 2, 'round' : 1, } #dash pattern dictionary _dashd = { 'dash0' : 0, 'dash1' : 0, 'dash2' : 0, 'dash3' : 0, 'dash4' : 0, 'dash5' : 0, 'dash6' : 0, 'dash7' : 0, } #dash pattern arguments #_count = 0 #_phase = 0 def set_foreground(self, fg, isRGB=None): """ Set the foreground color. fg can be a matlab format string, a html hex color string, an rgb unit tuple, or a float between 0 and 1. In the latter case, grayscale is used. """ #CM No Hex or Grayscale support yet! GraphicsContextBase.set_foreground(self, fg, isRGB) if isRGB is None: if len(fg) > 1: if fg is 'white': adapter.setColorRed_green_blue_(1.0, 1.0, 1.0) if fg is 'black': adapter.setColorRed_green_blue_(0.0, 0.0, 0.0) elif len(fg) is 3: r,g,b=fg adapter.setColorRed_green_blue_(r,g,b) elif len(fg) is 1: if fg is 'r': adapter.setColorRed_green_blue_(1.0, 0.0, 0.0) if fg is 'g': adapter.setColorRed_green_blue_(0.0, 1.0, 0.0) if fg is 'b': adapter.setColorRed_green_blue_(0.0, 0.0, 1.0) if fg is 'c': adapter.setColorRed_green_blue_(0.0, 1.0, 1.0) if fg is 'm': adapter.setColorRed_green_blue_(1.0, 0.0, 1.0) if fg is 'y': adapter.setColorRed_green_blue_(1.0, 1.0, 0.0) if fg is 'k': adapter.setColorRed_green_blue_(0.0, 0.0, 0.0) if fg is 'w': adapter.setColorRed_green_blue_(1.0, 1.0, 1.0) if fg <= 1: adapter.setColorRed_green_blue_(fg,0,0) # elif fg.isdigit(): # adapter.setColorRed_green_blue_(fg, 0.0, 0.0) # if DEBUG: print "color isRGB is None=%(fg)s" % {'fg' : fg} #elif isRGB is True: # if DEBUG: print "color isRBGB is True=%(fg)s" % {'fg' : fg} def set_linewidth(self, lw): GraphicsContextBase.set_linewidth(self, lw) adapter.setLinewidth_(lw) #This produces a lot of output! #if DEBUG: print "linewidth = %(lw)s" % {'lw' : lw} + " pixels" def set_capstyle(self, cs): GraphicsContextBase.set_capstyle(self, cs) 'one of butt/round/square/none' if cs is 'butt': adapter.setLineCapStyle_(0) if cs is 'round': adapter.setLineCapStyle_(1) if cs is 'projecting': adapter.setLineCapStyle_(2) def set_clip_rectangle(self, rect): if RDEBUG: print "set_clip_rectangle = %(rect)s" % {'rect' : rect} #x,y,width,height = rect #adapter.setClipRect_(((x, y), (width, height))) def set_linestyle(self, style): """ Set the linestyle to be one of ('solid', 'dashed', 'dashdot', 'dotted'). """ if RDEBUG: print "set_linestyle = %(style)s" % {'style' : style} #if style is 'solid' : adapter.setLinestyleSolid_() #if style is 'dashed' : adapter.setLinestylePatternPython_count_phase_(dashd["dash1"], dashd["dash2"], dashd["dash3"], dashd["dash4"], _count, _phase) #def set_dashes(self, offset, dashes): """ Set the dash pattern. The dash list is an even size list that gives the ink on, ink off in pixels. AquaTerm is limited to a count of 8 as of v1.0b3. """ # if RDEBUG: print "set_dashes = offset: %(offset)s" % {'offset' : offset} + " dashes: %(dashes)s" % {'dashes' : dashes} # if offset is not None: # _phase = offset #else # _phase = 0 #if dashes is None: # adapter.setLinestyleSolid_() # switch dashes off # _count = 0 # _phase = 0 #elif len(dashes) is 2: # _count = 2 # _dashd["dashd1"], _dashd["dash2"] = dashes #elif len(dashes) is 4: # _count = 4 # _dashd["dashd1"], _dashd["dash2"], _dashd["dash3"], _dashd["dash4"] = dashes #elif len(dashes) is 6: # _count = 6 # _dashd["dashd1"], _dashd["dash2"], _dashd["dash3"], _dashd["dash4"], _dashd["dash5"], _dashd["dash6"] = dashes #elif len(dashes) is 8: # _count = 8 # _dashd["dashd1"], _dashd["dash2"], _dashd["dash3"], _dashd["dash4"], _dashd["dash5"], _dashd["dash6"], _dashd["dash7"], _dashd["dash8"] = dashes ######################################################################## # # The following functions and classes are for matlab compatibility # mode (matplotlib.pylab) and implement window/figure managers, # etc... # ######################################################################## def draw_if_interactive(): """ This should be overriden in a windowing environment if drawing should be done in interactive python mode """ #if DEBUG: print "Starting draw_if_interactive" def show(): """ This is usually the last line of a matlab script and tells the backend that it is time to draw. In interactive mode, this may be a do nothing func. See the GTK backend for an example of how to handle interactive versus batch mode """ if DEBUG: print "Starting show()" for manager in Gcf.get_all_fig_managers(): manager.canvas.realize() def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ if DEBUG: print "Starting new_figure_manager" thisFig = Figure(*args, **kwargs) canvas = FigureCanvasAQT(thisFig) manager = FigureManagerAQT(canvas, num) return manager class FigureCanvasAQT(FigureCanvasBase): """ The canvas the figure renders into. Calls the draw and print fig methods, creates the renderers, etc... Public attribute figure - A Figure instance """ def draw(self): """ Draw the figure using the renderer """ if DEBUG: print "Starting draw in Figure CanvasAQT" width, height = self.figure.get_size_inches() if DEBUG: print "width(x) = %(width)s" % {'width' : width} + " inches" if DEBUG: print "height(y) = %(height)s" % {'height' : height} + " inches" #CM Convert Inches to pixels w = width*80 h = height*80 adapter.openPlotWithIndex_(1) renderer = RendererAQT(w, h, adapter) self.figure.draw(renderer) renderer._render() def print_figure(self, filename, dpi=80, facecolor='w', edgecolor='w', orientation='portrait'): """ Render the figure to hardcopy. Set the figure patch face and edge colors. This is useful because some of the GUIs have a gray figure face color background and you'll probably want to override this on hardcopy """ if DEBUG: print "Starting print_figure in Figure Canvas AQT" # set the new parameters origDPI = self.figure.dpi.get() if DEBUG: print "origDPI= %(origDPI)s" % {'origDPI' : origDPI} + "dpi" width, height = self.figure.get_size_inches() if DEBUG: print "width(x) = %(width)s" % {'width' : width} + " inches" if DEBUG: print "height(y) = %(height)s" % {'height' : height} + " inches" origfacecolor = self.figure.get_facecolor() origedgecolor = self.figure.get_edgecolor() #turning this off makes the image smaller but maintains the same window size: self.figure.dpi.set(dpi) self.figure.set_facecolor(facecolor) self.figure.set_edgecolor(edgecolor) #CM Convert Inches to pixels w = width*100 h = height*100 adapter.openPlotWithIndex_(1) adapter.setPlotTitle_(filename) renderer = RendererAQT(w, h, adapter) self.figure.draw(renderer) renderer._render() # do something to save to hardcopy # restore the new params and redraw the screen if necessary #self.figure.dpi.set(origDPI) #self.figure.set_facecolor(origfacecolor) #self.figure.set_edgecolor(origedgecolor) #self.draw() def realize(self, *args): """ This method will be called when the system is ready to draw, eg when a GUI window is realized """ if DEBUG: print "Starting realize in Figure Canvas AQT" self._isRealized = True self.draw() class FigureManagerAQT(FigureManagerBase): """ Wrap everything up into a window for the matlab interface For non interactive backends, the base class does all the work """ if DEBUG: print "Starting FigureManagerAQT(FigureManagerBase)" ######################################################################## # # Now just provide the standard names that backend.__init__ is expecting # ######################################################################## FigureManager = FigureManagerAQT error_msg = error_msg_aqt
_______________________________________________ Pythonmac-SIG maillist - Pythonmac-SIG@python.org http://mail.python.org/mailman/listinfo/pythonmac-sig