From: John H. <jdh...@ac...> - 2004-05-26 15:56:54
|
>>>>> "Jared" == Jared Wahlstrand <wah...@um...> writes: Jared> Hi, I wanted to be able to use Inkscape to fiddle with the Jared> details of my plot before exporting it to postscript, so I Jared> started writing an SVG backend. What I have so far is Jared> attached. Note that it's based on matplotlib-0.53.1, so it Jared> won't work with 0.54 yet. So far it just draws lines, Jared> rectangles, polygons, and text (only in helvetica). Jared> Before I go further, I wanted to make sure you or someone Jared> else is not already working on this. If not, I'll port it Jared> to use font-caching, the new transform stuff and figure out Jared> how to draw arcs and polygon collections, etc. This is absolutely great news! Glad to hear you made it so far on your own. I CCd the devel list, which you may want to join, in case anyone else has any advice for you. As far as I know, noone else is working on this. Here are a few thoughts for you when porting to 0.54. All the text layout has been moved to the text.Text front end. So you no longer need to worry about horizontal alignment and the like, compute_offsets is no longer needed, etc.. You also do not need to provide the window extent of text anymore. Just provide get_text_width_height(self, s, prop, ismath) where s is a string and prop is a FontProperties instance. This returns the width and height of the *unrotated* string. In the front end I use this to compute the width and height of the rotated string, compute the alignment offsets, and pass the x,y location to you to draw the rotated string in draw_text. Instead of working with Text instances, as in 0.53, you now work with strings and font properties. The other new Renderer method is get_canvas_width_height As for polygon collections, you can implement this if you want, but the base class provides an implementation that is reasonably fast (approx 5x faster than the prior method of using separate polygon instances). This may be good enough, since my guess is for most cases you only demand the highest performance for interactive use, eg with one of the GUI backends. So the *Agg backends implement this in extension code. As for draw_arc, currently this is only used for drawing circles, so if you implemented draw_circle there is would suffice. Jared> The fact that I, a python novice, could get so far in a day Jared> attests to the awesomeness of matplotlib. Thanks! A day... Amazing. Maybe we'll have you write the PDF backend next :-) JDH from __future__ import division from cStringIO import StringIO from matplotlib.afm import AFM from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ FigureManagerBase, FigureCanvasBase from matplotlib.cbook import iterable, is_string_like, flatten, enumerate,\ get_recursive_filelist, True, False from matplotlib.figure import Figure from matplotlib.font_manager import fontManager from matplotlib.ft2font import FT2Font from matplotlib._matlab_helpers import Gcf from matplotlib.text import Text from matplotlib.transforms import Bound1D, Bound2D, Transform from matplotlib import rcParams from matplotlib.numerix import fromstring, UInt8, Float32 import binascii import sys,os def error_msg_svg(msg, *args): print >>sys.stderr, 'Error:', msg sys.exit() def _nums_to_str(seq, fmt='%1.3f'): return ' '.join([_int_or_float(val, fmt) for val in seq]) def draw_if_interactive(): pass def show(): """ Show all the figures and enter the gtk mainloop This should be the last line of your script """ for manager in Gcf.get_all_fig_managers(): manager.figure.realize() def new_figure_manager(num, *args): thisFig = Figure(*args) canvas = FigureCanvasSVG(thisFig) manager = FigureManagerSVG(canvas, num) return manager def _rgb_to_hex(rgb): rgbhex='#' for c in rgb: h=hex(int(c*255))[2:] if len(h) < 2: h='0'+ h rgbhex += h return rgbhex class RendererSVG(RendererBase): def __init__(self, svgwriter,width,height): self._svgwriter = svgwriter self.width=width self.height=height def flipy(self): 'return true if y small numbers are top for renderer' return False def draw_rawsvg(self, svg): self._svgwriter.write(svg) def compute_text_offsets(self, t): """ Return the (x,y) offsets to adjust for the alignment specifications """ prop = t.get_font_properties() font = AFM(file(fontManager.findfont(prop, fontext='afm'))) text = t.get_text() l,b,w,h = font.get_str_bbox(text) fontsize = prop.get_size_in_points() w *= 0.001*fontsize h *= 0.001*fontsize halign = t.get_horizontalalignment() valign = t.get_verticalalignment() if t.get_rotation()=='vertical': w, h = h, w if halign=='center': offsetx = w/2 elif halign=='right': offsetx = 0 else: offsetx = w if valign=='center': offsety = h/2 elif valign=='top': offsety = h else: offsety = 0 else: if halign=='center': offsetx = -w/2 elif halign=='right': offsetx = -w else: offsetx = 0 if valign=='center': offsety = h/2 elif valign=='top': offsety = h else: offsety = 0 return (offsetx, offsety) def draw_arc(self, gc, rgbFace, x, y, width, height, angle1, angle2): pass def draw_line(self, gc, x1, y1, x2, y2): """ Draw a single line from x1,y1 to x2,y2 """ type = '<path ' details = ' d=\"M %f,%f L %f,%f\" ' % (x1,self.height-y1,x2,self.height-y2) self._draw_svg(type, details, gc, None) def draw_lines(self, gc, x, y): """ x and y are equal length arrays, draw lines connecting each point in x, y """ if len(x)==0: return if len(x)!=len(y): error_msg_svg('x and y must be the same length') type = '<path ' details =' d=\"M %f,%f ' % (x[0], self.height-y[0]) for tup in zip(x[1:], self.height-y[1:]): details += 'L %f,%f ' % tup details += '\" ' self._draw_svg(type, details, gc, None) def draw_rectangle(self, gc, rgbFace, x, y, width, height): rgbhex='fill:#' for c in rgbFace: rgbhex += hex(int(c*255))[2:] type = '<rect ' details = """ width=\"%f\" height=\"%f\" x=\"%f\" y=\"%f\" """ % (width, height, x, self.height-y-height) self._draw_svg(type, details, gc, rgbFace) def draw_polygon(self, gc, rgbFace, points): type = '<polygon ' details = 'points =\"' for point in points: details += '%f,%f ' % (point[0],self.height-point[1]) details += '\"' self._draw_svg(type, details, gc, rgbFace) def draw_text(self, gc, x, y, t): """ draw a Text instance """ prop = t.get_font_properties() font = AFM(file(fontManager.findfont(prop, fontext='afm'))) text = t.get_text() l,b,w,h = font.get_str_bbox(text) if text=='': return ox, oy = self.compute_text_offsets(t) if t.get_rotation()=='vertical': x = x+ox y = self.height - y+oy-0.001*h else: x = x+ox y = self.height - y+oy thetext = '%s' % text fontname = font.get_fontname() fontsize = prop.get_size_in_points() if t.get_rotation()=='vertical': rotate = '90 rotate' else: rotate = '' svg = '<text ' svg += """\ x=\"%f\" y=\"%f\" style=\"font-size:%f;stroke-width:1.0000000pt;font-family:helvetica;\" > """ % (x,y,float(fontsize)) svg += thetext+' </text>' self.draw_rawsvg(svg) def get_svg(self): return self._svgwriter.getvalue() def finish(self): self._svgwriter.write('</svg>') def new_gc(self): return GraphicsContextSVG() def get_text_extent(self, t): x, y = t.get_xy_display() prop = t.get_font_properties() font = AFM(file(fontManager.findfont(prop, fontext='afm'))) text = t.get_text() l,b,w,h = font.get_str_bbox(text) fontsize = prop.get_size_in_points() l *= 0.001*fontsize b *= 0.001*fontsize w *= 0.001*fontsize h *= 0.001*fontsize ox, oy = self.compute_text_offsets(t) left = x+ox+l bottom = y-oy+b if t.get_rotation()=='vertical': w,h = h,w return Bound2D(left, bottom, w, h) def _draw_svg(self, type, details, gc, rgbFace): svg=type if rgbFace is not None: rgbhex='fill:#' for c in rgbFace: rgbhex += hex(int(c*255))[2:] rgbhex += ';' else: rgbhex='fill:none;' style = self._get_gc_props_svg(gc) svg+=style+rgbhex+ ' \"\n' svg += details svg += ' />\n' self._svgwriter.write(svg) def _get_gc_props_svg(self, gc): color='stroke:'+_rgb_to_hex(gc.get_rgb())+';' linewidth = 'stroke-width:'+repr(gc.get_linewidth())+'pt;' join = 'stroke-linejoin:'+gc.get_joinstyle()+';' cap = 'stroke-linecap:'+gc.get_capstyle()+';' offset, seq = gc.get_dashes() if seq is not None: dashes = 'stroke-dasharray:' for s in seq: dashes += '%d ' % s dashes += ';' dashes += 'stroke-dashoffset:%f;' % offset else: dashes = '' style = 'style=\"'+color+linewidth+join+cap+dashes return style class GraphicsContextSVG(GraphicsContextBase): def set_linestyle(self, style): GraphicsContextBase.set_linestyle(self, style) offset, dashes = self._dashd[style] self.set_dashes(offset, dashes) class FigureCanvasSVG(FigureCanvasBase): def draw(self): pass def print_figure(self, filename, dpi=100, facecolor='w', edgecolor='w', orientation='portrait'): basename, ext = os.path.splitext(filename) if not len(ext): filename += '.svg' self._svgwriter = StringIO() renderer = RendererSVG(self._svgwriter,self.figure.figsize[0]*dpi,self.figure.figsize[1]*dpi) print dpi print (self.figure.figsize[0]*dpi, self.figure.figsize[1]*dpi) self._svgwriter.write(_svgProlog % (renderer.width,renderer.height)) self.figure.draw(renderer) renderer.finish() try: fh = file(filename, 'w') except IOError: error_msg_svg('Could not open %s for writing' % filename) return print >>fh, renderer.get_svg() class FigureManagerSVG(FigureManagerBase): pass FigureManager = FigureManagerSVG error_msg = error_msg_svg _svgProlog = """<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"> <!-- Created with matplotlib (http://matplotlib.sourceforge.net/) --> <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.0\" x=\"0.0000000\" y=\"0.0000000\" width=\"%f\" height=\"%f\" id=\"svg1\"> """ |