如何在 Matplotlib 中将自定义标记与形状内的文本一起使用?

How to use a cutom marker in Matplotlib with text inside a shape?

背景

在 Matplotlib 中,我们可以使用 $ ..... $ (Reference 1)

使用 mathtext 作为标记来渲染字符串

问题

有什么方法可以将此文本包含在圆形或矩形框内,或任何不同的形状中?类似注册符号如图here

我想在图上使用这个标记,如下所示:

此图中使用了文本“$T$”,我希望将文本括在圆形或矩形中。

解决方案

正如答案评论中所建议的,我在文本标记之前绘制了一个稍大的方形标记。这解决了这个问题。 最终图如下图:

编辑:最简单的方法是将所需的 "frames" 补丁简单地放置在与标记相同的位置。只需确保它们具有较低的 zorder,这样它们就不会覆盖数据点。

下面更复杂的方法:

你可以制作补丁。这是我用来制作自定义问号的示例:

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.markers as m

fig, ax = plt.subplots()
lim = -5.8, 5.7
ax.set(xlim = lim, ylim = lim)

marker_obj = m.MarkerStyle('$?$') #Here you place your letter
path = marker_obj.get_path().transformed(marker_obj.get_transform())

path._vertices = np.array(path._vertices)*8 #To make it larger
patch = mpl.patches.PathPatch(path, facecolor="cornflowerblue", lw=2)
ax.add_patch(patch)

def translate_verts(patch, i=0, j=0, z=None):
    patch._path._vertices = patch._path._vertices + [i, j]

def rescale_verts(patch, factor = 1):
    patch._path._vertices = patch._path._vertices * factor

#translate_verts(patch, i=-0.7, j=-0.1)

circ = mpl.patches.Arc([0,0], 11, 11,
                       angle=0.0, theta1=0.0, theta2=360.0,
                       lw=10, facecolor = "cornflowerblue",
                       edgecolor = "black")
ax.add_patch(circ)#One of the rings around the questionmark

circ = mpl.patches.Arc([0,0], 10.5, 10.5,
                       angle=0.0, theta1=0.0, theta2=360.0,
                       lw=10, edgecolor = "cornflowerblue")
ax.add_patch(circ)#Another one of the rings around the question mark

circ = mpl.patches.Arc([0,0], 10, 10,
                       angle=0.0, theta1=0.0, theta2=360.0,
                       lw=10, edgecolor = "black")
ax.add_patch(circ)



if __name__ == "__main__":
    ax.axis("off")
    ax.set_position([0, 0, 1, 1])
    fig.canvas.draw()
    #plt.savefig("question.png", dpi=40)
    plt.show()

编辑,第二个答案: 创建由其他补丁组成的自定义补丁:

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import mpl_toolkits.mplot3d.art3d as art3d


class PlanetPatch(mpl.patches.Circle):
    """
        This class combines many patches to make a custom patch
        The best way to reproduce such a thing is to read the
        source code for all patches you plan on combining.
        Also make use of ratios as often as possible to maintain
        proportionality between patches of different sizes"""
    cz = 0
    def __init__(self, xy, radius,
                 color = None, linewidth = 20,
                 edgecolor = "black", ringcolor = "white",
                 *args, **kwargs):
        ratio = radius/6
        mpl.patches.Circle.__init__(self, xy, radius,
                                    linewidth = linewidth*ratio,
                                    color = color,
                                    zorder = PlanetPatch.cz,
                                    *args, **kwargs)
        self.set_edgecolor(edgecolor)
        xy_ringcontour = np.array(xy)+[0, radius*-0.2/6]
        self.xy_ringcontour = xy_ringcontour - np.array(xy)
        self.ring_contour = mpl.patches.Arc(xy_ringcontour,
                                15*radius/6, 4*radius/6,
                                angle =10, theta1 = 165,
                                theta2 = 14.5,
                                fill = False, 
                                linewidth = 65*linewidth*ratio/20,
                                zorder = 1+PlanetPatch.cz)

        self.ring_inner = mpl.patches.Arc(xy_ringcontour,
                                 15*radius/6, 4*radius/6,
                                 angle = 10, theta1 = 165 ,
                                 theta2 = 14.5,fill = False,
                                 linewidth = 36*linewidth*ratio/20,
                                 zorder = 2+PlanetPatch.cz)

        self.top = mpl.patches.Wedge([0,0], radius, theta1 = 8,
                                     theta2 = 192,
                                     zorder=3+PlanetPatch.cz)
        self.xy_init = xy
        self.top._path._vertices=self.top._path._vertices+xy

        self.ring_contour._edgecolor = self._edgecolor
        self.ring_inner.set_edgecolor(ringcolor)
        self.top._facecolor = self._facecolor

    def add_to_ax(self, ax):
        ax.add_patch(self)
        ax.add_patch(self.ring_contour)
        ax.add_patch(self.ring_inner)
        ax.add_patch(self.top)


    def translate(self, dx, dy):
        self._center = self.center + [dx,dy]
        self.ring_inner._center = self.ring_inner._center +[dx, dy]
        self.ring_contour._center = self.ring_contour._center + [dx,dy]
        self.top._path._vertices = self.top._path._vertices + [dx,dy]

    def set_xy(self, new_xy):
        """As you can see all patches have different ways
            to have their positions updated"""
        new_xy = np.array(new_xy)
        self._center = new_xy
        self.ring_inner._center = self.xy_ringcontour + new_xy
        self.ring_contour._center = self.xy_ringcontour + new_xy
        self.top._path._vertices += new_xy - self.xy_init 

fig  = plt.figure(figsize=(6, 6))
ax = fig.add_subplot()
lim = -8.5, 8.6
ax.set(xlim = lim, ylim = lim,
       facecolor = "black")
planets = []
colors = mpl.colors.cnames
colors = [c for c in colors]
for x in range(100):
    xy = np.random.randint(-7, 7, 2)
    r = np.random.randint(1, 15)/30
    color = np.random.choice(colors)
    planet = PlanetPatch(xy, r, linewidth = 20,
                         color = color,
                         ringcolor = np.random.choice(colors),
                         edgecolor = np.random.choice(colors))
    planet.add_to_ax(ax)
    planets.append(planet)


fig.canvas.draw()
#plt.savefig("planet.png", dpi=10)
plt.show()