Search code examples
pythonmatplotlibannotations

How to align two matplotlib text-boxes side-by-side in the top-right corner?


I am trying to put two text-boxes side-by-side in the top-right corner of a matplotlib figure. I followed this tutorial on text-alignment and can place one text-box in the top-right corner. But I do not know how to add a second text-box such that the right-edge of the top-left box intersects the left-edge of the top-right box.

MWE Figure

The code to output run the minimal working example is below:

import numpy as np
import matplotlib.pyplot as plt

# data
x = np.linspace(-10, 10, 51)
shrink_factors = np.linspace(1, 0, x.size)
y1 = shrink_factors*np.sin(np.exp(-x))
y2 = shrink_factors*np.cos(np.exp(-x))

# get plot parameters
xlim = [np.min(x), np.max(x)]
ylim = [0, 1.125*np.max([np.max(y1), np.max(y2)])]
facecolors = ("red", "blue")
(color1, color2) = facecolors
label1 = "Label 1"
label2 = "Label 2"
text1 = "RED 1"
text2 = "BLUE 2"
text_background_color = "gainsboro"
text_size = 12
figsize = (12, 7)
# figsize = (7, 12)

# initialize plot
fig, ax = plt.subplots(
    figsize=figsize)

# plot data
ax.plot(x, y1, color=color1, label=label1)
ax.plot(x, y2, color=color2, label=label2)
ax.grid(color="black", linestyle=":", alpha=0.3)
ax.set_xlim(xlim)
ax.set_ylim(ylim)
fig.legend(mode="expand", loc="lower center", ncol=2)

# add text-boxes side-by-side
text_box1 = ax.text(0.95, 0.95, text1, 
                    color=color1, 
                    fontsize=text_size,
                    horizontalalignment="right", 
                    verticalalignment="top", 
                    transform=ax.transAxes)
text_box1.set_bbox({"facecolor": text_background_color, "edgecolor": "black"})
text_box1_pos = text_box1.get_position()
text_box2 = ax.text(text_box1_pos[0], 0.95, text2, 
                    color=color2, 
                    fontsize=text_size, 
                    horizontalalignment="left", 
                    verticalalignment="top", 
                    transform=ax.transAxes)
text_box2.set_bbox({"facecolor": text_background_color, "edgecolor": "black"})

# finish plot
plt.show()
plt.close()

Solution

  • I had encountered a similar issue some time back. Here is what I did... This is based on some of the info provide in another post here. Given below are the updates to your code...

    1. Update the x position of the first text box to 0.9, so that both the labels are within the plot
    2. Once the first text box is plotted, add the below code.. this is similar what is explained in the post I mentioned. Note that I am using transAxes so that we have same coordinates everywhere. More info here. Note the bb_datacoords will now have the x and y coordinates of the first textbox
    #text_box1_pos = text_box1.get_position()  --> You code, not required anymore
    
    r = fig.canvas.get_renderer()
    transf = ax.transAxes.inverted()
    bb = text_box1.get_window_extent(renderer = r)
    bb_datacoords = bb.transformed(transf)
    
    1. Use the x1 position from bbox as the start point for the second text box, like below...
        bb_datacoords.x1+0.01, #text_box1_pos[0],
    

    Note that, as mentioned in the above SO answer, the exact position is not known till the plot is done, so there is a small delta of 0.01 that you can add to make them non-overlapping. You can increase it (to say 0.02) if you want to see a small gap in between the two boxes.

    The resulting plot looks like this... Hope this helps.

    enter image description here