ThePredator - 1 year ago 82

Python Question

As of now I am using Matplotlib to generate plots.

The legend on the plot can be tweaked using some parameters (as mentioned in this guide). But I would like to have something specific in the legend, as attached in this image below.

I would like to **highlight one of the labels in the legend** like shown (as of now done using MS paint).

If there are other ways of highlighting a specific label, that would also suffice.

Answer Source

The answer by FLab is actually quite reasonable given how painful it can be to backtrace the coordinates of the plotted items. However, the demands of publication-grade figures are quite often unreasonable, and seeing `matplotlib`

challenged by MS Paint is a enough good motivation for answering this.

Lets consider this example from the matplotlib gallery as a starting point:

```
N = 100
x = np.arange(N)
fig = plt.figure()
ax = fig.add_subplot(111)
xx = x - (N/2.0)
plt.plot(xx, (xx*xx)-1225, label='$y=x^2$')
plt.plot(xx, 25*xx, label='$y=25x$')
plt.plot(xx, -25*xx, label='$y=-25x$')
legend = plt.legend()
plt.show()
```

Once an image has been drawn, we can backtrace the elements in the `legend`

instance to find out their coordinates. There are two difficulties associated with this:

- The coordinates we'll get through the
`get_window_extent`

method are in pixels, not "data" coordinates, so we'll need to use a transform function. A great overview of the transforms is given here. - Finding a proper boundary is tricky. The
`legend`

instance above has two useful attributes,`legend.legendHandles`

and`legend.texts`

- two lists with a list of line artists and text labels respectively. One would need to get a bounding box for both elements, while keeping in mind that the implementation might not be perfect and is backend-specific (c.f. this SO question). This is a proper way to do this, but it's not the one in this answer, because...

.. because luckily in your case the legend items seem to be uniformly separated, so we could just get the legend box, split it into a number of rectangles equal to the number of rows in your legend, and draw one of the rectangles on-screen. Below we'll define two functions, one to get the data coordinates of the legend box, and another one to split them into chunks and draw a rectangle according to an index:

```
from matplotlib.patches import Rectangle
def get_legend_box_coord(ax, legend):
""" Returns coordinates of the legend box """
disp2data = ax.transData.inverted().transform
box = legend.legendPatch
# taken from here:
# http://stackoverflow.com/a/28728709/4118756
box_pixcoords = box.get_window_extent(ax)
box_xycoords = [disp2data(box_pixcoords.p0), disp2data(box_pixcoords.p1)]
box_xx, box_yy = np.array(box_xycoords).T
return box_xx, box_yy
def draw_sublegend_box(ax, legend, idx):
nitems = len(legend.legendHandles)
xx, yy = get_legend_box_coord(ax, legend)
# assuming equal spacing between legend items:
y_divisors = np.linspace(*yy, num=nitems+1)
height = y_divisors[idx]-y_divisors[idx+1]
width = np.diff(xx)
lower_left_xy = [xx[0], y_divisors[idx+1]]
legend_box = Rectangle(
xy = lower_left_xy,
width = width,
height = height,
fill = False,
zorder = 10)
ax.add_patch(legend_box)
```

Now, calling `draw_sublegend_box(ax, legend, 1)`

produces the following plot:

Note that annotating the legend in such is way is only possible once the figure has been drawn.