1010from matplotlib .font_manager import findfont , FontProperties
1111from matplotlib .ft2font import FT2Font , KERNING_DEFAULT , LOAD_NO_HINTING
1212from matplotlib .mathtext import MathTextParser
13+ from matplotlib .path import Path
14+ from matplotlib .transforms import Affine2D
1315
1416from xml .sax .saxutils import escape as escape_xml_text
1517
@@ -39,6 +41,7 @@ def __init__(self, width, height, svgwriter, basename=None):
3941 self ._imaged = {}
4042 self ._clipd = {}
4143 self ._char_defs = {}
44+ self ._markers = {}
4245 self .mathtext_parser = MathTextParser ('SVG' )
4346 self .fontd = {}
4447 svgwriter .write (svgProlog % (width ,height ,width ,height ))
@@ -74,7 +77,7 @@ def _get_style(self, gc, rgbFace):
7477 if rgbFace is None :
7578 fill = 'none'
7679 else :
77- fill = rgb2hex (rgbFace )
80+ fill = rgb2hex (rgbFace [: 3 ] )
7881
7982 offset , seq = gc .get_dashes ()
8083 if seq is None :
@@ -103,28 +106,38 @@ def _get_style(self, gc, rgbFace):
103106
104107 def _get_gc_clip_svg (self , gc ):
105108 cliprect = gc .get_clip_rectangle ()
106- if cliprect is None :
107- return '' , None
108- else :
109- # See if we've already seen this clip rectangle
110- key = hash (cliprect )
111- if self ._clipd .get (key ) is None : # If not, store a new clipPath
112- self ._clipd [key ] = cliprect
113- x , y , w , h = cliprect
109+ clippath , clippath_trans = gc .get_clip_path ()
110+ if clippath is not None :
111+ pathkey = (hash (clippath ), hash (clippath_trans ))
112+ path = ''
113+ if self ._clipd .get (pathkey ) is None :
114+ self ._clipd [pathkey ] = clippath
115+ path_data = self ._convert_path (clippath , clippath_trans )
116+ path = """\
117+ <defs>
118+ <clipPath id="%(pathkey)s">
119+ <path d="%(path_data)s"/>
120+ </clipPath>
121+ </defs>
122+ """ % locals ()
123+ return path , pathkey
124+ elif cliprect is not None :
125+ rectkey = hash (cliprect )
126+ box = ''
127+ if self ._clipd .get (rectkey ) is None :
128+ self ._clipd [rectkey ] = cliprect
129+ x , y , w , h = cliprect .bounds
114130 y = self .height - (y + h )
115- style = "stroke: gray; fill: none;"
116131 box = """\
117132 <defs>
118- <clipPath id="%(key)s">
119- <rect x="%(x)s" y="%(y)s" width="%(w)s" height="%(h)s"
120- style="%(style)s"/>
133+ <clipPath id="%(rectkey)s">
134+ <rect x="%(x)s" y="%(y)s" width="%(w)s" height="%(h)s"/>
121135 </clipPath>
122136</defs>
123137""" % locals ()
124- return box , key
125- else :
126- # return id of previously defined clipPath
127- return '' , key
138+ return box , rectkey
139+
140+ return '' , None
128141
129142 def open_group (self , s ):
130143 self ._groupd [s ] = self ._groupd .get (s ,0 ) + 1
@@ -133,20 +146,66 @@ def open_group(self, s):
133146 def close_group (self , s ):
134147 self ._svgwriter .write ('</g>\n ' )
135148
136- def draw_arc (self , gc , rgbFace , x , y , width , height , angle1 , angle2 , rotation ):
137- """
138- Ignores angles for now
139- """
140- details = 'cx="%s" cy="%s" rx="%s" ry="%s" transform="rotate(%1.1f %s %s)"' % \
141- (x , self .height - y , width / 2.0 , height / 2.0 , - rotation , x , self .height - y )
142- self ._draw_svg_element ('ellipse' , details , gc , rgbFace )
143-
144149 def option_image_nocomposite (self ):
145150 """
146151 if svg.image_noscale is True, compositing multiple images into one is prohibited
147152 """
148153 return rcParams ['svg.image_noscale' ]
149154
155+ _path_commands = {
156+ Path .MOVETO : 'M%s %s' ,
157+ Path .LINETO : 'L%s %s' ,
158+ Path .CURVE3 : 'Q%s %s %s %s' ,
159+ Path .CURVE4 : 'C%s %s %s %s %s %s'
160+ }
161+
162+ def _make_flip_transform (self , transform ):
163+ return (transform +
164+ Affine2D ()
165+ .scale (1.0 , - 1.0 )
166+ .translate (0.0 , self .height ))
167+
168+ def _convert_path (self , path , transform ):
169+ tpath = transform .transform_path (path )
170+
171+ path_data = []
172+ appender = path_data .append
173+ path_commands = self ._path_commands
174+ currpos = 0
175+ for points , code in tpath .iter_segments ():
176+ if code == Path .CLOSEPOLY :
177+ segment = 'z'
178+ else :
179+ segment = path_commands [code ] % tuple (points )
180+
181+ if currpos + len (segment ) > 75 :
182+ appender ("\n " )
183+ currpos = 0
184+ appender (segment )
185+ currpos += len (segment )
186+ return '' .join (path_data )
187+
188+ def draw_path (self , gc , path , transform , rgbFace = None ):
189+ trans_and_flip = self ._make_flip_transform (transform )
190+ path_data = self ._convert_path (path , trans_and_flip )
191+ self ._draw_svg_element ('path' , 'd="%s"' % path_data , gc , rgbFace )
192+
193+ def draw_markers (self , gc , marker_path , marker_trans , path , trans , rgbFace = None ):
194+ write = self ._svgwriter .write
195+
196+ key = self ._convert_path (marker_path , marker_trans + Affine2D ().scale (0 , - 1.0 ))
197+ name = self ._markers .get (key )
198+ if name is None :
199+ name = 'm_%x' % len (self ._markers )
200+ write ('<defs><path id="%s" d="%s"/></defs>\n ' % (name , key ))
201+ self ._markers [key ] = name
202+
203+ trans_and_flip = self ._make_flip_transform (trans )
204+ tpath = trans_and_flip .transform_path (path )
205+ for x , y in tpath .vertices :
206+ details = 'xlink:href="#%s" transform="translate(%f, %f)"' % (name , x , y )
207+ self ._draw_svg_element ('use' , details , gc , rgbFace )
208+
150209 def draw_image (self , x , y , im , bbox ):
151210 trans = [1 ,0 ,0 ,1 ,0 ,0 ]
152211 transstr = ''
@@ -204,38 +263,6 @@ def draw_image(self, x, y, im, bbox):
204263 'xlink:href="%s" %s/>\n ' % (x / trans [0 ], (self .height - y )/ trans [3 ]- h , w , h , hrefstr , transstr )
205264 )
206265
207- def draw_line (self , gc , x1 , y1 , x2 , y2 ):
208- details = 'd="M%s,%sL%s,%s"' % (x1 , self .height - y1 ,
209- x2 , self .height - y2 )
210- self ._draw_svg_element ('path' , details , gc , None )
211-
212- def draw_lines (self , gc , x , y , transform = None ):
213- if len (x )== 0 : return
214- if len (x )!= len (y ):
215- raise ValueError ('x and y must be the same length' )
216-
217- y = self .height - y
218- details = ['d="M%s,%s' % (x [0 ], y [0 ])]
219- xys = zip (x [1 :], y [1 :])
220- details .extend (['L%s,%s' % tup for tup in xys ])
221- details .append ('"' )
222- details = '' .join (details )
223- self ._draw_svg_element ('path' , details , gc , None )
224-
225- def draw_point (self , gc , x , y ):
226- # result seems to have a hole in it...
227- self .draw_arc (gc , gc .get_rgb (), x , y , 1 , 0 , 0 , 0 , 0 )
228-
229- def draw_polygon (self , gc , rgbFace , points ):
230- details = 'points = "%s"' % ' ' .join (['%s,%s' % (x ,self .height - y )
231- for x , y in points ])
232- self ._draw_svg_element ('polygon' , details , gc , rgbFace )
233-
234- def draw_rectangle (self , gc , rgbFace , x , y , width , height ):
235- details = 'width="%s" height="%s" x="%s" y="%s"' % (width , height , x ,
236- self .height - y - height )
237- self ._draw_svg_element ('rect' , details , gc , rgbFace )
238-
239266 def draw_text (self , gc , x , y , s , prop , angle , ismath ):
240267 if ismath :
241268 self ._draw_mathtext (gc , x , y , s , prop , angle )
@@ -467,7 +494,7 @@ def print_svgz(self, filename, *args, **kwargs):
467494 return self ._print_svg (filename , svgwriter )
468495
469496 def _print_svg (self , filename , svgwriter ):
470- self .figure .dpi . set (72 )
497+ self .figure .set_dpi (72.0 )
471498 width , height = self .figure .get_size_inches ()
472499 w , h = width * 72 , height * 72
473500
0 commit comments