在 matplotlib 中剪切注释

Clipping annotations in matplotlib

有没有办法为 matplotlib.text.Annotation 设置剪辑边界?

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

fig, ax = plt.subplots()
vals = [
        (10,20,"Lorem ipsum dolor sit amet, consectetur adipiscing elit"),
        (30,20, "Pellentesque scelerisque congue fermentum."),
        (50,10, "Aliquam erat volutpat")
]
ax.set_ylim(0,1)
ax.broken_barh(map(lambda v: v[:2],vals), (0, 1), facecolors=('yellow','red','green'))
for v in vals:
    ax.annotate(
        v[2], 
        xy=(v[0],0.4), 
        clip_on=True, 
        clip_box=Bbox([[v[0],0],[v[0]+v[1],1]])
    )

plt.show()

我尝试在 ax.annotate 调用上设置 clip_boxclip_path,但这不起作用。

首先,问得好!您正在深入细节,因此您需要了解相当多的 semi-undocumented 细节。

除了以下两点,您当前的方法是可行的:

  1. clip_box 应该在显示坐标中,因此您需要对其进行转换。
  2. 为此使用 "plain" Bbox 是不够的。每次显示更改时,要将数据坐标转换为 re-done,您需要使用 TransformedBbox.

为了进一步说明发生了什么,让我们解决第一个问题,而不是第二个问题:

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

fig, ax = plt.subplots()
vals = [
        (10,20,"Lorem ipsum dolor sit amet, consectetur adipiscing elit"),
        (30,20, "Pellentesque scelerisque congue fermentum."),
        (50,10, "Aliquam erat volutpat")
]
ax.set_ylim(0,1)
ax.broken_barh(map(lambda v: v[:2],vals), (0, 1), 
              facecolors=('yellow','red','green'))
for v in vals:
    box = Bbox([[v[0],0],[v[0]+v[1],1]]).transformed(ax.transData)
    anno = ax.annotate(v[2], xy=(v[0],0.4), clip_box=box)

plt.show()

一开始看起来不错,但还有一个问题:因为 clip_box 是静态的并且在显示坐标中,所以一旦您以任何方式更改绘图(例如,缩放、平移、自动缩放、更改图形 window 大小、更改图形 DPI)。

例如,如果我点击 "save" 按钮(或调用 savefig),剪辑框将不正确,因为图形的 DPI 将在之前更改(从 80 到 100)节省!

因此,您需要为此使用 TransformedBbox。它只是 Bbox 的一个版本,它保存对转换的引用,如果转换发生变化,则 re-transforms 底层 Bbox 。在 matplotlib 中有一些 Transformed* 类 沿着这些线:例如TransformedPath,等等。它们不是您总是需要了解的详细程度,但它们可能非常有用。

作为最后一个例子,无论情节如何,它都会正常工作 zoomed/panned/changed:

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

fig, ax = plt.subplots()
vals = [
        (10,20,"Lorem ipsum dolor sit amet, consectetur adipiscing elit"),
        (30,20, "Pellentesque scelerisque congue fermentum."),
        (50,10, "Aliquam erat volutpat")
]
ax.set_ylim(0,1)
ax.broken_barh(map(lambda v: v[:2],vals), (0, 1),
              facecolors=('yellow','red','green'))
for v in vals:
    box = TransformedBbox(Bbox([[v[0],0],[v[0]+v[1],1]]), ax.transData)
    anno = ax.annotate(v[2], xy=(v[0],0.4), clip_box=box)

plt.show()