12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829 |
- """
- The `.OffsetBox` is a simple container artist. Its child artists are
- meant to be drawn at a relative position to OffsetBox. The [VH]Packer,
- DrawingArea and TextArea are derived from the OffsetBox.
- The [VH]Packer automatically adjust the relative positions of their
- children, which should be instances of the OffsetBox. This is used to
- align similar artists together, e.g., in legend.
- The DrawingArea can contain any Artist as a child. The
- DrawingArea has a fixed width and height. The position of children
- relative to the parent is fixed. The TextArea is contains a single
- Text instance. The width and height of the TextArea instance is the
- width and height of the its child text.
- """
- import numpy as np
- from matplotlib import cbook, docstring, rcParams
- import matplotlib.artist as martist
- import matplotlib.path as mpath
- import matplotlib.text as mtext
- import matplotlib.transforms as mtransforms
- from matplotlib.font_manager import FontProperties
- from matplotlib.image import BboxImage
- from matplotlib.patches import (
- FancyBboxPatch, FancyArrowPatch, bbox_artist as mbbox_artist)
- from matplotlib.text import _AnnotationBase
- from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
- DEBUG = False
- # for debugging use
- def bbox_artist(*args, **kwargs):
- if DEBUG:
- mbbox_artist(*args, **kwargs)
- # _get_packed_offsets() and _get_aligned_offsets() are coded assuming
- # that we are packing boxes horizontally. But same function will be
- # used with vertical packing.
- def _get_packed_offsets(wd_list, total, sep, mode="fixed"):
- """
- Given a list of (width, xdescent) of each boxes, calculate the
- total width and the x-offset positions of each items according to
- *mode*. xdescent is analogous to the usual descent, but along the
- x-direction. xdescent values are currently ignored.
- For simplicity of the description, the terminology used here assumes a
- horizontal layout, but the function works equally for a vertical layout.
- There are three packing modes:
- - 'fixed': The elements are packed tight to the left with a spacing of
- *sep* in between. If *total* is *None* the returned total will be the
- right edge of the last box. A non-*None* total will be passed unchecked
- to the output. In particular this means that right edge of the last
- box may be further to the right than the returned total.
- - 'expand': Distribute the boxes with equal spacing so that the left edge
- of the first box is at 0, and the right edge of the last box is at
- *total*. The parameter *sep* is ignored in this mode. A total of *None*
- is accepted and considered equal to 1. The total is returned unchanged
- (except for the conversion *None* to 1). If the total is smaller than
- the sum of the widths, the laid out boxes will overlap.
- - 'equal': If *total* is given, the total space is divided in N equal
- ranges and each box is left-aligned within its subspace.
- Otherwise (*total* is *None*), *sep* must be provided and each box is
- left-aligned in its subspace of width ``(max(widths) + sep)``. The
- total width is then calculated to be ``N * (max(widths) + sep)``.
- Parameters
- ----------
- wd_list : list of (float, float)
- (width, xdescent) of boxes to be packed.
- total : float or None
- Intended total length. *None* if not used.
- sep : float
- Spacing between boxes.
- mode : {'fixed', 'expand', 'equal'}
- The packing mode.
- Returns
- -------
- total : float
- The total width needed to accommodate the laid out boxes.
- offsets : array of float
- The left offsets of the boxes.
- """
- w_list, d_list = zip(*wd_list)
- # d_list is currently not used.
- if mode == "fixed":
- offsets_ = np.cumsum([0] + [w + sep for w in w_list])
- offsets = offsets_[:-1]
- if total is None:
- total = offsets_[-1] - sep
- return total, offsets
- elif mode == "expand":
- # This is a bit of a hack to avoid a TypeError when *total*
- # is None and used in conjugation with tight layout.
- if total is None:
- total = 1
- if len(w_list) > 1:
- sep = (total - sum(w_list)) / (len(w_list) - 1)
- else:
- sep = 0
- offsets_ = np.cumsum([0] + [w + sep for w in w_list])
- offsets = offsets_[:-1]
- return total, offsets
- elif mode == "equal":
- maxh = max(w_list)
- if total is None:
- if sep is None:
- raise ValueError("total and sep cannot both be None when "
- "using layout mode 'equal'.")
- total = (maxh + sep) * len(w_list)
- else:
- sep = total / len(w_list) - maxh
- offsets = (maxh + sep) * np.arange(len(w_list))
- return total, offsets
- else:
- raise ValueError("Unknown mode : %s" % (mode,))
- def _get_aligned_offsets(hd_list, height, align="baseline"):
- """
- Given a list of (height, descent) of each boxes, align the boxes
- with *align* and calculate the y-offsets of each boxes.
- total width and the offset positions of each items according to
- *mode*. xdescent is analogous to the usual descent, but along the
- x-direction. xdescent values are currently ignored.
- *hd_list* : list of (width, xdescent) of boxes to be aligned.
- *sep* : spacing between boxes
- *height* : Intended total length. None if not used.
- *align* : align mode. 'baseline', 'top', 'bottom', or 'center'.
- """
- if height is None:
- height = max(h for h, d in hd_list)
- if align == "baseline":
- height_descent = max(h - d for h, d in hd_list)
- descent = max(d for h, d in hd_list)
- height = height_descent + descent
- offsets = [0. for h, d in hd_list]
- elif align in ["left", "top"]:
- descent = 0.
- offsets = [d for h, d in hd_list]
- elif align in ["right", "bottom"]:
- descent = 0.
- offsets = [height - h + d for h, d in hd_list]
- elif align == "center":
- descent = 0.
- offsets = [(height - h) * .5 + d for h, d in hd_list]
- else:
- raise ValueError("Unknown Align mode : %s" % (align,))
- return height, descent, offsets
- class OffsetBox(martist.Artist):
- """
- The OffsetBox is a simple container artist. The child artist are meant
- to be drawn at a relative position to its parent.
- """
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # Clipping has not been implemented in the OffesetBox family, so
- # disable the clip flag for consistency. It can always be turned back
- # on to zero effect.
- self.set_clip_on(False)
- self._children = []
- self._offset = (0, 0)
- def set_figure(self, fig):
- """
- Set the `.Figure` for the `.OffsetBox` and all its children.
- Parameters
- ----------
- fig : `~matplotlib.figure.Figure`
- """
- martist.Artist.set_figure(self, fig)
- for c in self.get_children():
- c.set_figure(fig)
- @martist.Artist.axes.setter
- def axes(self, ax):
- # TODO deal with this better
- martist.Artist.axes.fset(self, ax)
- for c in self.get_children():
- if c is not None:
- c.axes = ax
- def contains(self, mouseevent):
- """
- Delegate the mouse event contains-check to the children.
- As a container, the `.OffsetBox` does not respond itself to
- mouseevents.
- Parameters
- ----------
- mouseevent : `matplotlib.backend_bases.MouseEvent`
- Returns
- -------
- contains : bool
- Whether any values are within the radius.
- details : dict
- An artist-specific dictionary of details of the event context,
- such as which points are contained in the pick radius. See the
- individual Artist subclasses for details.
- See Also
- --------
- .Artist.contains
- """
- inside, info = self._default_contains(mouseevent)
- if inside is not None:
- return inside, info
- for c in self.get_children():
- a, b = c.contains(mouseevent)
- if a:
- return a, b
- return False, {}
- def set_offset(self, xy):
- """
- Set the offset.
- Parameters
- ----------
- xy : (float, float) or callable
- The (x, y) coordinates of the offset in display units. These can
- either be given explicitly as a tuple (x, y), or by providing a
- function that converts the extent into the offset. This function
- must have the signature::
- def offset(width, height, xdescent, ydescent, renderer) \
- -> (float, float)
- """
- self._offset = xy
- self.stale = True
- def get_offset(self, width, height, xdescent, ydescent, renderer):
- """
- Return the offset as a tuple (x, y).
- The extent parameters have to be provided to handle the case where the
- offset is dynamically determined by a callable (see
- `~.OffsetBox.set_offset`).
- Parameters
- ----------
- width, height, xdescent, ydescent
- Extent parameters.
- renderer : `.RendererBase` subclass
- """
- return (self._offset(width, height, xdescent, ydescent, renderer)
- if callable(self._offset)
- else self._offset)
- def set_width(self, width):
- """
- Set the width of the box.
- Parameters
- ----------
- width : float
- """
- self.width = width
- self.stale = True
- def set_height(self, height):
- """
- Set the height of the box.
- Parameters
- ----------
- height : float
- """
- self.height = height
- self.stale = True
- def get_visible_children(self):
- r"""Return a list of the visible child `.Artist`\s."""
- return [c for c in self._children if c.get_visible()]
- def get_children(self):
- r"""Return a list of the child `.Artist`\s."""
- return self._children
- def get_extent_offsets(self, renderer):
- """
- Update offset of the children and return the extent of the box.
- Parameters
- ----------
- renderer : `.RendererBase` subclass
- Returns
- -------
- width
- height
- xdescent
- ydescent
- list of (xoffset, yoffset) pairs
- """
- raise NotImplementedError(
- "get_extent_offsets must be overridden in derived classes.")
- def get_extent(self, renderer):
- """Return a tuple ``width, height, xdescent, ydescent`` of the box."""
- w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
- return w, h, xd, yd
- def get_window_extent(self, renderer):
- """Return the bounding box (`.Bbox`) in display space."""
- w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
- px, py = self.get_offset(w, h, xd, yd, renderer)
- return mtransforms.Bbox.from_bounds(px - xd, py - yd, w, h)
- def draw(self, renderer):
- """
- Update the location of children if necessary and draw them
- to the given *renderer*.
- """
- width, height, xdescent, ydescent, offsets = self.get_extent_offsets(
- renderer)
- px, py = self.get_offset(width, height, xdescent, ydescent, renderer)
- for c, (ox, oy) in zip(self.get_visible_children(), offsets):
- c.set_offset((px + ox, py + oy))
- c.draw(renderer)
- bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
- self.stale = False
- class PackerBase(OffsetBox):
- def __init__(self, pad=None, sep=None, width=None, height=None,
- align=None, mode=None,
- children=None):
- """
- Parameters
- ----------
- pad : float, optional
- The boundary padding in points.
- sep : float, optional
- The spacing between items in points.
- width, height : float, optional
- Width and height of the container box in pixels, calculated if
- *None*.
- align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'}
- Alignment of boxes.
- mode : {'fixed', 'expand', 'equal'}
- The packing mode.
- - 'fixed' packs the given `.Artists` tight with *sep* spacing.
- - 'expand' uses the maximal available space to distribute the
- artists with equal spacing in between.
- - 'equal': Each artist an equal fraction of the available space
- and is left-aligned (or top-aligned) therein.
- children : list of `.Artist`
- The artists to pack.
- Notes
- -----
- *pad* and *sep* need to given in points and will be scale with
- the renderer dpi, while *width* and *height* need to be in
- pixels.
- """
- super().__init__()
- self.height = height
- self.width = width
- self.sep = sep
- self.pad = pad
- self.mode = mode
- self.align = align
- self._children = children
- class VPacker(PackerBase):
- """
- The VPacker has its children packed vertically. It automatically
- adjust the relative positions of children in the drawing time.
- """
- def __init__(self, pad=None, sep=None, width=None, height=None,
- align="baseline", mode="fixed",
- children=None):
- """
- Parameters
- ----------
- pad : float, optional
- The boundary padding in points.
- sep : float, optional
- The spacing between items in points.
- width, height : float, optional
- Width and height of the container box in pixels, calculated if
- *None*.
- align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'}
- Alignment of boxes.
- mode : {'fixed', 'expand', 'equal'}
- The packing mode.
- - 'fixed' packs the given `.Artists` tight with *sep* spacing.
- - 'expand' uses the maximal available space to distribute the
- artists with equal spacing in between.
- - 'equal': Each artist an equal fraction of the available space
- and is left-aligned (or top-aligned) therein.
- children : list of `.Artist`
- The artists to pack.
- Notes
- -----
- *pad* and *sep* need to given in points and will be scale with
- the renderer dpi, while *width* and *height* need to be in
- pixels.
- """
- super().__init__(pad, sep, width, height, align, mode, children)
- def get_extent_offsets(self, renderer):
- # docstring inherited
- dpicor = renderer.points_to_pixels(1.)
- pad = self.pad * dpicor
- sep = self.sep * dpicor
- if self.width is not None:
- for c in self.get_visible_children():
- if isinstance(c, PackerBase) and c.mode == "expand":
- c.set_width(self.width)
- whd_list = [c.get_extent(renderer)
- for c in self.get_visible_children()]
- whd_list = [(w, h, xd, (h - yd)) for w, h, xd, yd in whd_list]
- wd_list = [(w, xd) for w, h, xd, yd in whd_list]
- width, xdescent, xoffsets = _get_aligned_offsets(wd_list,
- self.width,
- self.align)
- pack_list = [(h, yd) for w, h, xd, yd in whd_list]
- height, yoffsets_ = _get_packed_offsets(pack_list, self.height,
- sep, self.mode)
- yoffsets = yoffsets_ + [yd for w, h, xd, yd in whd_list]
- ydescent = height - yoffsets[0]
- yoffsets = height - yoffsets
- yoffsets = yoffsets - ydescent
- return (width + 2 * pad, height + 2 * pad,
- xdescent + pad, ydescent + pad,
- list(zip(xoffsets, yoffsets)))
- class HPacker(PackerBase):
- """
- The HPacker has its children packed horizontally. It automatically
- adjusts the relative positions of children at draw time.
- """
- def __init__(self, pad=None, sep=None, width=None, height=None,
- align="baseline", mode="fixed",
- children=None):
- """
- Parameters
- ----------
- pad : float, optional
- The boundary padding in points.
- sep : float, optional
- The spacing between items in points.
- width, height : float, optional
- Width and height of the container box in pixels, calculated if
- *None*.
- align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'}
- Alignment of boxes.
- mode : {'fixed', 'expand', 'equal'}
- The packing mode.
- - 'fixed' packs the given `.Artists` tight with *sep* spacing.
- - 'expand' uses the maximal available space to distribute the
- artists with equal spacing in between.
- - 'equal': Each artist an equal fraction of the available space
- and is left-aligned (or top-aligned) therein.
- children : list of `.Artist`
- The artists to pack.
- Notes
- -----
- *pad* and *sep* need to given in points and will be scale with
- the renderer dpi, while *width* and *height* need to be in
- pixels.
- """
- super().__init__(pad, sep, width, height, align, mode, children)
- def get_extent_offsets(self, renderer):
- # docstring inherited
- dpicor = renderer.points_to_pixels(1.)
- pad = self.pad * dpicor
- sep = self.sep * dpicor
- whd_list = [c.get_extent(renderer)
- for c in self.get_visible_children()]
- if not whd_list:
- return 2 * pad, 2 * pad, pad, pad, []
- if self.height is None:
- height_descent = max(h - yd for w, h, xd, yd in whd_list)
- ydescent = max(yd for w, h, xd, yd in whd_list)
- height = height_descent + ydescent
- else:
- height = self.height - 2 * pad # width w/o pad
- hd_list = [(h, yd) for w, h, xd, yd in whd_list]
- height, ydescent, yoffsets = _get_aligned_offsets(hd_list,
- self.height,
- self.align)
- pack_list = [(w, xd) for w, h, xd, yd in whd_list]
- width, xoffsets_ = _get_packed_offsets(pack_list, self.width,
- sep, self.mode)
- xoffsets = xoffsets_ + [xd for w, h, xd, yd in whd_list]
- xdescent = whd_list[0][2]
- xoffsets = xoffsets - xdescent
- return (width + 2 * pad, height + 2 * pad,
- xdescent + pad, ydescent + pad,
- list(zip(xoffsets, yoffsets)))
- class PaddedBox(OffsetBox):
- """
- A container to add a padding around an `.Artist`.
- The `.PaddedBox` contains a `.FancyBboxPatch` that is used to visualize
- it when rendering.
- """
- def __init__(self, child, pad=None, draw_frame=False, patch_attrs=None):
- """
- Parameters
- ----------
- child : `~matplotlib.artist.Artist`
- The contained `.Artist`.
- pad : float
- The padding in points. This will be scaled with the renderer dpi.
- In contrast *width* and *hight* are in *pixel* and thus not scaled.
- draw_frame : bool
- Whether to draw the contained `.FancyBboxPatch`.
- patch_attrs : dict or None
- Additional parameters passed to the contained `.FancyBboxPatch`.
- """
- super().__init__()
- self.pad = pad
- self._children = [child]
- self.patch = FancyBboxPatch(
- xy=(0.0, 0.0), width=1., height=1.,
- facecolor='w', edgecolor='k',
- mutation_scale=1, # self.prop.get_size_in_points(),
- snap=True
- )
- self.patch.set_boxstyle("square", pad=0)
- if patch_attrs is not None:
- self.patch.update(patch_attrs)
- self._drawFrame = draw_frame
- def get_extent_offsets(self, renderer):
- # docstring inherited.
- dpicor = renderer.points_to_pixels(1.)
- pad = self.pad * dpicor
- w, h, xd, yd = self._children[0].get_extent(renderer)
- return (w + 2 * pad, h + 2 * pad, xd + pad, yd + pad,
- [(0, 0)])
- def draw(self, renderer):
- """
- Update the location of children if necessary and draw them
- to the given *renderer*.
- """
- width, height, xdescent, ydescent, offsets = self.get_extent_offsets(
- renderer)
- px, py = self.get_offset(width, height, xdescent, ydescent, renderer)
- for c, (ox, oy) in zip(self.get_visible_children(), offsets):
- c.set_offset((px + ox, py + oy))
- self.draw_frame(renderer)
- for c in self.get_visible_children():
- c.draw(renderer)
- #bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
- self.stale = False
- def update_frame(self, bbox, fontsize=None):
- self.patch.set_bounds(bbox.x0, bbox.y0,
- bbox.width, bbox.height)
- if fontsize:
- self.patch.set_mutation_scale(fontsize)
- self.stale = True
- def draw_frame(self, renderer):
- # update the location and size of the legend
- bbox = self.get_window_extent(renderer)
- self.update_frame(bbox)
- if self._drawFrame:
- self.patch.draw(renderer)
- class DrawingArea(OffsetBox):
- """
- The DrawingArea can contain any Artist as a child. The DrawingArea
- has a fixed width and height. The position of children relative to
- the parent is fixed. The children can be clipped at the
- boundaries of the parent.
- """
- def __init__(self, width, height, xdescent=0.,
- ydescent=0., clip=False):
- """
- *width*, *height* : width and height of the container box.
- *xdescent*, *ydescent* : descent of the box in x- and y-direction.
- *clip* : Whether to clip the children
- """
- super().__init__()
- self.width = width
- self.height = height
- self.xdescent = xdescent
- self.ydescent = ydescent
- self._clip_children = clip
- self.offset_transform = mtransforms.Affine2D()
- self.dpi_transform = mtransforms.Affine2D()
- @property
- def clip_children(self):
- """
- If the children of this DrawingArea should be clipped
- by DrawingArea bounding box.
- """
- return self._clip_children
- @clip_children.setter
- def clip_children(self, val):
- self._clip_children = bool(val)
- self.stale = True
- def get_transform(self):
- """
- Return the `~matplotlib.transforms.Transform` applied to the children.
- """
- return self.dpi_transform + self.offset_transform
- def set_transform(self, t):
- """
- set_transform is ignored.
- """
- def set_offset(self, xy):
- """
- Set the offset of the container.
- Parameters
- ----------
- xy : (float, float)
- The (x, y) coordinates of the offset in display units.
- """
- self._offset = xy
- self.offset_transform.clear()
- self.offset_transform.translate(xy[0], xy[1])
- self.stale = True
- def get_offset(self):
- """
- return offset of the container.
- """
- return self._offset
- def get_window_extent(self, renderer):
- '''
- get the bounding box in display space.
- '''
- w, h, xd, yd = self.get_extent(renderer)
- ox, oy = self.get_offset() # w, h, xd, yd)
- return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h)
- def get_extent(self, renderer):
- """
- Return with, height, xdescent, ydescent of box
- """
- dpi_cor = renderer.points_to_pixels(1.)
- return (self.width * dpi_cor, self.height * dpi_cor,
- self.xdescent * dpi_cor, self.ydescent * dpi_cor)
- def add_artist(self, a):
- 'Add any :class:`~matplotlib.artist.Artist` to the container box'
- self._children.append(a)
- if not a.is_transform_set():
- a.set_transform(self.get_transform())
- if self.axes is not None:
- a.axes = self.axes
- fig = self.figure
- if fig is not None:
- a.set_figure(fig)
- def draw(self, renderer):
- """
- Draw the children
- """
- dpi_cor = renderer.points_to_pixels(1.)
- self.dpi_transform.clear()
- self.dpi_transform.scale(dpi_cor)
- # At this point the DrawingArea has a transform
- # to the display space so the path created is
- # good for clipping children
- tpath = mtransforms.TransformedPath(
- mpath.Path([[0, 0], [0, self.height],
- [self.width, self.height],
- [self.width, 0]]),
- self.get_transform())
- for c in self._children:
- if self._clip_children and not (c.clipbox or c._clippath):
- c.set_clip_path(tpath)
- c.draw(renderer)
- bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
- self.stale = False
- class TextArea(OffsetBox):
- """
- The TextArea is contains a single Text instance. The text is
- placed at (0, 0) with baseline+left alignment. The width and height
- of the TextArea instance is the width and height of the its child
- text.
- """
- def __init__(self, s,
- textprops=None,
- multilinebaseline=None,
- minimumdescent=True,
- ):
- """
- Parameters
- ----------
- s : str
- a string to be displayed.
- textprops : dictionary, optional, default: None
- Dictionary of keyword parameters to be passed to the
- `~matplotlib.text.Text` instance contained inside TextArea.
- multilinebaseline : bool, optional
- If `True`, baseline for multiline text is adjusted so that it is
- (approximately) center-aligned with singleline text.
- minimumdescent : bool, optional
- If `True`, the box has a minimum descent of "p".
- """
- if textprops is None:
- textprops = {}
- textprops.setdefault("va", "baseline")
- self._text = mtext.Text(0, 0, s, **textprops)
- OffsetBox.__init__(self)
- self._children = [self._text]
- self.offset_transform = mtransforms.Affine2D()
- self._baseline_transform = mtransforms.Affine2D()
- self._text.set_transform(self.offset_transform +
- self._baseline_transform)
- self._multilinebaseline = multilinebaseline
- self._minimumdescent = minimumdescent
- def set_text(self, s):
- "Set the text of this area as a string."
- self._text.set_text(s)
- self.stale = True
- def get_text(self):
- "Returns the string representation of this area's text"
- return self._text.get_text()
- def set_multilinebaseline(self, t):
- """
- Set multilinebaseline .
- If True, baseline for multiline text is adjusted so that it is
- (approximately) center-aligned with single-line text.
- """
- self._multilinebaseline = t
- self.stale = True
- def get_multilinebaseline(self):
- """
- get multilinebaseline .
- """
- return self._multilinebaseline
- def set_minimumdescent(self, t):
- """
- Set minimumdescent .
- If True, extent of the single line text is adjusted so that
- it has minimum descent of "p"
- """
- self._minimumdescent = t
- self.stale = True
- def get_minimumdescent(self):
- """
- get minimumdescent.
- """
- return self._minimumdescent
- def set_transform(self, t):
- """
- set_transform is ignored.
- """
- def set_offset(self, xy):
- """
- Set the offset of the container.
- Parameters
- ----------
- xy : (float, float)
- The (x, y) coordinates of the offset in display units.
- """
- self._offset = xy
- self.offset_transform.clear()
- self.offset_transform.translate(xy[0], xy[1])
- self.stale = True
- def get_offset(self):
- """
- return offset of the container.
- """
- return self._offset
- def get_window_extent(self, renderer):
- '''
- get the bounding box in display space.
- '''
- w, h, xd, yd = self.get_extent(renderer)
- ox, oy = self.get_offset() # w, h, xd, yd)
- return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h)
- def get_extent(self, renderer):
- _, h_, d_ = renderer.get_text_width_height_descent(
- "lp", self._text._fontproperties, ismath=False)
- bbox, info, d = self._text._get_layout(renderer)
- w, h = bbox.width, bbox.height
- self._baseline_transform.clear()
- if len(info) > 1 and self._multilinebaseline:
- d_new = 0.5 * h - 0.5 * (h_ - d_)
- self._baseline_transform.translate(0, d - d_new)
- d = d_new
- else: # single line
- h_d = max(h_ - d_, h - d)
- if self.get_minimumdescent():
- ## to have a minimum descent, #i.e., "l" and "p" have same
- ## descents.
- d = max(d, d_)
- #else:
- # d = d
- h = h_d + d
- return w, h, 0., d
- def draw(self, renderer):
- """
- Draw the children
- """
- self._text.draw(renderer)
- bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
- self.stale = False
- class AuxTransformBox(OffsetBox):
- """
- Offset Box with the aux_transform. Its children will be
- transformed with the aux_transform first then will be
- offseted. The absolute coordinate of the aux_transform is meaning
- as it will be automatically adjust so that the left-lower corner
- of the bounding box of children will be set to (0, 0) before the
- offset transform.
- It is similar to drawing area, except that the extent of the box
- is not predetermined but calculated from the window extent of its
- children. Furthermore, the extent of the children will be
- calculated in the transformed coordinate.
- """
- def __init__(self, aux_transform):
- self.aux_transform = aux_transform
- OffsetBox.__init__(self)
- self.offset_transform = mtransforms.Affine2D()
- # ref_offset_transform makes offset_transform always relative to the
- # lower-left corner of the bbox of its children.
- self.ref_offset_transform = mtransforms.Affine2D()
- def add_artist(self, a):
- 'Add any :class:`~matplotlib.artist.Artist` to the container box'
- self._children.append(a)
- a.set_transform(self.get_transform())
- self.stale = True
- def get_transform(self):
- """
- Return the :class:`~matplotlib.transforms.Transform` applied
- to the children
- """
- return (self.aux_transform
- + self.ref_offset_transform
- + self.offset_transform)
- def set_transform(self, t):
- """
- set_transform is ignored.
- """
- def set_offset(self, xy):
- """
- Set the offset of the container.
- Parameters
- ----------
- xy : (float, float)
- The (x, y) coordinates of the offset in display units.
- """
- self._offset = xy
- self.offset_transform.clear()
- self.offset_transform.translate(xy[0], xy[1])
- self.stale = True
- def get_offset(self):
- """
- return offset of the container.
- """
- return self._offset
- def get_window_extent(self, renderer):
- '''
- get the bounding box in display space.
- '''
- w, h, xd, yd = self.get_extent(renderer)
- ox, oy = self.get_offset() # w, h, xd, yd)
- return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h)
- def get_extent(self, renderer):
- # clear the offset transforms
- _off = self.offset_transform.get_matrix() # to be restored later
- self.ref_offset_transform.clear()
- self.offset_transform.clear()
- # calculate the extent
- bboxes = [c.get_window_extent(renderer) for c in self._children]
- ub = mtransforms.Bbox.union(bboxes)
- # adjust ref_offset_transform
- self.ref_offset_transform.translate(-ub.x0, -ub.y0)
- # restor offset transform
- self.offset_transform.set_matrix(_off)
- return ub.width, ub.height, 0., 0.
- def draw(self, renderer):
- """
- Draw the children
- """
- for c in self._children:
- c.draw(renderer)
- bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
- self.stale = False
- class AnchoredOffsetbox(OffsetBox):
- """
- An offset box placed according to the legend location
- loc. AnchoredOffsetbox has a single child. When multiple children
- is needed, use other OffsetBox class to enclose them. By default,
- the offset box is anchored against its parent axes. You may
- explicitly specify the bbox_to_anchor.
- """
- zorder = 5 # zorder of the legend
- # Location codes
- codes = {'upper right': 1,
- 'upper left': 2,
- 'lower left': 3,
- 'lower right': 4,
- 'right': 5,
- 'center left': 6,
- 'center right': 7,
- 'lower center': 8,
- 'upper center': 9,
- 'center': 10,
- }
- def __init__(self, loc,
- pad=0.4, borderpad=0.5,
- child=None, prop=None, frameon=True,
- bbox_to_anchor=None,
- bbox_transform=None,
- **kwargs):
- """
- loc is a string or an integer specifying the legend location.
- The valid location codes are::
- 'upper right' : 1,
- 'upper left' : 2,
- 'lower left' : 3,
- 'lower right' : 4,
- 'right' : 5, (same as 'center right', for back-compatibility)
- 'center left' : 6,
- 'center right' : 7,
- 'lower center' : 8,
- 'upper center' : 9,
- 'center' : 10,
- pad : pad around the child for drawing a frame. given in
- fraction of fontsize.
- borderpad : pad between offsetbox frame and the bbox_to_anchor,
- child : OffsetBox instance that will be anchored.
- prop : font property. This is only used as a reference for paddings.
- frameon : draw a frame box if True.
- bbox_to_anchor : bbox to anchor. Use self.axes.bbox if None.
- bbox_transform : with which the bbox_to_anchor will be transformed.
- """
- super().__init__(**kwargs)
- self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
- self.set_child(child)
- if isinstance(loc, str):
- loc = cbook._check_getitem(self.codes, loc=loc)
- self.loc = loc
- self.borderpad = borderpad
- self.pad = pad
- if prop is None:
- self.prop = FontProperties(size=rcParams["legend.fontsize"])
- elif isinstance(prop, dict):
- self.prop = FontProperties(**prop)
- if "size" not in prop:
- self.prop.set_size(rcParams["legend.fontsize"])
- else:
- self.prop = prop
- 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=0)
- self._drawFrame = frameon
- def set_child(self, child):
- "set the child to be anchored"
- self._child = child
- if child is not None:
- child.axes = self.axes
- self.stale = True
- def get_child(self):
- "return the child"
- return self._child
- def get_children(self):
- "return the list of children"
- return [self._child]
- def get_extent(self, renderer):
- """
- return the extent of the artist. The extent of the child
- added with the pad is returned
- """
- w, h, xd, yd = self.get_child().get_extent(renderer)
- fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
- pad = self.pad * fontsize
- return w + 2 * pad, h + 2 * pad, xd + pad, yd + pad
- def get_bbox_to_anchor(self):
- """
- return the bbox that the legend will be anchored
- """
- if self._bbox_to_anchor is None:
- return self.axes.bbox
- else:
- transform = self._bbox_to_anchor_transform
- if transform is None:
- return self._bbox_to_anchor
- else:
- return TransformedBbox(self._bbox_to_anchor,
- transform)
- def set_bbox_to_anchor(self, bbox, transform=None):
- """
- set the bbox that the child will be anchored.
- *bbox* can be a Bbox instance, a list of [left, bottom, width,
- height], or a list of [left, bottom] where the width and
- height will be assumed to be zero. The bbox will be
- transformed to display coordinate by the given transform.
- """
- if bbox is None or isinstance(bbox, BboxBase):
- self._bbox_to_anchor = bbox
- else:
- try:
- l = len(bbox)
- except TypeError:
- raise ValueError("Invalid argument for bbox : %s" % str(bbox))
- if l == 2:
- bbox = [bbox[0], bbox[1], 0, 0]
- self._bbox_to_anchor = Bbox.from_bounds(*bbox)
- self._bbox_to_anchor_transform = transform
- self.stale = True
- def get_window_extent(self, renderer):
- '''
- get the bounding box in display space.
- '''
- self._update_offset_func(renderer)
- w, h, xd, yd = self.get_extent(renderer)
- ox, oy = self.get_offset(w, h, xd, yd, renderer)
- return Bbox.from_bounds(ox - xd, oy - yd, w, h)
- def _update_offset_func(self, renderer, fontsize=None):
- """
- Update the offset func which depends on the dpi of the
- renderer (because of the padding).
- """
- if fontsize is None:
- fontsize = renderer.points_to_pixels(
- self.prop.get_size_in_points())
- def _offset(w, h, xd, yd, renderer, fontsize=fontsize, self=self):
- bbox = Bbox.from_bounds(0, 0, w, h)
- borderpad = self.borderpad * fontsize
- bbox_to_anchor = self.get_bbox_to_anchor()
- x0, y0 = self._get_anchored_bbox(self.loc,
- bbox,
- bbox_to_anchor,
- borderpad)
- return x0 + xd, y0 + yd
- self.set_offset(_offset)
- def update_frame(self, bbox, fontsize=None):
- self.patch.set_bounds(bbox.x0, bbox.y0,
- bbox.width, bbox.height)
- if fontsize:
- self.patch.set_mutation_scale(fontsize)
- def draw(self, renderer):
- "draw the artist"
- if not self.get_visible():
- return
- fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
- self._update_offset_func(renderer, fontsize)
- if self._drawFrame:
- # update the location and size of the legend
- bbox = self.get_window_extent(renderer)
- self.update_frame(bbox, fontsize)
- self.patch.draw(renderer)
- width, height, xdescent, ydescent = self.get_extent(renderer)
- px, py = self.get_offset(width, height, xdescent, ydescent, renderer)
- self.get_child().set_offset((px, py))
- self.get_child().draw(renderer)
- self.stale = False
- def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad):
- """
- return the position of the bbox anchored at the parentbbox
- with the loc code, with the borderpad.
- """
- assert loc in range(1, 11) # called only internally
- BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = range(11)
- anchor_coefs = {UR: "NE",
- UL: "NW",
- LL: "SW",
- LR: "SE",
- R: "E",
- CL: "W",
- CR: "E",
- LC: "S",
- UC: "N",
- C: "C"}
- c = anchor_coefs[loc]
- container = parentbbox.padded(-borderpad)
- anchored_box = bbox.anchored(c, container=container)
- return anchored_box.x0, anchored_box.y0
- class AnchoredText(AnchoredOffsetbox):
- """
- AnchoredOffsetbox with Text.
- """
- def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, **kwargs):
- """
- Parameters
- ----------
- s : str
- Text.
- loc : str
- Location code.
- pad : float, optional
- Pad between the text and the frame as fraction of the font
- size.
- borderpad : float, optional
- Pad between the frame and the axes (or *bbox_to_anchor*).
- prop : dictionary, optional, default: None
- Dictionary of keyword parameters to be passed to the
- `~matplotlib.text.Text` instance contained inside AnchoredText.
- Notes
- -----
- Other keyword parameters of `AnchoredOffsetbox` are also
- allowed.
- """
- if prop is None:
- prop = {}
- badkwargs = {'ha', 'horizontalalignment', 'va', 'verticalalignment'}
- if badkwargs & set(prop):
- cbook.warn_deprecated(
- "3.1", message="Mixing horizontalalignment or "
- "verticalalignment with AnchoredText is not supported, "
- "deprecated since %(since)s, and will raise an exception "
- "%(removal)s.")
- self.txt = TextArea(s, textprops=prop, minimumdescent=False)
- fp = self.txt._text.get_fontproperties()
- super().__init__(
- loc, pad=pad, borderpad=borderpad, child=self.txt, prop=fp,
- **kwargs)
- class OffsetImage(OffsetBox):
- def __init__(self, arr,
- zoom=1,
- cmap=None,
- norm=None,
- interpolation=None,
- origin=None,
- filternorm=1,
- filterrad=4.0,
- resample=False,
- dpi_cor=True,
- **kwargs
- ):
- OffsetBox.__init__(self)
- self._dpi_cor = dpi_cor
- self.image = BboxImage(bbox=self.get_window_extent,
- cmap=cmap,
- norm=norm,
- interpolation=interpolation,
- origin=origin,
- filternorm=filternorm,
- filterrad=filterrad,
- resample=resample,
- **kwargs
- )
- self._children = [self.image]
- self.set_zoom(zoom)
- self.set_data(arr)
- def set_data(self, arr):
- self._data = np.asarray(arr)
- self.image.set_data(self._data)
- self.stale = True
- def get_data(self):
- return self._data
- def set_zoom(self, zoom):
- self._zoom = zoom
- self.stale = True
- def get_zoom(self):
- return self._zoom
- # def set_axes(self, axes):
- # self.image.set_axes(axes)
- # martist.Artist.set_axes(self, axes)
- # def set_offset(self, xy):
- # """
- # Set the offset of the container.
- #
- # Parameters
- # ----------
- # xy : (float, float)
- # The (x, y) coordinates of the offset in display units.
- # """
- # self._offset = xy
- # self.offset_transform.clear()
- # self.offset_transform.translate(xy[0], xy[1])
- def get_offset(self):
- """
- return offset of the container.
- """
- return self._offset
- def get_children(self):
- return [self.image]
- def get_window_extent(self, renderer):
- '''
- get the bounding box in display space.
- '''
- w, h, xd, yd = self.get_extent(renderer)
- ox, oy = self.get_offset()
- return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h)
- def get_extent(self, renderer):
- if self._dpi_cor: # True, do correction
- dpi_cor = renderer.points_to_pixels(1.)
- else:
- dpi_cor = 1.
- zoom = self.get_zoom()
- data = self.get_data()
- ny, nx = data.shape[:2]
- w, h = dpi_cor * nx * zoom, dpi_cor * ny * zoom
- return w, h, 0, 0
- def draw(self, renderer):
- """
- Draw the children
- """
- self.image.draw(renderer)
- # bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
- self.stale = False
- 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.
- """
- martist.Artist.__init__(self, **kwargs)
- _AnnotationBase.__init__(self,
- xy,
- xycoords=xycoords,
- annotation_clip=annotation_clip)
- self.offsetbox = offsetbox
- self.arrowprops = arrowprops
- self.set_fontsize(fontsize)
- if xybox is None:
- self.xybox = xy
- else:
- self.xybox = xybox
- if boxcoords is None:
- self.boxcoords = xycoords
- else:
- self.boxcoords = boxcoords
- 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
- 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
- @property
- def xyann(self):
- return self.xybox
- @xyann.setter
- def xyann(self, xyann):
- self.xybox = xyann
- self.stale = True
- @property
- def anncoords(self):
- return self.boxcoords
- @anncoords.setter
- def anncoords(self, coords):
- self.boxcoords = coords
- self.stale = True
- def contains(self, mouseevent):
- inside, info = self._default_contains(mouseevent)
- if inside is not None:
- return inside, info
- t, tinfo = self.offsetbox.contains(mouseevent)
- #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)
- self.stale = True
- 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.xybox
- if isinstance(self.boxcoords, tuple):
- xcoord, ycoord = self.boxcoords
- 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.boxcoords)
- 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:
- 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 shrunk by shrinkA 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)
- self.stale = False
- class DraggableBase:
- """
- Helper base class for a draggable artist (legend, offsetbox).
- Derived classes must override the following methods::
- def save_offset(self):
- '''
- Called when the object is picked for dragging; should save the
- reference position of the artist.
- '''
- def update_offset(self, dx, dy):
- '''
- Called during the dragging; (*dx*, *dy*) is the pixel offset from
- the point where the mouse drag started.
- '''
- Optionally, you may override the following methods::
- def artist_picker(self, artist, evt):
- '''The picker method that will be used.'''
- return self.ref_artist.contains(evt)
- def finalize_offset(self):
- '''Called when the mouse is released.'''
- In the current implementation of `DraggableLegend` and
- `DraggableAnnotation`, `update_offset` places the artists in display
- coordinates, and `finalize_offset` recalculates their position in axes
- coordinate and set a relevant attribute.
- """
- def __init__(self, ref_artist, use_blit=False):
- self.ref_artist = ref_artist
- self.got_artist = False
- self.canvas = self.ref_artist.figure.canvas
- self._use_blit = use_blit and self.canvas.supports_blit
- c2 = self.canvas.mpl_connect('pick_event', self.on_pick)
- c3 = self.canvas.mpl_connect('button_release_event', self.on_release)
- ref_artist.set_picker(self.artist_picker)
- self.cids = [c2, c3]
- def on_motion(self, evt):
- if self._check_still_parented() and self.got_artist:
- dx = evt.x - self.mouse_x
- dy = evt.y - self.mouse_y
- self.update_offset(dx, dy)
- self.canvas.draw()
- def on_motion_blit(self, evt):
- if self._check_still_parented() and self.got_artist:
- dx = evt.x - self.mouse_x
- dy = evt.y - self.mouse_y
- self.update_offset(dx, dy)
- self.canvas.restore_region(self.background)
- self.ref_artist.draw(self.ref_artist.figure._cachedRenderer)
- self.canvas.blit()
- def on_pick(self, evt):
- if self._check_still_parented() and evt.artist == self.ref_artist:
- self.mouse_x = evt.mouseevent.x
- self.mouse_y = evt.mouseevent.y
- self.got_artist = True
- if self._use_blit:
- self.ref_artist.set_animated(True)
- self.canvas.draw()
- self.background = self.canvas.copy_from_bbox(
- self.ref_artist.figure.bbox)
- self.ref_artist.draw(self.ref_artist.figure._cachedRenderer)
- self.canvas.blit()
- self._c1 = self.canvas.mpl_connect('motion_notify_event',
- self.on_motion_blit)
- else:
- self._c1 = self.canvas.mpl_connect('motion_notify_event',
- self.on_motion)
- self.save_offset()
- def on_release(self, event):
- if self._check_still_parented() and self.got_artist:
- self.finalize_offset()
- self.got_artist = False
- self.canvas.mpl_disconnect(self._c1)
- if self._use_blit:
- self.ref_artist.set_animated(False)
- def _check_still_parented(self):
- if self.ref_artist.figure is None:
- self.disconnect()
- return False
- else:
- return True
- def disconnect(self):
- """Disconnect the callbacks."""
- for cid in self.cids:
- self.canvas.mpl_disconnect(cid)
- try:
- c1 = self._c1
- except AttributeError:
- pass
- else:
- self.canvas.mpl_disconnect(c1)
- def artist_picker(self, artist, evt):
- return self.ref_artist.contains(evt)
- def save_offset(self):
- pass
- def update_offset(self, dx, dy):
- pass
- def finalize_offset(self):
- pass
- class DraggableOffsetBox(DraggableBase):
- def __init__(self, ref_artist, offsetbox, use_blit=False):
- DraggableBase.__init__(self, ref_artist, use_blit=use_blit)
- self.offsetbox = offsetbox
- def save_offset(self):
- offsetbox = self.offsetbox
- renderer = offsetbox.figure._cachedRenderer
- w, h, xd, yd = offsetbox.get_extent(renderer)
- offset = offsetbox.get_offset(w, h, xd, yd, renderer)
- self.offsetbox_x, self.offsetbox_y = offset
- self.offsetbox.set_offset(offset)
- def update_offset(self, dx, dy):
- loc_in_canvas = self.offsetbox_x + dx, self.offsetbox_y + dy
- self.offsetbox.set_offset(loc_in_canvas)
- def get_loc_in_canvas(self):
- offsetbox = self.offsetbox
- renderer = offsetbox.figure._cachedRenderer
- w, h, xd, yd = offsetbox.get_extent(renderer)
- ox, oy = offsetbox._offset
- loc_in_canvas = (ox - xd, oy - yd)
- return loc_in_canvas
- class DraggableAnnotation(DraggableBase):
- def __init__(self, annotation, use_blit=False):
- DraggableBase.__init__(self, annotation, use_blit=use_blit)
- self.annotation = annotation
- def save_offset(self):
- ann = self.annotation
- self.ox, self.oy = ann.get_transform().transform(ann.xyann)
- def update_offset(self, dx, dy):
- ann = self.annotation
- ann.xyann = ann.get_transform().inverted().transform(
- (self.ox + dx, self.oy + dy))
|