【问题标题】:Add a line to matplotlib annotations在 matplotlib 注释中添加一行
【发布时间】:2021-04-04 06:31:53
【问题描述】:

我有一个 matplotlib 图,其中某些点被注释。我已经弄清楚了如何自己做注释,包括箭头和一切。但是,我需要在每个注释的文本旁边添加一行。它应该与文本平行运行,与文本有一定的偏移,以点为单位。线的长度基于每个带注释的点所具有的百分比值。理想情况下,我想要一条长度始终相同的行(大约 15 个文本字符,这是注释中文本的最大长度),但根据提到的百分比值,可以说是红色和灰色部分。 非常感谢任何帮助或建议。

编辑: 以下是一些模拟数据点的最小示例:

import numpy as np
import matplotlib.pyplot as plt

x=[2, 3, 4, 6, 7, 8, 10, 11]
y=[1, 3, 4, 2, 3, 1, 5, 2]
tx=[3, 4, 5, 6, 7, 8, 9, 10]
yd=dict(zip(x, y))

plt.scatter(x, y)
plt.xlim(0, 14)
plt.ylim(0, 8)

tspace=list(np.linspace(.05, .95, len(tx)))
tsd=dict(zip(tx, tspace))

arpr = {"arrowstyle": "-",
        "connectionstyle": "arc,angleA=-90,armA=20,angleB=90,armB=20,rad=10"}
for i, j in zip(x, tx):
    plt.annotate("foo bar baz", (i, yd[i]), (tsd[j], .75),
              textcoords="axes fraction", arrowprops=arpr,
              annotation_clip=False, rotation="vertical")

这是当前输出与所需输出的比较:

【问题讨论】:

  • 您能否提供一张图片来显示您要查找的内容?您编写或尝试过的任何代码都会非常有用
  • @DerekO 添加了代码和图片
  • 酷!我去看看

标签: python matplotlib annotations


【解决方案1】:

您可以使用plt.Rectangle 来绘制条形图——首先是整个条形高度的灰色条形,然后是整个条形高度百分比的红色条形。

但是,由于矩形的宽度和长度参数以绘图上的 x 和 y 坐标为单位,因此我们需要能够访问您所做的文本注释的坐标。

您使用textcoords="axes fraction" 设置注释坐标,这使得在x 和y 坐标中访问矩形的开始和结束坐标变得很困难,因此我定义了一些常量x_min, x_max, y_min, y_max 来限制绘图,然后直接从tspace 列表以及条形注释计算文本注释的坐标。

可以在列表中设置每个条形的红色空间百分比,以便通用。

import numpy as np
import matplotlib.pyplot as plt

x=[2, 3, 4, 6, 7, 8, 10, 11]
y=[1, 3, 4, 2, 3, 1, 5, 2]
tx=[3, 4, 5, 6, 7, 8, 9, 10]
yd=dict(zip(x, y))

fig,ax = plt.subplots(1,1)
plt.scatter(x, y)
x_min, x_max = 0, 14
y_min, y_max = 0, 8
y_text_end = 0.75*(y_max-y_min)
plt.xlim(0, 14)
plt.ylim(0, 8)

tspace=list(np.linspace(.05, .95, len(tx)))
# tsd=dict(zip(tx, tspace))

# random percentage values to demonstrate the bar functionality
bar_percentages = [0.95, 0.9, 0.8, 0.6, 0.4, 0.2, 0.1, 0.05]
bar_width = 0.2
bar_height = 1.9

arpr = {"arrowstyle": "-",
        "connectionstyle": "arc,angleA=-90,armA=20,angleB=90,armB=20,rad=10"}

## axes fraction is convenient but it's important to be able to access the exact coordinates for the Rectangle function
for i, x_val in enumerate(x):
    plt.annotate("foo bar baz", (x_val, yd[x_val]), (tspace[i]*(x_max-x_min), y_text_end),
    arrowprops=arpr, annotation_clip=False, rotation="vertical")

    bar_grey = plt.Rectangle((tspace[i]*(x_max-x_min)+0.4, y_text_end-0.1), bar_width, bar_height, fc='#cccccc')
    bar_red = plt.Rectangle((tspace[i]*(x_max-x_min)+0.4, y_text_end-0.1), bar_width, bar_percentages[i]*bar_height, fc='r')
    plt.gca().add_patch(bar_grey)
    plt.gca().add_patch(bar_red)

plt.show()

【讨论】:

  • 这行得通,谢谢,但前提是纵横比或缩放没有变化。应该在原始帖子中提到放大是一个问题。注释的文本可以很好地缩放(即无论缩放如何,字体大小都保持不变),我想知道是否有办法在框中获得类似的行为。理想情况下,它们将链接在一起,据我了解,plt 注释作为容器对象(文本、箭头等)工作,所以也许可以将框添加到其中?
  • 编辑:我自己解决了,至少对我来说是一个“足够好”的解决方案。我将在下面作为单独的答案分享它。仍然感谢您的意见。
【解决方案2】:

我已经找到了一个解决方案,尽管它很老套,并且没有理想的“灰盒”,但这对我的目的来说很好,如果它可以帮助某人,我会在这里分享。如果有人知道改进,请随时贡献。感谢@DerekO 提供了useful input,我将其合并到我的解决方案中。

本文改编自thismatplotlib 演示。我只是将自定义框移到文本之外,并使用百分比的附加参数修改了宽度和高度。我不得不将它拆分为两个实际的注释,因为使用自定义框的箭头不会从正确的位置开始,但是这样可以正常工作。缩放/缩放现在表现良好并跟随文本。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import BoxStyle

class MyStyle(BoxStyle._Base):
    def __init__(self, pad, per=1.):
        self.pad = pad
        self.per = per
        super().__init__()

    def transmute(self, x0, y0, width, height, mutation_size):
        # padding
        pad = mutation_size * self.pad

        # width and height with padding added.
        width = width + 2.*pad
        width *= self.per
        height = 8.
        # boundary of the padded box
        x0, y0 = x0-pad, y0-pad,
        x1, y1 = x0+width, y0-height

        cp = [(x0, y0),
              (x1, y0),
              (x1, y1),
              (x0, y1),
              (x0, y0)]

        com = [Path.MOVETO,
               Path.LINETO,
               Path.LINETO,
               Path.LINETO,
               Path.CLOSEPOLY]

        path = Path(cp, com)

        return path


# register the custom style
BoxStyle._style_list["percent"] = MyStyle

x=[2, 3, 4, 6, 7, 8, 10, 11]
y=[1, 3, 4, 2, 3, 1, 5, 2]
tx=[3, 4, 5, 6, 7, 8, 9, 10]
yd=dict(zip(x, y))

fig,ax = plt.subplots(1,1)
plt.scatter(x, y)
x_min, x_max = 0, 14
y_min, y_max = 0, 8
y_text_end = 0.75*(y_max-y_min)
plt.xlim(0, 14)
plt.ylim(0, 8)

tspace=list(np.linspace(.05, .95, len(tx)))
# tsd=dict(zip(tx, tspace))

# random percentage values to demonstrate the bar functionality
bar_percentages = [0.95, 0.9, 0.8, 0.6, 0.4, 0.2, 0.1, 0.05]

arpr = {"arrowstyle": "-",
        "connectionstyle": "arc,angleA=-90,armA=20,angleB=90,armB=20,rad=10"}

## axes fraction is convenient but it's important to be able to access the exact coordinates for the Rectangle function
for i, x_val in enumerate(x):
    plt.annotate("", (x_val, yd[x_val]), (tspace[i]*(x_max-x_min), y_text_end),
                 arrowprops=arpr, annotation_clip=False, rotation="vertical",)
    plt.annotate("foo bar baz", (x_val, yd[x_val]), (tspace[i]*(x_max-x_min), y_text_end),
                 annotation_clip=False, rotation="vertical",
                 va="bottom", ha="right",
                 bbox=dict(boxstyle=f"percent,pad=.2,per={bar_percentages[i]}",
                           fc="red",
                           ec="none"))

del BoxStyle._style_list["percent"]

plt.show()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    相关资源
    最近更新 更多