2

Background

In Matplotlib, we can render the string using mathtext as a marker using $ ..... $ (Reference 1)

Question

Is there any way to enclose this text in a circular or rectangular box, or any different different shape? Similar to the registered symbol as shown here

I want to use this marker on a plot as shown below: Text '$T$' is used in this plot, I want the text to be enclosed in a circle or rectangle

Text '$T$' is used in this plot, I want the text to be enclosed in a circle or rectangle.

Solution

As suggested in the comments of the answer, I have plotted a square marker of a bit larger size before the text marker. This resolved the issue. The final figure is shown below:

Neeraj Hanumante
  • 1,575
  • 2
  • 18
  • 37

1 Answers1

3

Edit: Easiest way is to simply place patches to be the desired "frames" in the same location as the markers. Just make sure they have a lower zorder so that they don't cover the data points.

More sophisticated ways below:

You can make patches. Here is an example I used to make a custom question mark:

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()

Example after some more work

Edit, second answer: creating a custom patch made of other patches:

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()

enter image description here

p479h
  • 169
  • 1
  • 8
  • Then things become more complicated because you have to create this custom marker as a patch. Things get tricky there because each patch will have positions, edges, centers and vertices defined a litte bit differently. If you really want to do it, it is a good Idea to read the source code of the patches you plan on using. I am also going to add another answer with an example where I created one custom patch. – p479h Jun 19 '20 at 09:32
  • 1
    Or you can simply place rectangles in the same positions as the "T" 's with lower `zorder` to make sure they are below the "T" 's. – p479h Jun 19 '20 at 09:40