From: Thane <th...@ma...> - 2004-11-04 22:31:05
|
""" backend_dotnet.py A .NET backend for matplotlib, based on backend_template.py and=0A= various other backends. =0A= Author: Thane Plummer (th...@ma...) =20 This module requires that .NET be installed on the computer. =20 License: This work is licensed under a PSF compatible license. A copy=0A= should be included with this source code. =20 History: 0.2.4 11/04/04 Added dashstyle parsing for lines.=20 0.2.3 11/04/04 Fixed draw_arc coordinates and fill. 0.2.2 11/04/04 Fixed colors; all coord xform now done in Python. 0.2.1 11/03/04 Fixed GraphicsContext parsing to the .NET DLL. 0.2.0 11/01/04 Created a DLL for the backend. Should work with = any .NET machine. 0.1.1 10/21/04 Added threading for interactive use from command = line. 0.1.0 08/15/04 Initial coding in PythonNet. Needs some tweaking, = but most of it works.=0A= =0A= """ __version__ =3D "0.2.4"=0A= =0A= import sys import math import matplotlib=0A= from matplotlib._matlab_helpers import Gcf=0A= from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \=0A= FigureManagerBase, FigureCanvasBase, error_msg=0A= =0A= from matplotlib.cbook import enumerate, True, False=0A= from matplotlib.figure import Figure=0A= from matplotlib.transforms import Bbox from matplotlib.colors import ColorConverter # .NET Graphics imports import _backend_dotnet as dotnet =0A= # the true dots per inch on the screen; should be display dependent=0A= # see = http://groups.google.com/groups?q=3Dscreen+dpi+x11&hl=3Den&lr=3D&ie=3DUTF= -8&oe=3DUTF-8&safe=3Doff&selm=3D7077.26e81ad5%40swift.cs.tcd.ie&rnum=3D5 = for some info about screen dpi=0A= PIXELS_PER_INCH =3D 75=0A= =0A= def error_msg_dotnet(msg, *args):=0A= """=0A= Signal an error condition -- in a GUI, popup a error dialog=0A= """=0A= print >>sys.stderr, 'Error:', msg #WinForms.MessageBox.Show("Error", msg)=0A= #sys.exit() # Errors should not be fatal in .NET=0A= =0A= class RendererDotNet(RendererBase):=0A= """=0A= The renderer handles all the drawing primitives using a graphics=0A= context instance that controls the colors/styles=0A= """ =20 def __init__(self, width, height): self.height =3D int(height) self.width =3D int(width) self.form =3D dotnet.create_form(self.width, self.height) =20 def show(self): dotnet.show()=0A= =0A= def get_canvas_width_height(self):=0A= 'return the canvas width and height in display coords'=0A= return self.width, self.height =0A= =0A= def get_text_width_height(self, s, fontsize, ismath=3DFalse):=0A= """=0A= get the width and height in display coords of the string s=0A= with fontsize in points fontsize =3D (['Lucida Grande', 'Verdana', 'Geneva', 'Lucida', = 'Bitstream Vera Sans', 'Arial', 'Helvetica', 'sans-serif'], 'normal', = 'normal', 'medium', 'normal', 12.0)=0A= """ w =3D len(s)*5 if isinstance(fontsize, tuple): h =3D fontsize[-1] else: h =3D 12=0A= return w, h =0A= def flipy(self):=0A= 'return true if y small numbers are top for renderer'=0A= return True=0A= =0A= def draw_text(self, gc, x, y, s, prop, angle, ismath=3DFalse): =0A= """=0A= Render the matplotlib.text.Text instance at x, y in window=0A= coords using GraphicsContext gc=0A= """ # Note: prop is a class 'instance' -- NOT a tuple # prop =3D (['Lucida Grande', 'Verdana', 'Geneva', 'Lucida', = 'Bitstream Vera Sans', 'Arial', 'Helvetica', 'sans-serif'], 'normal', = 'normal', 'medium', 'normal', 14.0) # See font_manager.py FontProperties class for more methods fontsize =3D prop.get_size() if not isinstance(fontsize, int) or not isinstance(fontsize, = float): fontsize =3D 10 # ToDo: support the prop.size = option in .NET fontfamily =3D prop.get_family() fontname =3D fontfamily[0] x =3D x - 1 # this one pixel seems to help y =3D int(y - fontsize - 1) # this one pixel seems to help angle =3D -angle dotnet.draw_text(gc.gc_tuple(), x, y, s, fontname, fontsize, = angle) =0A= =0A= def draw_arc(self, gcEdge, rgbFace, x, y, width, height, angle1, = angle2):=0A= """=0A= Draw an arc centered at x,y with width and height and angles=0A= from 0.0 to 360.0.=0A= =0A= If rgbFace is not None, fill the arc with it. gcEdge=0A= is a GraphicsContext instance=0A= """ ulX =3D x - width/2.0=0A= ulY =3D self.height - y - (height/2.0) if rgbFace: dotnet.fill_arc(rgbFace, ulX, ulY, width, height) dotnet.draw_arc(gcEdge.gc_tuple(), ulX, ulY, width, height, = angle1, angle2) =0A= =0A= def draw_line(self, gc, x1, y1, x2, y2):=0A= """=0A= Draw a single line from x1,y1 to x2,y2=0A= """ dotnet.draw_line(gc.gc_tuple(), x1, self.height - y1, x2, = self.height - y2)=0A= =0A= def draw_lines(self, gc, x, y):=0A= """=0A= x and y are equal length arrays, draw lines connecting each=0A= point in x, y=0A= """ # Morph the points for .NET graphics -- .NET method uses (x,y) = for upper left height =3D self.height # does this increase the speed? ulY =3D [height - yval for yval in y] dotnet.draw_lines(gc.gc_tuple(), x, ulY)=0A= =0A= def draw_polygon(self, gcEdge, rgbFace, points):=0A= """=0A= Draw a polygon. points is a len vertices tuple, each element=0A= giving the x,y coords a vertex.=0A= =0A= If rgbFace is not None, fill the rectangle with it. gcEdge=0A= is a GraphicsContext instance=0A= """=20 # Hack to fix point structure -- .NET method uses (x,y) for = upper left xvals =3D [x for x,y in points] yvals =3D [self.height - y for x,y in points] if rgbFace: dotnet.fill_polygon(rgbFace, xvals, yvals) dotnet.draw_polygon(gcEdge.gc_tuple(), xvals, yvals)=0A= =0A= def draw_rectangle(self, gcEdge, rgbFace, x, y, width, height):=0A= """=0A= Draw a rectangle at lower left x,y with width and height.=0A= =0A= If rgbFace is not None, fill the rectangle with it. gcEdge=0A= is a GraphicsContext instance=0A= """ if rgbFace: dotnet.fill_rectangle(rgbFace, x, self.height - = (self.height+y), width, height) dotnet.draw_rectangle(gcEdge, x, self.height - (self.height+y), = width, height) =0A= def draw_point(self, gc, x, y):=0A= """=0A= Draw a single point at x,y=0A= """ #~ print "draw_point" dotnet.draw_point(gc, x, self.height - y) =0A= =0A= def new_gc(self):=0A= """=0A= Return an instance of a GraphicsContextDotNet=0A= """ return GraphicsContextDotNet()=0A= =0A= def points_to_pixels(self, points):=0A= """=0A= Convert points to display units. Many imaging systems assume=0A= some value for pixels per inch. Eg, suppose yours is 96 and=0A= dpi =3D 300. Then points to pixels is: 96/72.0 * 300/72.0 * = points=0A= """=0A= #return 96/72.0 * 300/72.0 * points # Speed up DpiX =3D DpiY =3D 96 #print "self.graphics.DpiX, DpiY =3D %d %d" % = (self.graphics.DpiX, self.graphics.DpiY) return points #return 1.38889*points #return points*(PIXELS_PER_INCH/72.0*self.graphics.DpiX/72.0)=0A= class GraphicsContextBaseDotNet(GraphicsContextBase):=0A= =0A= # a mapping of dash styles #~ _dashd_dotnet =3D { #~ 'solid' : DashStyle.Solid,=0A= #~ 'dashed' : DashStyle.Dash,=0A= #~ 'dashdot' : DashStyle.DashDot,=0A= #~ 'dotted' : DashStyle.Dot, #~ 'dashdotdot' : DashStyle.DashDotDot #~ } =20 #~ _linejoin_dotnet =3D { #~ 'miter' : LineJoin.Miter,=20 #~ 'round' : LineJoin.Round,=20 #~ 'bevel' : LineJoin.Bevel #~ } =20 # ToDo: What is a 'projecting' capstyle? Triangle? Arrow? Square #~ _capstyle_dotnet =3D { #~ 'butt' : LineCap.Flat,=20 #~ 'round' : LineCap.Round,=20 #~ 'projecting' : LineCap.Square #~ } =20 =0A= def __init__(self):=0A= self._rgb =3D (0.0, 0.0, 0.0)=0A= self._linewidth =3D 1.0=0A= self._capstyle =3D 'butt'=0A= self._joinstyle =3D 'miter'=0A= self._dashes =3D None, None # if None it's converted to (-1, = '') in tuple=0A= self._cliprect =3D None # If None it's converted to (-1, -1, = -1, -1) in tuple=0A= self._linestyle =3D 'solid'=0A= self._antialiased =3D 1 # use 0,1 not True, False for extension = code=0A= self._alpha =3D 1.0 self.colorConverter =3D ColorConverter() =20 def gc_tuple(self): dash_offset, dash_list =3D self._dashes if not dash_offset: dash_offset =3D -1 if not dash_list: dash_list =3D "" cliprect =3D self._cliprect if not cliprect: cliprect =3D (-1, -1, -1, -1) return (self._rgb,=20 self._linewidth,=0A= self._capstyle,=0A= self._joinstyle,=0A= (dash_offset, dash_list),=0A= cliprect,=0A= self._linestyle,=0A= self._antialiased,=0A= self._alpha) =20 def get_color(self): r,g,b =3D self._rgb return Color.FromArgb(self._alpha *255, r*255, g*255, b*255)=0A= =0A= def get_antialiased(self):=0A= "Return true if the object shuold try to do antialiased = rendering"=0A= return self._antialiased=0A= =0A= def get_clip_rectangle(self):=0A= """=0A= Return the clip rectangle as (left, bottom, width, height)=0A= """=0A= return self._cliprect=0A= =0A= def get_dashes(self):=0A= """=0A= Return the dash information as an offset dashlist tuple The=0A= dash list is a even size list that gives the ink on, ink off=0A= in pixels. See p107 of to postscript BLUEBOOK for more info=0A= =0A= Default value is None=0A= """=0A= return self._dashes=0A= =0A= def get_alpha(self):=0A= """=0A= Return the alpha value used for blending - not supported on=0A= all backends=0A= """=0A= return self._alpha=0A= =0A= def get_capstyle(self):=0A= """=0A= Return the capstyle as a string in ('butt', 'round', = 'projecting')=0A= """=0A= return self._capstyle=0A= =0A= def get_joinstyle(self):=0A= """=0A= Return the line join style as one of ('miter', 'round', 'bevel')=0A= """=0A= return self._joinstyle=0A= =0A= # ToDo: BUG REPORT -> def get_linestyle(self, style): def get_linestyle(self):=0A= """=0A= Return the linestyle: one of ('solid', 'dashed', 'dashdot',=0A= 'dotted'). =0A= """=0A= return self._linestyle=0A= =0A= def get_rgb(self):=0A= """=0A= returns a tuple of three floats from 0-1. color can be a=0A= matlab format string, a html hex color string, or a rgb tuple=0A= """=0A= return self._rgb=0A= =0A= def set_antialiased(self, b):=0A= """=0A= True if object should be drawn with antialiased rendering=0A= """=0A= # use 0, 1 to make life easier on extension code trying to read = the gc=0A= if b:=20 self._antialiased =3D 1=0A= else:=20 self._antialiased =3D 0=0A= =0A= def set_clip_rectangle(self, rectangle):=0A= """=0A= Set the clip rectangle with sequence (left, bottom, width, = height)=0A= """ self._cliprect =3D rectangle # Do this with .NET? Need to add self.height and graphics = object to class #x, b, w, h =3D rectangle #y =3D self.height - int(self.height + b) #clipRegion =3D Rectangle(x, y, w, h) # upper-left_xy, width, = height #self.graphics.SetClip(clipRegion) =0A= =0A= def set_alpha(self, alpha):=0A= """=0A= Set the alpha value used for blending - not supported on=0A= all backends=0A= """=0A= self._alpha =3D alpha =0A= =0A= def set_linestyle(self, style):=0A= """=0A= Set the linestyle to be one of ('solid', 'dashed', 'dashdot',=0A= 'dotted'). =0A= """=0A= if style not in ('solid', 'dashed', 'dashdot', = 'dotted','dashdotdot'):=0A= error_msg('Unrecognized linestyle. Found %s' % js)=0A= return=0A= self._linestyle =3D style=0A= =0A= def set_dashes(self, dash_offset, dash_list):=0A= """=0A= Set the dash style for the gc. dash offset is the offset=0A= (usually 0). Dash list specifies the on-off sequence as=0A= points=0A= """=0A= self._dashes =3D dash_offset, dash_list=0A= =0A= def set_foreground(self, fg, isRGB=3DNone):=0A= """=0A= Set the foreground color. fg can be a matlab format string, a=0A= html hex color string, an rgb unit tuple, or a float between 0=0A= and 1. In the latter case, grayscale is used.=0A= =0A= The GraphicsContext converts colors to rgb internally. If you=0A= know the color is rgb already, you can set isRGB to True to=0A= avoid the performace hit of the conversion=0A= """ pass=0A= if isRGB:=0A= self._rgb =3D fg else: self._rgb =3D self.colorConverter.to_rgb(fg)=0A= =0A= =0A= def set_graylevel(self, frac):=0A= """=0A= Set the foreground color to be a gray level with frac frac=0A= """=0A= self._rgb =3D (frac, frac, frac)=0A= =0A= =0A= def set_linewidth(self, w):=0A= """=0A= Set the linewidth in points=0A= """=0A= self._linewidth =3D w =0A= def set_capstyle(self, cs):=0A= """=0A= Set the capstyle as a string in ('butt', 'round', 'projecting')=0A= """=0A= if cs not in ('butt', 'round', 'projecting'):=0A= error_msg('Unrecognized cap style. Found %s' % cs)=0A= self._capstyle =3D cs #~ self.pen.EndCap =3D self._capstyle_dotnet[cs]=0A= =0A= def set_joinstyle(self, js):=0A= """=0A= Set the join style to be one of ('miter', 'round', 'bevel')=0A= """=0A= if js not in ('miter', 'round', 'bevel'):=0A= error_msg('Unrecognized join style. Found %s' % js)=0A= self._joinstyle =3D js #~ self.pen.LineJoin =3D self._linejoin_dotnet[js]=0A= =0A= def get_linewidth(self):=0A= """=0A= Return the line width in points as a scalar=0A= """=0A= return self._linewidth=0A= =0A= def copy_properties(self, gc):=0A= 'Copy properties from gc to self'=0A= self._rgb =3D gc._rgb=0A= self._linewidth =3D gc._linewidth=0A= self._capstyle =3D gc._capstyle=0A= self._joinstyle =3D gc._joinstyle=0A= self._linestyle =3D gc._linestyle=0A= self._dashes =3D gc._dashes=0A= self._cliprect =3D gc._cliprect=0A= self._antialiased =3D gc._antialiased=0A= self._alpha =3D gc._alpha =20 =0A= class GraphicsContextDotNet(GraphicsContextBaseDotNet):=0A= """=0A= The graphics context provides the color, line styles, etc... See=0A= the gtk and postscript backends for examples of mapping the=0A= graphics context attributes (cap styles, join styles, line widths,=0A= colors) to a particular backend. In GTK this is done by wrapping=0A= a gtk.gdk.GC object and forwarding the appropriate calls to it=0A= using a dictionary mapping styles to gdk constants. In=0A= Postscript, all the work is done by the renderer, mapping line=0A= styles to postscript calls.=0A= =0A= The base GraphicsContext stores colors as a RGB tuple on the unit=0A= interval, eg, (0.5, 0.0, 1.0). You will probably need to map this=0A= to colors appropriate for your backend. Eg, see the ColorManager=0A= class for the GTK backend. If it's more appropriate to do the=0A= mapping at the renderer level (as in the postscript backend), you=0A= don't need to override any of the GC methods. If it's more=0A= approritate to wrap an instance (as in the GTK backend) and do the=0A= mapping here, you'll need to override several of the setter=0A= methods.=0A= """ =20 pass =20 =0A= =0A= ########################################################################=0A= # =0A= # The following functions and classes are for matlab compatibility=0A= # mode (matplotlib.matlab) and implement window/figure managers,=0A= # etc...=0A= #=0A= ########################################################################=0A= =0A= def draw_if_interactive():=0A= """=0A= Update the figure after every command from the command line.=0A= """ pass # Code below doesn't work... why?=0A= #~ if matplotlib.is_interactive():=0A= #~ figManager =3D Gcf.get_active()=0A= #~ if figManager is not None:=0A= #~ figManager.canvas.draw() =0A= def show():=0A= """=0A= This is usually the last line of a matlab script and tells the=0A= backend that it is time to draw. In interactive mode, this may be=0A= a do nothing func. See the GTK backend for an example of how to=0A= handle interactive versus batch mode=0A= """ for manager in Gcf.get_all_fig_managers():=0A= manager.canvas.realize()=0A= =0A= =0A= def new_figure_manager(num, *args, **kwargs):=0A= """=0A= Create a new figure manager instance=0A= """=0A= thisFig =3D Figure(*args, **kwargs) l,b,w,h =3D thisFig.bbox.get_bounds()=0A= w =3D int(math.ceil(w))=0A= h =3D int(math.ceil(h))=0A= canvas =3D FigureCanvasDotNet(w, h) canvas.figure =3D thisFig canvas.figurenum =3D num manager =3D FigureManagerDotNet(canvas, num)=0A= return manager =20 =0A= class FigureCanvasDotNet(FigureCanvasBase):=0A= """=0A= The canvas the figure renders into. Calls the draw and print fig=0A= methods, creates the renderers, etc...=0A= =0A= Public attribute=0A= =0A= figure - A Figure instance=0A= """ =20 def __init__(self, w, h): self.figure =3D None self.figurenum =3D None self.width =3D w #640 =20 self.height =3D h #480 self._isRealized =3D False self._isConfigured =3D False self.renderer =3D RendererDotNet(self.width, self.height) #~ self.Closed +=3D self.on_close # Release objects =0A= def draw(self):=0A= """=0A= Draw the figure using the renderer=0A= """ need_new_renderer =3D False try: self.renderer except: need_new_renderer =3D True =20 if need_new_renderer: if self.figure !=3D None: l,b,w,h =3D self.figure.bbox.get_bounds()=0A= w =3D int(math.ceil(w))=0A= h =3D int(math.ceil(h)) self.height =3D h self.width =3D w =0A= self.renderer =3D RendererDotNet(self.width, self.height) #print "New RendererDotNet"=0A= self.figure.draw(self.renderer) self.renderer.show() =20 #~ def on_close(self, sender, args): #~ #WinForms.MessageBox.Show("Info", "Closing form..") #~ Gcf.destroy(self.figurenum) =0A= =0A= def print_figure(self, filename, dpi=3D150,=0A= facecolor=3D'w', edgecolor=3D'w',=0A= orientation=3D'portrait'):=0A= =0A= """=0A= Render the figure to hardcopy. Set the figure patch face and=0A= edge colors. This is useful because some of the GUIs have a=0A= gray figure face color background and you'll probably want to=0A= override this on hardcopy=0A= """=0A= # set the new parameters=0A= origDPI =3D self.figure.dpi.get()=0A= origfacecolor =3D self.figure.get_facecolor()=0A= origedgecolor =3D self.figure.get_edgecolor()=0A= self.figure.dpi.set(dpi)=0A= self.figure.set_facecolor(facecolor)=0A= self.figure.set_edgecolor(edgecolor) =0A= =0A= renderer =3D RendererDotNet(self.width, self.height)=0A= self.figure.draw(renderer)=0A= # do something to save to hardcopy=0A= =0A= # restore the new params and redraw the screen if necessary=0A= self.figure.dpi.set(origDPI)=0A= self.figure.set_facecolor(origfacecolor)=0A= self.figure.set_edgecolor(origedgecolor)=0A= self.draw()=0A= =0A= def realize(self, *args):=0A= """=0A= This method will be called when the system is ready to draw,=0A= eg when a GUI window is realized=0A= """=0A= self._isRealized =3D True =0A= self.draw() =0A= class FigureManagerDotNet(FigureManagerBase):=0A= """=0A= Public attributes=0A= =0A= canvas : The FigureCanvas instance=0A= num : The Figure number=0A= toolbar : The gtk.Toolbar=0A= window : The gtk.Window=0A= =0A= """ pass=0A= #~ def __init__(self, canvas, num):=0A= #~ FigureManagerBase.__init__(self, canvas, num)=0A= =0A= #~ dotnet.window_title("Figure %d" % num)=0A= #~ self.canvas.show()=0A= =0A= =0A= ########################################################################=0A= # =0A= # Now just provide the standard names that backend.__init__ is expecting=0A= # =0A= ########################################################################=0A= =0A= =0A= FigureManager =3D FigureManagerDotNet=0A= error_msg =3D error_msg_dotnet=0A= =0A= |