4

I am testing the clip_box feature of Artist using the code snippet below:

import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
import numpy as np

fig = plt.figure()
ax = fig.subplots(1, 2)

x = [1, 2, 3, 4]
y = [3, 8, 5, 2]

line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)

boundingbox = Bbox(np.array([[0, 0], [3, 9]]))
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)

plt.show()

What I expect is the last part of line_b will be cut out by the clip box, and line_b will be a bit shorter than line_a.

It turns out that there's nothing left on the second subplot. It's totally empty. Is my understanding of the clip_box wrong or are there some issues in the code snippet?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
olddon
  • 41
  • 3

2 Answers2

2

I've spent some time reading about Bboxes in Matplotlib and they are pretty complicated. The set_clip_box method you refer to has not got very helpful documentation, and the examples of its use both use the bbox of an Axes, which is a nested transformation; ie _, ax = plt.subplots(); ax.bbox is a TransformedBbox based on a linear transform of another TransformedBbox based on an Affine2D transform of a plain Bbox! (All of this explained in more detail here.)

It seems that these involve transformations between different sets of co-ordinates; in the case of a regular Axes it is between x- and y-values, pixels, and the specific adaptations to screen size. I would be happy to hear from someone who knows more about Bboxes why your Bbox acts the way it does. But what you want to achieve can be done much more easily, using a FancyBboxPatch (a Rectangle patch would work just as well):

from matplotlib.patches import FancyBboxPatch

f, ax = plt.subplots(1, 2)

x = [1, 2, 3, 4]
y = [3, 8, 5, 2]

line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)


bb = Bbox([[0, 0], [3, 9]])
ax[1].add_patch(FancyBboxPatch((bb.xmin, bb.ymin), bb.width, bb.height, boxstyle="square",
                               ec='white', fc='white', zorder=2.1))

Enter image description here

(ec and fc are edge colour and fill colour; zorder determines the artist order. Lines are 2, so we just need out Bbox patch to be slightly higher.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Josh Friedlander
  • 10,870
  • 5
  • 35
  • 75
2

The "natural" clip box for the right hand side plot is ax[1].bbox. Finding its extent tells us what units should be used to specify the clip box Bbox.

Since we don't add the Bbox instance to any axes when we create, it could only be relative to the figure. When we print ax[1].bbox, we can see that its size is to be specified in pixels.

It's indeed much simpler to use a Rectangle or Polygon to specify the clip box because they can be added to axes. Using 'none' color for its facecolor could be more convenient because it's figure style-independent.

import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox

fig = plt.figure(dpi=89)
ax = fig.subplots(1, 2)

x = [1, 2, 3, 4]
y = [3, 8, 5, 2]

line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)

print(ax[1].bbox, '\n', ax[1].bbox.extents)
# the line above prints
# TransformedBbox(
#     Bbox(x0=0.5477272727272726, y0=0.10999999999999999, x1=0.8999999999999999, y1=0.88),
#     BboxTransformTo(
#         TransformedBbox(
#             Bbox(x0=0.0, y0=0.0, x1=6.393258426966292, y1=4.797752808988764),
#             Affine2D().scale(178.0))))
# [ 623.31363636   93.94       1024.2         751.52      ]
# 178.0 is 2 * dpi, I believe the doubling happens because of what screen I have got

boundingbox =  Bbox.from_extents([623.31363636, 93.94, 900.2, 751.52])
print(boundingbox, '\n', boundingbox.extents)
# the line above prints
# Bbox(x0=623.31363636, y0=93.94, x1=900.2, y1=751.52) 
# [623.31363636  93.94       900.2        751.52      ]
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)

plt.show()
Yulia V
  • 3,507
  • 10
  • 31
  • 64
  • thanks! 2 quick questions: 1) I get a different output for `ax[1].bbox`, did you use a different command? 2) is there any case where I would want to use a clip_box? – Josh Friedlander Oct 26 '22 at 22:25
  • @JoshFriedlander: thanks for the bounty :) 1) Assuming you use the same dpi, its possibly because you have got a different screen (I have got a MacBook pro with Retina screen). I don't think the exact number of pixels matters, you just take what you have got, and map (transform) it to the axes' coordinates. If it's unclear, I can add a quick example later. – Yulia V Oct 27 '22 at 13:16
  • 2) I have used clip boxes for drawing (you can do pretty drawings with matplotlib!) and to make sure subordinate axes (axes on axes) stay within the parent axes box. – Yulia V Oct 27 '22 at 13:19
  • thanks for that. re 1), I agree that I probably have a different dpi, but specifically I meant that `ax[1].bbox` returns just the object repr, ie ``, not any more detail – Josh Friedlander Oct 27 '22 at 19:36
  • 1
    @JoshFriedlander - probably IDE-specific. I use VSC, and this is how my printout looks. The other way is to debug and expand the `ax[1].bbox` object – Yulia V Oct 27 '22 at 19:58