Search code examples
pythondrawingplotmatplotliblabel

How can I make the xtick labels of a plot be simple drawings?


Instead of words or numbers being the tick labels of the x axis, I want to draw a simple drawing (made of lines and circles) as the label for each x tick. Is this possible? If so, what is the best way to go about it in matplotlib?


Solution

  • I would remove the tick labels and replace the text with patches. Here is a brief example of performing this task:

    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    
    
    # define where to put symbols vertically
    TICKYPOS = -.6
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(range(10))
    
    # set ticks where your images will be
    ax.get_xaxis().set_ticks([2,4,6,8])
    # remove tick labels
    ax.get_xaxis().set_ticklabels([])
    
    
    # add a series of patches to serve as tick labels
    ax.add_patch(patches.Circle((2,TICKYPOS),radius=.2,
                                fill=True,clip_on=False))
    ax.add_patch(patches.Circle((4,TICKYPOS),radius=.2,
                                fill=False,clip_on=False))
    ax.add_patch(patches.Rectangle((6-.1,TICKYPOS-.05),.2,.2,
                                   fill=True,clip_on=False))
    ax.add_patch(patches.Rectangle((8-.1,TICKYPOS-.05),.2,.2,
                                   fill=False,clip_on=False))
    

    This results in the following figure:

    enter image description here

    It is key to set clip_on to False, otherwise patches outside the axes will not be shown. The coordinates and sizes (radius, width, height, etc.) of the patches will depend on where your axes is in the figure. For example, if you are considering doing this with subplots, you will need to be sensitive of the patches placement so as to not overlap any other axes. It may be worth your time investigating Transformations, and defining the positions and sizes in an other unit (Axes, Figure or display).

    If you have specific image files that you want to use for the symbols, you can use the BboxImage class to create artists to be added to the axes instead of patches. For example I made a simple icon with the following script:

    import matplotlib.pyplot as plt
    
    fig = plt.figure(figsize=(1,1),dpi=400)
    ax = fig.add_axes([0,0,1,1],frameon=False)
    ax.set_axis_off()
    
    ax.plot(range(10),linewidth=32)
    ax.plot(range(9,-1,-1),linewidth=32)
    
    fig.savefig('thumb.png')
    

    producing this image:

    enter image description here

    Then I created a BboxImage at the location I want the tick label and of the size I want:

    lowerCorner = ax.transData.transform((.8,TICKYPOS-.2))
    upperCorner = ax.transData.transform((1.2,TICKYPOS+.2))
    
    bbox_image = BboxImage(Bbox([lowerCorner[0],
                                 lowerCorner[1],
                                 upperCorner[0],
                                 upperCorner[1],
                                 ]),
                           norm = None,
                           origin=None,
                           clip_on=False,
                           )
    

    Noticed how I used the transData transformation to convert from data units to display units, which are required in the definition of the Bbox.

    Now I read in the image using the imread routine, and set it's results (a numpy array) to the data of bbox_image and add the artist to the axes:

    bbox_image.set_data(imread('thumb.png'))
    ax.add_artist(bbox_image)
    

    This results in an updated figure: enter image description here

    If you do directly use images, make sure to import the required classes and methods:

    from matplotlib.image import BboxImage,imread
    from matplotlib.transforms import Bbox