From ea082aaa90aa563b46c8df0ccc29b9b82a40ac05 Mon Sep 17 00:00:00 2001 From: Hassan Kibirige Date: Sun, 24 May 2015 21:12:39 -0500 Subject: [PATCH] Clipping for OffsetBoxes - Child `Artists` of `DrawingArea` can now get clipped to the bounds of the parent --- .../api_changes/2015-04-18-drawingarea.rst | 8 + doc/users/whats_new/offsetbox.rst | 11 + examples/pylab_examples/anchored_artists.py | 2 +- lib/matplotlib/__init__.py | 1 + lib/matplotlib/offsetbox.py | 18 +- .../test_offsetbox/offsetbox_clipping.pdf | Bin 0 -> 1584 bytes .../test_offsetbox/offsetbox_clipping.png | Bin 0 -> 3969 bytes .../test_offsetbox/offsetbox_clipping.svg | 241 ++++++++++++++++++ lib/matplotlib/tests/test_offsetbox.py | 49 ++++ .../axes_grid1/anchored_artists.py | 2 +- 10 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 doc/api/api_changes/2015-04-18-drawingarea.rst create mode 100644 doc/users/whats_new/offsetbox.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.png create mode 100644 lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.svg create mode 100644 lib/matplotlib/tests/test_offsetbox.py diff --git a/doc/api/api_changes/2015-04-18-drawingarea.rst b/doc/api/api_changes/2015-04-18-drawingarea.rst new file mode 100644 index 000000000000..c977dd93e01c --- /dev/null +++ b/doc/api/api_changes/2015-04-18-drawingarea.rst @@ -0,0 +1,8 @@ +'OffsetBox.DrawingArea' respects the 'clip' keyword argument +```````````````````````````````````````````````````````````` + +The call signature was `OffsetBox.DrawingArea(..., clip=True)` but nothing +was done with the `clip` argument. The object did not do any clipping +regardless of that parameter. Now the object can and does clip the child `Artists` if they are set to be clipped. + +You can turn off the clipping on a per-child basis using `child.set_clip_on(False)`. diff --git a/doc/users/whats_new/offsetbox.rst b/doc/users/whats_new/offsetbox.rst new file mode 100644 index 000000000000..c1d16eb1dbb4 --- /dev/null +++ b/doc/users/whats_new/offsetbox.rst @@ -0,0 +1,11 @@ +OffsetBoxes now support clipping +```````````````````````````````` + +`Artists` draw onto objects of type :class:`~OffsetBox` +through :class:`~OffsetBox.DrawingArea` and :class:`~OffsetBox.TextArea`. +The `TextArea` calculates the required space for the text and so the +text is always within the bounds, for this nothing has changed. + +However, `DrawingArea` acts as a parent for zero or more `Artists` that +draw on it and may do so beyond the bounds. Now child `Artists` can be +clipped to the bounds of the `DrawingArea`. diff --git a/examples/pylab_examples/anchored_artists.py b/examples/pylab_examples/anchored_artists.py index f8600d0cfec4..af4585f819fe 100644 --- a/examples/pylab_examples/anchored_artists.py +++ b/examples/pylab_examples/anchored_artists.py @@ -63,7 +63,7 @@ class AnchoredDrawingArea(AnchoredOffsetbox): def __init__(self, width, height, xdescent, ydescent, loc, pad=0.4, borderpad=0.5, prop=None, frameon=True): - self.da = DrawingArea(width, height, xdescent, ydescent, clip=True) + self.da = DrawingArea(width, height, xdescent, ydescent) super(AnchoredDrawingArea, self).__init__(loc, pad=pad, borderpad=borderpad, child=self.da, diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index c2cf103d53ca..814d73290f37 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1416,6 +1416,7 @@ def tk_window_focus(): 'matplotlib.tests.test_lines', 'matplotlib.tests.test_mathtext', 'matplotlib.tests.test_mlab', + 'matplotlib.tests.test_offsetbox', 'matplotlib.tests.test_patches', 'matplotlib.tests.test_path', 'matplotlib.tests.test_patheffects', diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index 47a402911b37..691076c42ae0 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -24,6 +24,7 @@ import matplotlib.transforms as mtransforms import matplotlib.artist as martist import matplotlib.text as mtext +import matplotlib.path as mpath import numpy as np from matplotlib.transforms import Bbox, BboxBase, TransformedBbox @@ -569,14 +570,16 @@ 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 parent is fixed. The children can be clipped at the + boundaries of the parent. """ def __init__(self, width, height, xdescent=0., - ydescent=0., clip=True): + 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(DrawingArea, self).__init__() @@ -585,6 +588,7 @@ def __init__(self, width, height, xdescent=0., self.height = height self.xdescent = xdescent self.ydescent = ydescent + self._clip_children = clip self.offset_transform = mtransforms.Affine2D() self.offset_transform.clear() @@ -656,7 +660,17 @@ def draw(self, renderer): self.dpi_transform.clear() self.dpi_transform.scale(dpi_cor, 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.)) diff --git a/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.pdf b/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.pdf new file mode 100644 index 0000000000000000000000000000000000000000..18e1169d303f1c2199db8013cfc079ee9325caa0 GIT binary patch literal 1584 zcmZ`(eQZ-z6bDf;4?+-0m~>k%(Yg+|zIWTcevmQU+P!5Nu+h#a=)~9dwkzx2YhQ0i zhYn^8Y!N}o*dIn_qN1V@&DbzzMqI+gj|>)_E|IVj!6wdRi}(@T490t3`{>rj_s@Ix z_s%)@oZtDKqi=Kx<5(Z}VJDU#V2%r^EtH|*PprtrHfOVnv zkPo?Q$z|l!q=-^1A}UDob!erg?JFy3t>gzz7t63)kv}9>%L$S?4bR)Gz+yJh9IGTr z#xa7EZ#Rm`am7bCqMW$3pp-U-b&_OB#29d>|FQ|wvzD(A3KPNrD}+haF65K_g#Rr_ z3gQ+3X=ha8V^~3A8GOa^b|_dMoR?JUkPW_HVNkSJ$jNx$o#$bML+fI=`EU z{eD}&V@=V+ZqFO*w*S4c_t2KH)^Ea7CofIEH~Z_K$>jJ{&;Ha8 z{nzTsE`9!P%DvNARJ?1|_>n;8Cr2Oa+B5sviqYaxbM(1K2iAbedkeNRUz~GZJYIjQ zu;<(|;kYw8Q*iKWVV7fd>+)9&-Pf=DgwB@rEgNVb=}`B-ua}mxl{f$e#@~wIa84WLPxJ``&ZY$^l&m7p_obJ+6j9F9CY z$U!2M9AC~#nwW!`?fICABP%Ay&Th`T7jljTbznsdg^{AJ_Btsb1GQ^pPq}58He20U bp3q)I2%{v%u&7{_LTIw^j9y>UATa*{x^wf< literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.png b/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.png new file mode 100644 index 0000000000000000000000000000000000000000..3b62fc31883bb3412b8de31100c9534445c1b0dd GIT binary patch literal 3969 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i1B%QlYbpRzEX7WqAsj$Z!;#Vf4nJ@F#*W;|lxbnLviJr;B4q#hkadH|9n^mur7`Uc8A>nN^y9!8P5;RHpL<3B3>6 z=DHUwS5vllz$4|9bGM4YT!)Dw&9SzGJ*=btk& zFbMoO{{~1MsNxV{U{FwYU|?wIVE_skF|jZ(oER0v79hoj)eQAN{`~yBW9LrE>C>k_ zKH4q5`8fl_hu+=0cYl0&dHKg*UtfRx^z`)2VmVdDPYrkO-04}pdi70X28JKp@^&>6 z`SC>kYk6-hDSslLqS)by@`|po`{rdHdq|2S^STM4+tNUZIZr!?n z-*@)z_iwOK*uTH*>#M7)H{Q)tU%zm}&73<`t}wN=d-VSQ|Lp&Qfx*G80ho>?8JQRu zQdl^Aw&o@cte++c(WHDRbxP!prXSmcOpiFfj_~U8){XOOH@131@Q2E2ZzrR0z zdwUyLtS=K#VE8{tKYm}&{kq?|-+1I2f$TH$7z}#DH;`MQ#+;F3_z+@K|F7oHm&^W} z(?4*4*xy(hGFdnd?0@q1_V&lm=hx4(ZFW!Ce)}wCb`92Qp2(7nL|e{#i56wrPDIxY&C9 z?OR#S_S-jst?wUg=d=IMAwT~P);u+`n{^3`4nH)gW>Q3I`X(st-*D~d-s@oW@Hi)0 zKS+Et`-gU5yWa4aguH#-oINe4UY1B5e_Z&DN9o;OJus5nU+?$(Z?Sa!eOPL$`}_5J zVQHypHL#ag|L|}-|Kqc>%{QmpcmXTSpTu??K#li5!d9c1op5Fc7l}Xhll;GY%*a+g R11x?SJYD@<);T3K0RUCiPmurs literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.svg b/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.svg new file mode 100644 index 000000000000..dda77394fef2 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_offsetbox/offsetbox_clipping.svg @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_offsetbox.py b/lib/matplotlib/tests/test_offsetbox.py new file mode 100644 index 000000000000..1c421d4c0b1c --- /dev/null +++ b/lib/matplotlib/tests/test_offsetbox.py @@ -0,0 +1,49 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import nose + +from matplotlib.testing.decorators import image_comparison +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +import matplotlib.lines as mlines +from matplotlib.offsetbox import AnchoredOffsetbox, DrawingArea + + +@image_comparison(baseline_images=['offsetbox_clipping'], remove_text=True) +def test_offsetbox_clipping(): + # - create a plot + # - put an AnchoredOffsetbox with a child DrawingArea + # at the center of the axes + # - give the DrawingArea a gray background + # - put a black line across the bounds of the DrawingArea + # - see that the black line is clipped to the edges of + # the DrawingArea. + fig, ax = plt.subplots() + size = 100 + da = DrawingArea(size, size, clip=True) + bg = mpatches.Rectangle((0, 0), size, size, + facecolor='#CCCCCC', + edgecolor='None', + linewidth=0) + line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2], + color='black', + linewidth=10) + anchored_box = AnchoredOffsetbox( + loc=10, + child=da, + pad=0., + frameon=False, + bbox_to_anchor=(.5, .5), + bbox_transform=ax.transAxes, + borderpad=0.) + + da.add_artist(bg) + da.add_artist(line) + ax.add_artist(anchored_box) + ax.set_xlim((0, 1)) + ax.set_ylim((0, 1)) + + +if __name__ == '__main__': + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/mpl_toolkits/axes_grid1/anchored_artists.py b/lib/mpl_toolkits/axes_grid1/anchored_artists.py index bfcfeea344a7..17881eba5a55 100644 --- a/lib/mpl_toolkits/axes_grid1/anchored_artists.py +++ b/lib/mpl_toolkits/axes_grid1/anchored_artists.py @@ -24,7 +24,7 @@ def __init__(self, width, height, xdescent, ydescent, *prop* : font property. This is only used for scaling the paddings. """ - self.da = DrawingArea(width, height, xdescent, ydescent, clip=True) + self.da = DrawingArea(width, height, xdescent, ydescent) self.drawing_area = self.da super(AnchoredDrawingArea, self).__init__(loc, pad=pad, borderpad=borderpad,