def set_bbox(self, rectprops): """ Draw a bounding box around self. rectprops are any settable properties for a rectangle, eg facecolor='red', alpha=0.5. t.set_bbox(dict(facecolor='red', alpha=0.5)) If rectprops has "boxstyle" key. A FancyBboxPatch is initialized with rectprops and will be drawn. The mutation scale of the FancyBboxPath is set to the fontsize. ACCEPTS: rectangle prop dict """ # The self._bbox_patch object is created only if rectprops has # boxstyle key. Otherwise, self._bbox will be set to the # rectprops and the bbox will be drawn using bbox_artist # function. This is to keep the backward compatibility. if rectprops is not None and "boxstyle" in rectprops: props = rectprops.copy() boxstyle = props.pop("boxstyle") bbox_transmuter = props.pop("bbox_transmuter", None) self._bbox_patch = FancyBboxPatch((0., 0.), 1., 1., boxstyle=boxstyle, bbox_transmuter=bbox_transmuter, transform=mtransforms.IdentityTransform(), **props) self._bbox = None else: self._bbox_patch = None self._bbox = rectprops
class Text(Artist): """ Handle storing and drawing of text in window or data coordinates. """ zorder = 3 def __str__(self): return "Text(%g,%g,%s)"%(self._y,self._y,repr(self._text)) def __init__(self, x=0, y=0, text='', color=None, # defaults to rc params verticalalignment='bottom', horizontalalignment='left', multialignment=None, fontproperties=None, # defaults to FontProperties() rotation=None, linespacing=None, **kwargs ): """ Create a :class:`~matplotlib.text.Text` instance at *x*, *y* with string *text*. Valid kwargs are %(Text)s """ Artist.__init__(self) self.cached = maxdict(5) self._x, self._y = x, y if color is None: color = rcParams['text.color'] if fontproperties is None: fontproperties=FontProperties() elif is_string_like(fontproperties): fontproperties=FontProperties(fontproperties) self.set_text(text) self.set_color(color) self._verticalalignment = verticalalignment self._horizontalalignment = horizontalalignment self._multialignment = multialignment self._rotation = rotation self._fontproperties = fontproperties self._bbox = None self._bbox_patch = None # a FancyBboxPatch instance self._renderer = None if linespacing is None: linespacing = 1.2 # Maybe use rcParam later. self._linespacing = linespacing self.update(kwargs) #self.set_bbox(dict(pad=0)) def contains(self,mouseevent): """Test whether the mouse event occurred in the patch. In the case of text, a hit is true anywhere in the axis-aligned bounding-box containing the text. Returns True or False. """ if callable(self._contains): return self._contains(self,mouseevent) if not self.get_visible() or self._renderer is None: return False,{} l,b,w,h = self.get_window_extent().bounds r = l+w t = b+h xyverts = (l,b), (l, t), (r, t), (r, b) x, y = mouseevent.x, mouseevent.y inside = nxutils.pnpoly(x, y, xyverts) return inside,{} def _get_xy_display(self): 'get the (possibly unit converted) transformed x, y in display coords' x, y = self.get_position() return self.get_transform().transform_point((x,y)) def _get_multialignment(self): if self._multialignment is not None: return self._multialignment else: return self._horizontalalignment def get_rotation(self): 'return the text angle as float in degrees' return get_rotation(self._rotation) # string_or_number -> number def update_from(self, other): 'Copy properties from other to self' Artist.update_from(self, other) self._color = other._color self._multialignment = other._multialignment self._verticalalignment = other._verticalalignment self._horizontalalignment = other._horizontalalignment self._fontproperties = other._fontproperties.copy() self._rotation = other._rotation self._picker = other._picker self._linespacing = other._linespacing def _get_layout(self, renderer): key = self.get_prop_tup() if key in self.cached: return self.cached[key] horizLayout = [] thisx, thisy = 0.0, 0.0 xmin, ymin = 0.0, 0.0 width, height = 0.0, 0.0 lines = self._text.split('\n') whs = np.zeros((len(lines), 2)) horizLayout = np.zeros((len(lines), 4)) # Find full vertical extent of font, # including ascenders and descenders: tmp, heightt, bl = renderer.get_text_width_height_descent( 'lp', self._fontproperties, ismath=False) offsety = heightt * self._linespacing baseline = None for i, line in enumerate(lines): clean_line, ismath = self.is_math_text(line) w, h, d = renderer.get_text_width_height_descent( clean_line, self._fontproperties, ismath=ismath) if baseline is None: baseline = h - d whs[i] = w, h horizLayout[i] = thisx, thisy, w, h thisy -= offsety width = max(width, w) ymin = horizLayout[-1][1] ymax = horizLayout[0][1] + horizLayout[0][3] height = ymax-ymin xmax = xmin + width # get the rotation matrix M = Affine2D().rotate_deg(self.get_rotation()) offsetLayout = np.zeros((len(lines), 2)) offsetLayout[:] = horizLayout[:, 0:2] # now offset the individual text lines within the box if len(lines)>1: # do the multiline aligment malign = self._get_multialignment() if malign == 'center': offsetLayout[:, 0] += width/2.0 - horizLayout[:, 2] / 2.0 elif malign == 'right': offsetLayout[:, 0] += width - horizLayout[:, 2] # the corners of the unrotated bounding box cornersHoriz = np.array( [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)], np.float_) # now rotate the bbox cornersRotated = M.transform(cornersHoriz) txs = cornersRotated[:, 0] tys = cornersRotated[:, 1] # compute the bounds of the rotated box xmin, xmax = txs.min(), txs.max() ymin, ymax = tys.min(), tys.max() width = xmax - xmin height = ymax - ymin # Now move the box to the targe position offset the display bbox by alignment halign = self._horizontalalignment valign = self._verticalalignment # compute the text location in display coords and the offsets # necessary to align the bbox with that location if halign=='center': offsetx = (xmin + width/2.0) elif halign=='right': offsetx = (xmin + width) else: offsetx = xmin if valign=='center': offsety = (ymin + height/2.0) elif valign=='top': offsety = (ymin + height) elif valign=='baseline': offsety = (ymin + height) - baseline else: offsety = ymin xmin -= offsetx ymin -= offsety bbox = Bbox.from_bounds(xmin, ymin, width, height) # now rotate the positions around the first x,y position xys = M.transform(offsetLayout) xys -= (offsetx, offsety) xs, ys = xys[:, 0], xys[:, 1] ret = bbox, zip(lines, whs, xs, ys) self.cached[key] = ret return ret def set_bbox(self, rectprops): """ Draw a bounding box around self. rectprops are any settable properties for a rectangle, eg facecolor='red', alpha=0.5. t.set_bbox(dict(facecolor='red', alpha=0.5)) If rectprops has "boxstyle" key. A FancyBboxPatch is initialized with rectprops and will be drawn. The mutation scale of the FancyBboxPath is set to the fontsize. ACCEPTS: rectangle prop dict """ # The self._bbox_patch object is created only if rectprops has # boxstyle key. Otherwise, self._bbox will be set to the # rectprops and the bbox will be drawn using bbox_artist # function. This is to keep the backward compatibility. if rectprops is not None and "boxstyle" in rectprops: props = rectprops.copy() boxstyle = props.pop("boxstyle") bbox_transmuter = props.pop("bbox_transmuter", None) self._bbox_patch = FancyBboxPatch((0., 0.), 1., 1., boxstyle=boxstyle, bbox_transmuter=bbox_transmuter, transform=mtransforms.IdentityTransform(), **props) self._bbox = None else: self._bbox_patch = None self._bbox = rectprops def get_bbox_patch(self): """ Return the bbox Patch object. Returns None if the the FancyBboxPatch is not made. """ return self._bbox_patch def update_bbox_position_size(self, renderer): """ Update the location and the size of the bbox. This method should be used when the position and size of the bbox needs to be updated before actually drawing the bbox. """ # For arrow_patch, use textbox as patchA by default. if not isinstance(self.arrow_patch, FancyArrowPatch): return if self._bbox_patch: trans = self.get_transform() # don't use self.get_position here, which refers to text position # in Text, and dash position in TextWithDash: posx = float(self.convert_xunits(self._x)) posy = float(self.convert_yunits(self._y)) posx, posy = trans.transform_point((posx, posy)) x_box, y_box, w_box, h_box = _get_textbox(self, renderer) self._bbox_patch.set_bounds(0., 0., w_box, h_box) theta = self.get_rotation()/180.*math.pi tr = mtransforms.Affine2D().rotate(theta) tr = tr.translate(posx+x_box, posy+y_box) self._bbox_patch.set_transform(tr) fontsize_in_pixel = renderer.points_to_pixels(self.get_size()) self._bbox_patch.set_mutation_scale(fontsize_in_pixel) #self._bbox_patch.draw(renderer) else: props = self._bbox if props is None: props = {} props = props.copy() # don't want to alter the pad externally pad = props.pop('pad', 4) pad = renderer.points_to_pixels(pad) bbox = self.get_window_extent(renderer) l,b,w,h = bbox.bounds l-=pad/2. b-=pad/2. w+=pad h+=pad r = Rectangle(xy=(l,b), width=w, height=h, ) r.set_transform(mtransforms.IdentityTransform()) r.set_clip_on( False ) r.update(props) self.arrow_patch.set_patchA(r) def _draw_bbox(self, renderer, posx, posy): """ Update the location and the size of the bbox (FancyBoxPatch), and draw """ x_box, y_box, w_box, h_box = _get_textbox(self, renderer) self._bbox_patch.set_bounds(0., 0., w_box, h_box) theta = self.get_rotation()/180.*math.pi tr = mtransforms.Affine2D().rotate(theta) tr = tr.translate(posx+x_box, posy+y_box) self._bbox_patch.set_transform(tr) fontsize_in_pixel = renderer.points_to_pixels(self.get_size()) self._bbox_patch.set_mutation_scale(fontsize_in_pixel) self._bbox_patch.draw(renderer) def draw(self, renderer): """ Draws the :class:`Text` object to the given *renderer*. """ if renderer is not None: self._renderer = renderer if not self.get_visible(): return if self._text=='': return bbox, info = self._get_layout(renderer) trans = self.get_transform() # don't use self.get_position here, which refers to text position # in Text, and dash position in TextWithDash: posx = float(self.convert_xunits(self._x)) posy = float(self.convert_yunits(self._y)) posx, posy = trans.transform_point((posx, posy)) canvasw, canvash = renderer.get_canvas_width_height() # draw the FancyBboxPatch if self._bbox_patch: self._draw_bbox(renderer, posx, posy) gc = renderer.new_gc() gc.set_foreground(self._color) gc.set_alpha(self._alpha) gc.set_url(self._url) if self.get_clip_on(): gc.set_clip_rectangle(self.clipbox) if self._bbox: bbox_artist(self, renderer, self._bbox) angle = self.get_rotation() if rcParams['text.usetex']: for line, wh, x, y in info: x = x + posx y = y + posy if renderer.flipy(): y = canvash-y clean_line, ismath = self.is_math_text(line) renderer.draw_tex(gc, x, y, clean_line, self._fontproperties, angle) return for line, wh, x, y in info: x = x + posx y = y + posy if renderer.flipy(): y = canvash-y clean_line, ismath = self.is_math_text(line) renderer.draw_text(gc, x, y, clean_line, self._fontproperties, angle, ismath=ismath) def get_color(self): "Return the color of the text" return self._color def get_fontproperties(self): "Return the :class:`~font_manager.FontProperties` object" return self._fontproperties def get_font_properties(self): 'alias for get_fontproperties' return self.get_fontproperties def get_family(self): "Return the list of font families used for font lookup" return self._fontproperties.get_family() def get_fontfamily(self): 'alias for get_family' return self.get_family() def get_name(self): "Return the font name as string" return self._fontproperties.get_name() def get_style(self): "Return the font style as string" return self._fontproperties.get_style() def get_size(self): "Return the font size as integer" return self._fontproperties.get_size_in_points() def get_variant(self): "Return the font variant as a string" return self._fontproperties.get_variant() def get_fontvariant(self): 'alias for get_variant' return self.get_variant() def get_weight(self): "Get the font weight as string or number" return self._fontproperties.get_weight() def get_fontname(self): 'alias for get_name' return self.get_name() def get_fontstyle(self): 'alias for get_style' return self.get_style() def get_fontsize(self): 'alias for get_size' return self.get_size() def get_fontweight(self): 'alias for get_weight' return self.get_weight() def get_stretch(self): 'Get the font stretch as a string or number' return self._fontproperties.get_stretch() def get_fontstretch(self): 'alias for get_stretch' return self.get_stretch() def get_ha(self): 'alias for get_horizontalalignment' return self.get_horizontalalignment() def get_horizontalalignment(self): """ Return the horizontal alignment as string. Will be one of 'left', 'center' or 'right'. """ return self._horizontalalignment def get_position(self): "Return the position of the text as a tuple (*x*, *y*)" x = float(self.convert_xunits(self._x)) y = float(self.convert_yunits(self._y)) return x, y def get_prop_tup(self): """ Return a hashable tuple of properties. Not intended to be human readable, but useful for backends who want to cache derived information about text (eg layouts) and need to know if the text has changed. """ x, y = self.get_position() return (x, y, self._text, self._color, self._verticalalignment, self._horizontalalignment, hash(self._fontproperties), self._rotation, self.figure.dpi, id(self._renderer), ) def get_text(self): "Get the text as string" return self._text def get_va(self): 'alias for :meth:`getverticalalignment`' return self.get_verticalalignment() def get_verticalalignment(self): """ Return the vertical alignment as string. Will be one of 'top', 'center', 'bottom' or 'baseline'. """ return self._verticalalignment def get_window_extent(self, renderer=None, dpi=None): ''' Return a :class:`~matplotlib.transforms.Bbox` object bounding the text, in display units. In addition to being used internally, this is useful for specifying clickable regions in a png file on a web page. *renderer* defaults to the _renderer attribute of the text object. This is not assigned until the first execution of :meth:`draw`, so you must use this kwarg if you want to call :meth:`get_window_extent` prior to the first :meth:`draw`. For getting web page regions, it is simpler to call the method after saving the figure. *dpi* defaults to self.figure.dpi; the renderer dpi is irrelevant. For the web application, if figure.dpi is not the value used when saving the figure, then the value that was used must be specified as the *dpi* argument. ''' #return _unit_box if not self.get_visible(): return Bbox.unit() if dpi is not None: dpi_orig = self.figure.dpi self.figure.dpi = dpi if self._text == '': tx, ty = self._get_xy_display() return Bbox.from_bounds(tx,ty,0,0) if renderer is not None: self._renderer = renderer if self._renderer is None: raise RuntimeError('Cannot get window extent w/o renderer') bbox, info = self._get_layout(self._renderer) x, y = self.get_position() x, y = self.get_transform().transform_point((x, y)) bbox = bbox.translated(x, y) if dpi is not None: self.figure.dpi = dpi_orig return bbox def set_backgroundcolor(self, color): """ Set the background color of the text by updating the bbox. .. seealso:: :meth:`set_bbox` ACCEPTS: any matplotlib color """ if self._bbox is None: self._bbox = dict(facecolor=color, edgecolor=color) else: self._bbox.update(dict(facecolor=color)) def set_color(self, color): """ Set the foreground color of the text ACCEPTS: any matplotlib color """ # Make sure it is hashable, or get_prop_tup will fail. try: hash(color) except TypeError: color = tuple(color) self._color = color def set_ha(self, align): 'alias for set_horizontalalignment' self.set_horizontalalignment(align) def set_horizontalalignment(self, align): """ Set the horizontal alignment to one of ACCEPTS: [ 'center' | 'right' | 'left' ] """ legal = ('center', 'right', 'left') if align not in legal: raise ValueError('Horizontal alignment must be one of %s' % str(legal)) self._horizontalalignment = align def set_ma(self, align): 'alias for set_verticalalignment' self.set_multialignment(align) def set_multialignment(self, align): """ Set the alignment for multiple lines layout. The layout of the bounding box of all the lines is determined bu the horizontalalignment and verticalalignment properties, but the multiline text within that box can be ACCEPTS: ['left' | 'right' | 'center