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 - [email protected]
http://mail.python.org/mailman/listinfo/pythonmac-sig