示例#1
0
class AnnotationBbox(martist.Artist, _AnnotationBase):
    """
    Annotation-like class, but with offsetbox instead of Text.
    """

    zorder = 3

    def __str__(self):
        return "AnnotationBbox(%g,%g)"%(self.xy[0],self.xy[1])
    @docstring.dedent_interpd
    def __init__(self, offsetbox, xy,
                 xybox=None,
                 xycoords='data',
                 boxcoords=None,
                 frameon=True, pad=0.4, # BboxPatch
                 annotation_clip=None,
                 box_alignment=(0.5, 0.5),
                 bboxprops=None,
                 arrowprops=None,
                 fontsize=None,
                 **kwargs):
        """
        *offsetbox* : OffsetBox instance

        *xycoords* : same as Annotation but can be a tuple of two
           strings which are interpreted as x and y coordinates.

        *boxcoords* : similar to textcoords as Annotation but can be a
           tuple of two strings which are interpreted as x and y
           coordinates.

        *box_alignment* : a tuple of two floats for a vertical and
           horizontal alignment of the offset box w.r.t. the *boxcoords*.
           The lower-left corner is (0.0) and upper-right corner is (1.1).

        other parameters are identical to that of Annotation.
        """
        self.offsetbox = offsetbox

        self.arrowprops = arrowprops

        self.set_fontsize(fontsize)


        if arrowprops is not None:
            self._arrow_relpos = self.arrowprops.pop("relpos", (0.5, 0.5))
            self.arrow_patch = FancyArrowPatch((0, 0), (1,1),
                                               **self.arrowprops)
        else:
            self._arrow_relpos = None
            self.arrow_patch = None

        _AnnotationBase.__init__(self,
                                 xy, xytext=xybox,
                                 xycoords=xycoords, textcoords=boxcoords,
                                 annotation_clip=annotation_clip)

        martist.Artist.__init__(self, **kwargs)

        #self._fw, self._fh = 0., 0. # for alignment
        self._box_alignment = box_alignment

        # frame
        self.patch = FancyBboxPatch(
            xy=(0.0, 0.0), width=1., height=1.,
            facecolor='w', edgecolor='k',
            mutation_scale=self.prop.get_size_in_points(),
            snap=True
            )
        self.patch.set_boxstyle("square",pad=pad)
        if bboxprops:
            self.patch.set(**bboxprops)
        self._drawFrame =  frameon


    def contains(self,event):
        t,tinfo = self.offsetbox.contains(event)
        #if self.arrow_patch is not None:
        #    a,ainfo=self.arrow_patch.contains(event)
        #    t = t or a

        # self.arrow_patch is currently not checked as this can be a line - JJ

        return t,tinfo


    def get_children(self):
        children = [self.offsetbox, self.patch]
        if self.arrow_patch:
            children.append(self.arrow_patch)
        return children

    def set_figure(self, fig):

        if self.arrow_patch is not None:
            self.arrow_patch.set_figure(fig)
        self.offsetbox.set_figure(fig)
        martist.Artist.set_figure(self, fig)

    def set_fontsize(self, s=None):
        """
        set fontsize in points
        """
        if s is None:
            s = rcParams["legend.fontsize"]

        self.prop=FontProperties(size=s)

    def get_fontsize(self, s=None):
        """
        return fontsize in points
        """
        return self.prop.get_size_in_points()

    def update_positions(self, renderer):
        "Update the pixel positions of the annotated point and the text."
        xy_pixel = self._get_position_xy(renderer)
        self._update_position_xybox(renderer, xy_pixel)

        mutation_scale = renderer.points_to_pixels(self.get_fontsize())
        self.patch.set_mutation_scale(mutation_scale)

        if self.arrow_patch:
            self.arrow_patch.set_mutation_scale(mutation_scale)


    def _update_position_xybox(self, renderer, xy_pixel):
        "Update the pixel positions of the annotation text and the arrow patch."

        x, y = self.xytext
        if isinstance(self.textcoords, tuple):
            xcoord, ycoord = self.textcoords
            x1, y1 = self._get_xy(renderer, x, y, xcoord)
            x2, y2 = self._get_xy(renderer, x, y, ycoord)
            ox0, oy0 = x1, y2
        else:
            ox0, oy0 = self._get_xy(renderer, x, y, self.textcoords)

        w, h, xd, yd = self.offsetbox.get_extent(renderer)

        _fw, _fh = self._box_alignment
        self.offsetbox.set_offset((ox0-_fw*w+xd, oy0-_fh*h+yd))

        # update patch position
        bbox = self.offsetbox.get_window_extent(renderer)
        #self.offsetbox.set_offset((ox0-_fw*w, oy0-_fh*h))
        self.patch.set_bounds(bbox.x0, bbox.y0,
                              bbox.width, bbox.height)

        x, y = xy_pixel

        ox1, oy1 = x, y

        if self.arrowprops:
            x0, y0 = x, y

            d = self.arrowprops.copy()

            # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key.

            # adjust the starting point of the arrow relative to
            # the textbox.
            # TODO : Rotation needs to be accounted.
            relpos = self._arrow_relpos

            ox0 = bbox.x0 + bbox.width * relpos[0]
            oy0 = bbox.y0 + bbox.height * relpos[1]

            # The arrow will be drawn from (ox0, oy0) to (ox1,
            # oy1). It will be first clipped by patchA and patchB.
            # Then it will be shrinked by shirnkA and shrinkB
            # (in points). If patch A is not set, self.bbox_patch
            # is used.

            self.arrow_patch.set_positions((ox0, oy0), (ox1,oy1))
            fs = self.prop.get_size_in_points()
            mutation_scale = d.pop("mutation_scale", fs)
            mutation_scale = renderer.points_to_pixels(mutation_scale)
            self.arrow_patch.set_mutation_scale(mutation_scale)

            patchA = d.pop("patchA", self.patch)
            self.arrow_patch.set_patchA(patchA)



    def draw(self, renderer):
        """
        Draw the :class:`Annotation` object to the given *renderer*.
        """

        if renderer is not None:
            self._renderer = renderer
        if not self.get_visible(): return

        xy_pixel = self._get_position_xy(renderer)

        if not self._check_xy(renderer, xy_pixel):
            return

        self.update_positions(renderer)

        if self.arrow_patch is not None:
            if self.arrow_patch.figure is None and self.figure is not None:
                self.arrow_patch.figure = self.figure
            self.arrow_patch.draw(renderer)

        if self._drawFrame:
            self.patch.draw(renderer)

        self.offsetbox.draw(renderer)
示例#2
0
class AnnotationBbox(martist.Artist, _AnnotationBase):
    """
    Annotation-like class, but with offsetbox instead of Text.
    """

    zorder = 3

    def __str__(self):
        return "AnnotationBbox(%g,%g)" % (self.xy[0], self.xy[1])

    @docstring.dedent_interpd
    def __init__(
            self,
            offsetbox,
            xy,
            xybox=None,
            xycoords='data',
            boxcoords=None,
            frameon=True,
            pad=0.4,  # BboxPatch
            annotation_clip=None,
            box_alignment=(0.5, 0.5),
            bboxprops=None,
            arrowprops=None,
            fontsize=None,
            **kwargs):
        """
        *offsetbox* : OffsetBox instance

        *xycoords* : same as Annotation but can be a tuple of two
           strings which are interpreted as x and y coordinates.

        *boxcoords* : similar to textcoords as Annotation but can be a
           tuple of two strings which are interpreted as x and y
           coordinates.

        *box_alignment* : a tuple of two floats for a vertical and
           horizontal alignment of the offset box w.r.t. the *boxcoords*.
           The lower-left corner is (0.0) and upper-right corner is (1.1).

        other parameters are identical to that of Annotation.
        """
        self.offsetbox = offsetbox

        self.arrowprops = arrowprops

        self.set_fontsize(fontsize)

        if arrowprops is not None:
            self._arrow_relpos = self.arrowprops.pop("relpos", (0.5, 0.5))
            self.arrow_patch = FancyArrowPatch((0, 0), (1, 1),
                                               **self.arrowprops)
        else:
            self._arrow_relpos = None
            self.arrow_patch = None

        _AnnotationBase.__init__(self,
                                 xy,
                                 xytext=xybox,
                                 xycoords=xycoords,
                                 textcoords=boxcoords,
                                 annotation_clip=annotation_clip)

        martist.Artist.__init__(self, **kwargs)

        #self._fw, self._fh = 0., 0. # for alignment
        self._box_alignment = box_alignment

        # frame
        self.patch = FancyBboxPatch(
            xy=(0.0, 0.0),
            width=1.,
            height=1.,
            facecolor='w',
            edgecolor='k',
            mutation_scale=self.prop.get_size_in_points(),
            snap=True)
        self.patch.set_boxstyle("square", pad=pad)
        if bboxprops:
            self.patch.set(**bboxprops)
        self._drawFrame = frameon

    def contains(self, event):
        t, tinfo = self.offsetbox.contains(event)
        #if self.arrow_patch is not None:
        #    a,ainfo=self.arrow_patch.contains(event)
        #    t = t or a

        # self.arrow_patch is currently not checked as this can be a line - JJ

        return t, tinfo

    def get_children(self):
        children = [self.offsetbox, self.patch]
        if self.arrow_patch:
            children.append(self.arrow_patch)
        return children

    def set_figure(self, fig):

        if self.arrow_patch is not None:
            self.arrow_patch.set_figure(fig)
        self.offsetbox.set_figure(fig)
        martist.Artist.set_figure(self, fig)

    def set_fontsize(self, s=None):
        """
        set fontsize in points
        """
        if s is None:
            s = rcParams["legend.fontsize"]

        self.prop = FontProperties(size=s)

    def get_fontsize(self, s=None):
        """
        return fontsize in points
        """
        return self.prop.get_size_in_points()

    def update_positions(self, renderer):
        "Update the pixel positions of the annotated point and the text."
        xy_pixel = self._get_position_xy(renderer)
        self._update_position_xybox(renderer, xy_pixel)

        mutation_scale = renderer.points_to_pixels(self.get_fontsize())
        self.patch.set_mutation_scale(mutation_scale)

        if self.arrow_patch:
            self.arrow_patch.set_mutation_scale(mutation_scale)

    def _update_position_xybox(self, renderer, xy_pixel):
        "Update the pixel positions of the annotation text and the arrow patch."

        x, y = self.xytext
        if isinstance(self.textcoords, tuple):
            xcoord, ycoord = self.textcoords
            x1, y1 = self._get_xy(renderer, x, y, xcoord)
            x2, y2 = self._get_xy(renderer, x, y, ycoord)
            ox0, oy0 = x1, y2
        else:
            ox0, oy0 = self._get_xy(renderer, x, y, self.textcoords)

        w, h, xd, yd = self.offsetbox.get_extent(renderer)

        _fw, _fh = self._box_alignment
        self.offsetbox.set_offset((ox0 - _fw * w + xd, oy0 - _fh * h + yd))

        # update patch position
        bbox = self.offsetbox.get_window_extent(renderer)
        #self.offsetbox.set_offset((ox0-_fw*w, oy0-_fh*h))
        self.patch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height)

        x, y = xy_pixel

        ox1, oy1 = x, y

        if self.arrowprops:
            x0, y0 = x, y

            d = self.arrowprops.copy()

            # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key.

            # adjust the starting point of the arrow relative to
            # the textbox.
            # TODO : Rotation needs to be accounted.
            relpos = self._arrow_relpos

            ox0 = bbox.x0 + bbox.width * relpos[0]
            oy0 = bbox.y0 + bbox.height * relpos[1]

            # The arrow will be drawn from (ox0, oy0) to (ox1,
            # oy1). It will be first clipped by patchA and patchB.
            # Then it will be shrinked by shirnkA and shrinkB
            # (in points). If patch A is not set, self.bbox_patch
            # is used.

            self.arrow_patch.set_positions((ox0, oy0), (ox1, oy1))
            fs = self.prop.get_size_in_points()
            mutation_scale = d.pop("mutation_scale", fs)
            mutation_scale = renderer.points_to_pixels(mutation_scale)
            self.arrow_patch.set_mutation_scale(mutation_scale)

            patchA = d.pop("patchA", self.patch)
            self.arrow_patch.set_patchA(patchA)

    def draw(self, renderer):
        """
        Draw the :class:`Annotation` object to the given *renderer*.
        """

        if renderer is not None:
            self._renderer = renderer
        if not self.get_visible(): return

        xy_pixel = self._get_position_xy(renderer)

        if not self._check_xy(renderer, xy_pixel):
            return

        self.update_positions(renderer)

        if self.arrow_patch is not None:
            if self.arrow_patch.figure is None and self.figure is not None:
                self.arrow_patch.figure = self.figure
            self.arrow_patch.draw(renderer)

        if self._drawFrame:
            self.patch.draw(renderer)

        self.offsetbox.draw(renderer)