""" Python module containg fixes / additions to mpl library (under construction) """ import numpy as np from matplotlib.text import Text from matplotlib.text import _get_text_metrics_with_cache from matplotlib.transforms import Affine2D from matplotlib.transforms import Bbox from matplotlib import _api def is_string_like(obj): """ Return True if *obj* is of string type used to avoid iterating over members """ # from depricated matplotlib # return isinstance(obj, (six.string_types, np.str_, np.unicode_)) return isinstance(obj, (str, np.str, np.string_, np.str_, np.unicode_)) class MyText(Text): def _get_layout(self, renderer): """ Return the extent (bbox) of the text together with multiple-alignment information. Note that it returns an extent of a rotated text when necessary. """ thisx, thisy = 0.0, 0.0 lines = self._get_wrapped_text().split("\n") # Ensures lines is not empty. ws = [] hs = [] xs = [] ys = [] # Full vertical extent of font, including ascenders and descenders: _, lp_h, lp_d = _get_text_metrics_with_cache( renderer, "lp", self._fontproperties, ismath="TeX" if self.get_usetex() else False, dpi=self.figure.dpi) min_dy = (lp_h - lp_d) * self._linespacing for i, line in enumerate(lines): clean_line, ismath = self._preprocess_math(line) if clean_line: w, h, d = _get_text_metrics_with_cache( renderer, clean_line, self._fontproperties, ismath=ismath, dpi=self.figure.dpi) else: w = h = d = 0 # For multiline text, increase the line spacing when the text # net-height (excluding baseline) is larger than that of a "l" # (e.g., use of superscripts), which seems what TeX does. h = max(h, lp_h) d = max(d, lp_d) ws.append(w) hs.append(h) # Metrics of the last line that are needed later: baseline = (h - d) - thisy if i == 0: # position at baseline thisy = -(h - d) else: # put baseline a good distance from bottom of previous line thisy -= max(min_dy, (h - d) * self._linespacing) xs.append(thisx) # == 0. ys.append(thisy) thisy -= d # Metrics of the last line that are needed later: descent = d # Bounding box definition: width = max(ws) xmin = 0 xmax = width ymax = 0 ymin = ys[-1] - descent # baseline of last line minus its descent # get the rotation matrix M = Affine2D().rotate_deg(self.get_rotation()) # now offset the individual text lines within the box malign = self._get_multialignment() if malign == 'left': offset_layout = [(x, y) for x, y in zip(xs, ys)] elif malign == 'center': offset_layout = [(x + width / 2 - w / 2, y) for x, y, w in zip(xs, ys, ws)] elif malign == 'right': offset_layout = [(x + width - w, y) for x, y, w in zip(xs, ys, ws)] # the corners of the unrotated bounding box corners_horiz = np.array( [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]) # now rotate the bbox corners_rotated = M.transform(corners_horiz) # compute the bounds of the rotated box xmin = corners_rotated[:, 0].min() xmax = corners_rotated[:, 0].max() ymin = corners_rotated[:, 1].min() ymax = corners_rotated[:, 1].max() width = xmax - xmin height = ymax - ymin # Now move the box to the target position offset the display # bbox by alignment halign = self._horizontalalignment valign = self._verticalalignment rotation_mode = self.get_rotation_mode() if rotation_mode != "anchor": # compute the text location in display coords and the offsets # necessary to align the bbox with that location if halign == 'center': offsetx = (xmin + xmax) / 2 elif halign == 'right': offsetx = xmax elif np.isreal(halign): # alex case offsetx = xmin + halign * (xmax - xmin) else: offsetx = xmin if valign == 'center': offsety = (ymin + ymax) / 2 elif valign == 'top': offsety = ymax elif valign == 'baseline': offsety = ymin + descent elif valign == 'center_baseline': offsety = ymin + height - baseline / 2.0 elif np.isreal(valign): # alex case offsety = ymin + valign * (ymax - ymin) else: offsety = ymin else: xmin1, ymin1 = corners_horiz[0] xmax1, ymax1 = corners_horiz[2] if halign == 'center': offsetx = (xmin1 + xmax1) / 2.0 elif halign == 'right': offsetx = xmax1 elif np.isreal(halign): # alex case offsetx = xmin1 + halign * (xmax1 - xmin1) else: offsetx = xmin1 if valign == 'center': offsety = (ymin1 + ymax1) / 2.0 elif valign == 'top': offsety = ymax1 elif valign == 'baseline': offsety = ymax1 - baseline elif valign == 'center_baseline': offsety = ymax1 - baseline / 2.0 elif np.isreal(valign): # alex case offsety = ymin1 + valign * (ymax1 - ymin1) else: offsety = ymin1 offsetx, offsety = M.transform((offsetx, offsety)) 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(offset_layout) - (offsetx, offsety) return bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent def set_horizontalalignment(self, align): """ Set the horizontal alignment relative to the anchor point. See also :doc:`/gallery/text_labels_and_annotations/text_alignment`. Parameters ---------- align : {'left', 'center', 'right'} """ if not np.isreal(align): _api.check_in_list(['center', 'right', 'left'], align=align) self._horizontalalignment = align self.stale = True def set_verticalalignment(self, align): """ Set the vertical alignment relative to the anchor point. See also :doc:`/gallery/text_labels_and_annotations/text_alignment`. Parameters ---------- align : {'bottom', 'baseline', 'center', 'center_baseline', 'top'} """ if not np.isreal(align): _api.check_in_list( ['top', 'bottom', 'center', 'baseline', 'center_baseline'], align=align) self._verticalalignment = align self.stale = True