You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			270 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			270 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
The inset module defines the InsetIndicator class, which draws the rectangle and
 | 
						|
connectors required for `.Axes.indicate_inset` and `.Axes.indicate_inset_zoom`.
 | 
						|
"""
 | 
						|
 | 
						|
from . import _api, artist, transforms
 | 
						|
from matplotlib.patches import ConnectionPatch, PathPatch, Rectangle
 | 
						|
from matplotlib.path import Path
 | 
						|
 | 
						|
 | 
						|
_shared_properties = ('alpha', 'edgecolor', 'linestyle', 'linewidth')
 | 
						|
 | 
						|
 | 
						|
class InsetIndicator(artist.Artist):
 | 
						|
    """
 | 
						|
    An artist to highlight an area of interest.
 | 
						|
 | 
						|
    An inset indicator is a rectangle on the plot at the position indicated by
 | 
						|
    *bounds* that optionally has lines that connect the rectangle to an inset
 | 
						|
    Axes (`.Axes.inset_axes`).
 | 
						|
 | 
						|
    .. versionadded:: 3.10
 | 
						|
    """
 | 
						|
    zorder = 4.99
 | 
						|
 | 
						|
    def __init__(self, bounds=None, inset_ax=None, zorder=None, **kwargs):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        bounds : [x0, y0, width, height], optional
 | 
						|
            Lower-left corner of rectangle to be marked, and its width
 | 
						|
            and height.  If not set, the bounds will be calculated from the
 | 
						|
            data limits of inset_ax, which must be supplied.
 | 
						|
 | 
						|
        inset_ax : `~.axes.Axes`, optional
 | 
						|
            An optional inset Axes to draw connecting lines to.  Two lines are
 | 
						|
            drawn connecting the indicator box to the inset Axes on corners
 | 
						|
            chosen so as to not overlap with the indicator box.
 | 
						|
 | 
						|
        zorder : float, default: 4.99
 | 
						|
            Drawing order of the rectangle and connector lines.  The default,
 | 
						|
            4.99, is just below the default level of inset Axes.
 | 
						|
 | 
						|
        **kwargs
 | 
						|
            Other keyword arguments are passed on to the `.Rectangle` patch.
 | 
						|
        """
 | 
						|
        if bounds is None and inset_ax is None:
 | 
						|
            raise ValueError("At least one of bounds or inset_ax must be supplied")
 | 
						|
 | 
						|
        self._inset_ax = inset_ax
 | 
						|
 | 
						|
        if bounds is None:
 | 
						|
            # Work out bounds from inset_ax
 | 
						|
            self._auto_update_bounds = True
 | 
						|
            bounds = self._bounds_from_inset_ax()
 | 
						|
        else:
 | 
						|
            self._auto_update_bounds = False
 | 
						|
 | 
						|
        x, y, width, height = bounds
 | 
						|
 | 
						|
        self._rectangle = Rectangle((x, y), width, height, clip_on=False, **kwargs)
 | 
						|
 | 
						|
        # Connector positions cannot be calculated till the artist has been added
 | 
						|
        # to an axes, so just make an empty list for now.
 | 
						|
        self._connectors = []
 | 
						|
 | 
						|
        super().__init__()
 | 
						|
        self.set_zorder(zorder)
 | 
						|
 | 
						|
        # Initial style properties for the artist should match the rectangle.
 | 
						|
        for prop in _shared_properties:
 | 
						|
            setattr(self, f'_{prop}', artist.getp(self._rectangle, prop))
 | 
						|
 | 
						|
    def _shared_setter(self, prop, val):
 | 
						|
        """
 | 
						|
        Helper function to set the same style property on the artist and its children.
 | 
						|
        """
 | 
						|
        setattr(self, f'_{prop}', val)
 | 
						|
 | 
						|
        artist.setp([self._rectangle, *self._connectors], prop, val)
 | 
						|
 | 
						|
    def set_alpha(self, alpha):
 | 
						|
        # docstring inherited
 | 
						|
        self._shared_setter('alpha', alpha)
 | 
						|
 | 
						|
    def set_edgecolor(self, color):
 | 
						|
        """
 | 
						|
        Set the edge color of the rectangle and the connectors.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        color : :mpltype:`color` or None
 | 
						|
        """
 | 
						|
        self._shared_setter('edgecolor', color)
 | 
						|
 | 
						|
    def set_color(self, c):
 | 
						|
        """
 | 
						|
        Set the edgecolor of the rectangle and the connectors, and the
 | 
						|
        facecolor for the rectangle.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        c : :mpltype:`color`
 | 
						|
        """
 | 
						|
        self._shared_setter('edgecolor', c)
 | 
						|
        self._shared_setter('facecolor', c)
 | 
						|
 | 
						|
    def set_linewidth(self, w):
 | 
						|
        """
 | 
						|
        Set the linewidth in points of the rectangle and the connectors.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        w : float or None
 | 
						|
        """
 | 
						|
        self._shared_setter('linewidth', w)
 | 
						|
 | 
						|
    def set_linestyle(self, ls):
 | 
						|
        """
 | 
						|
        Set the linestyle of the rectangle and the connectors.
 | 
						|
 | 
						|
        ==========================================  =================
 | 
						|
        linestyle                                   description
 | 
						|
        ==========================================  =================
 | 
						|
        ``'-'`` or ``'solid'``                      solid line
 | 
						|
        ``'--'`` or ``'dashed'``                    dashed line
 | 
						|
        ``'-.'`` or ``'dashdot'``                   dash-dotted line
 | 
						|
        ``':'`` or ``'dotted'``                     dotted line
 | 
						|
        ``'none'``, ``'None'``, ``' '``, or ``''``  draw nothing
 | 
						|
        ==========================================  =================
 | 
						|
 | 
						|
        Alternatively a dash tuple of the following form can be provided::
 | 
						|
 | 
						|
            (offset, onoffseq)
 | 
						|
 | 
						|
        where ``onoffseq`` is an even length tuple of on and off ink in points.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
 | 
						|
            The line style.
 | 
						|
        """
 | 
						|
        self._shared_setter('linestyle', ls)
 | 
						|
 | 
						|
    def _bounds_from_inset_ax(self):
 | 
						|
        xlim = self._inset_ax.get_xlim()
 | 
						|
        ylim = self._inset_ax.get_ylim()
 | 
						|
        return (xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0])
 | 
						|
 | 
						|
    def _update_connectors(self):
 | 
						|
        (x, y) = self._rectangle.get_xy()
 | 
						|
        width = self._rectangle.get_width()
 | 
						|
        height = self._rectangle.get_height()
 | 
						|
 | 
						|
        existing_connectors = self._connectors or [None] * 4
 | 
						|
 | 
						|
        # connect the inset_axes to the rectangle
 | 
						|
        for xy_inset_ax, existing in zip([(0, 0), (0, 1), (1, 0), (1, 1)],
 | 
						|
                                         existing_connectors):
 | 
						|
            # inset_ax positions are in axes coordinates
 | 
						|
            # The 0, 1 values define the four edges if the inset_ax
 | 
						|
            # lower_left, upper_left, lower_right upper_right.
 | 
						|
            ex, ey = xy_inset_ax
 | 
						|
            if self.axes.xaxis.get_inverted():
 | 
						|
                ex = 1 - ex
 | 
						|
            if self.axes.yaxis.get_inverted():
 | 
						|
                ey = 1 - ey
 | 
						|
            xy_data = x + ex * width, y + ey * height
 | 
						|
            if existing is None:
 | 
						|
                # Create new connection patch with styles inherited from the
 | 
						|
                # parent artist.
 | 
						|
                p = ConnectionPatch(
 | 
						|
                    xyA=xy_inset_ax, coordsA=self._inset_ax.transAxes,
 | 
						|
                    xyB=xy_data, coordsB=self.axes.transData,
 | 
						|
                    arrowstyle="-",
 | 
						|
                    edgecolor=self._edgecolor, alpha=self.get_alpha(),
 | 
						|
                    linestyle=self._linestyle, linewidth=self._linewidth)
 | 
						|
                self._connectors.append(p)
 | 
						|
            else:
 | 
						|
                # Only update positioning of existing connection patch.  We
 | 
						|
                # do not want to override any style settings made by the user.
 | 
						|
                existing.xy1 = xy_inset_ax
 | 
						|
                existing.xy2 = xy_data
 | 
						|
                existing.coords1 = self._inset_ax.transAxes
 | 
						|
                existing.coords2 = self.axes.transData
 | 
						|
 | 
						|
        if existing is None:
 | 
						|
            # decide which two of the lines to keep visible....
 | 
						|
            pos = self._inset_ax.get_position()
 | 
						|
            bboxins = pos.transformed(self.get_figure(root=False).transSubfigure)
 | 
						|
            rectbbox = transforms.Bbox.from_bounds(x, y, width, height).transformed(
 | 
						|
                self._rectangle.get_transform())
 | 
						|
            x0 = rectbbox.x0 < bboxins.x0
 | 
						|
            x1 = rectbbox.x1 < bboxins.x1
 | 
						|
            y0 = rectbbox.y0 < bboxins.y0
 | 
						|
            y1 = rectbbox.y1 < bboxins.y1
 | 
						|
            self._connectors[0].set_visible(x0 ^ y0)
 | 
						|
            self._connectors[1].set_visible(x0 == y1)
 | 
						|
            self._connectors[2].set_visible(x1 == y0)
 | 
						|
            self._connectors[3].set_visible(x1 ^ y1)
 | 
						|
 | 
						|
    @property
 | 
						|
    def rectangle(self):
 | 
						|
        """`.Rectangle`: the indicator frame."""
 | 
						|
        return self._rectangle
 | 
						|
 | 
						|
    @property
 | 
						|
    def connectors(self):
 | 
						|
        """
 | 
						|
        4-tuple of `.patches.ConnectionPatch` or None
 | 
						|
            The four connector lines connecting to (lower_left, upper_left,
 | 
						|
            lower_right upper_right) corners of *inset_ax*. Two lines are
 | 
						|
            set with visibility to *False*,  but the user can set the
 | 
						|
            visibility to True if the automatic choice is not deemed correct.
 | 
						|
        """
 | 
						|
        if self._inset_ax is None:
 | 
						|
            return
 | 
						|
 | 
						|
        if self._auto_update_bounds:
 | 
						|
            self._rectangle.set_bounds(self._bounds_from_inset_ax())
 | 
						|
        self._update_connectors()
 | 
						|
        return tuple(self._connectors)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        conn_same_style = []
 | 
						|
 | 
						|
        # Figure out which connectors have the same style as the box, so should
 | 
						|
        # be drawn as a single path.
 | 
						|
        for conn in self.connectors or []:
 | 
						|
            if conn.get_visible():
 | 
						|
                drawn = False
 | 
						|
                for s in _shared_properties:
 | 
						|
                    if artist.getp(self._rectangle, s) != artist.getp(conn, s):
 | 
						|
                        # Draw this connector by itself
 | 
						|
                        conn.draw(renderer)
 | 
						|
                        drawn = True
 | 
						|
                        break
 | 
						|
 | 
						|
                if not drawn:
 | 
						|
                    # Connector has same style as box.
 | 
						|
                    conn_same_style.append(conn)
 | 
						|
 | 
						|
        if conn_same_style:
 | 
						|
            # Since at least one connector has the same style as the rectangle, draw
 | 
						|
            # them as a compound path.
 | 
						|
            artists = [self._rectangle] + conn_same_style
 | 
						|
            paths = [a.get_transform().transform_path(a.get_path()) for a in artists]
 | 
						|
            path = Path.make_compound_path(*paths)
 | 
						|
 | 
						|
            # Create a temporary patch to draw the path.
 | 
						|
            p = PathPatch(path)
 | 
						|
            p.update_from(self._rectangle)
 | 
						|
            p.set_transform(transforms.IdentityTransform())
 | 
						|
            p.draw(renderer)
 | 
						|
 | 
						|
            return
 | 
						|
 | 
						|
        # Just draw the rectangle
 | 
						|
        self._rectangle.draw(renderer)
 | 
						|
 | 
						|
    @_api.deprecated(
 | 
						|
        '3.10',
 | 
						|
        message=('Since Matplotlib 3.10 indicate_inset_[zoom] returns a single '
 | 
						|
                 'InsetIndicator artist with a rectangle property and a connectors '
 | 
						|
                 'property.  From 3.12 it will no longer be possible to unpack the '
 | 
						|
                 'return value into two elements.'))
 | 
						|
    def __getitem__(self, key):
 | 
						|
        return [self._rectangle, self.connectors][key]
 |