1

It's my first time asking a question here. So please tell me if anything is amiss.

So I’m trying to create a dataset of synthetically generated charts to train a neural net to find bounding boxes for different elements of a chart - legend box, chart title, axes labels, etc. That's the part I've managed to do.

Next what I need is to create a mapping from different legend entries to their corresponding datapoints. I need to create annotations for bounding boxes around the different handles and text like this:

Please see an example here

I've tried looking around the docs, but there can't find any related functionality. Looking into properties of legend using matplotlib.artist.getp() also got me nothing on this.

fig, ax = plt.subplots(figsize=(12, 4))
x_vals = np.linspace(0, 5, 5)
y_vals = np.random.uniform(size=(5,))

ax.plot(x_vals, y_vals, label='line1')
ax.plot(x_vals, y_vals + np.random.randn(), label='line2')
leg = ax.legend()
ax.set_label('Label via method')

matplotlib.artist.getp(leg)

Output:
    agg_filter = None
    alpha = None
    animated = False
    bbox_to_anchor = TransformedBbox(     Bbox(x0=0.125, y0=0.125, x1=0...
    children = [<matplotlib.offsetbox.VPacker object at 0x7f3582d...
    clip_box = None
    clip_on = True
    clip_path = None
    contains = None
    default_handler_map = {<class 'matplotlib.container.StemContainer'>: <ma...
    figure = Figure(864x288)
    frame = FancyBboxPatch(640.55,203.64;60.625x33)
    frame_on = True
    gid = None
    label = 
    legend_handler_map = {<class 'matplotlib.container.StemContainer'>: <ma...
    lines = [<matplotlib.lines.Line2D object at 0x7f35834f4400...
    patches = <a list of 0 Patch objects>
    path_effects = []
    picker = None
    rasterized = None
    sketch_params = None
    snap = None
    texts = <a list of 2 Text objects>
    title = Text(0,0,'None')
    transform = IdentityTransform()
    transformed_clip_path_and_affine = (None, None)
    url = None
    visible = True
    window_extent = Bbox(x0=640.5500000000001, y0=203.64, x1=701.17500...
    zorder = 5

Any help would be appreciated. Please tell me if any clarification is needed. Thanks

1 Answer 1

1

I spent a solid 30min poking around trying to figure something out.. I'm sure there's an easier way but here's something I whipped up in the hopes it helps you

import matplotlib.pyplot as plt; plt.ion()
from matplotlib.patches import Rectangle as rect

fig, ax = plt.subplots()
ax.plot([0,1],[4,6],label='test')
leg = ax.legend(edgecolor='w', loc='upper left')
leg.get_frame().set_alpha(0)
line = leg.get_lines()[0]
plt.draw()
plt.pause(0.001)

#all of this is based on pixel coordinates in the figure
(lx0, ly0, lx1, ly1) = (leg.get_window_extent().x0,
                       leg.get_window_extent().y0,
                       leg.get_window_extent().x1,
                       leg.get_window_extent().y1)
(mx0, my0, mx1, my1) = (line.get_window_extent(fig).x0,
                       line.get_window_extent(fig).y0,
                       line.get_window_extent(fig).x1,
                       line.get_window_extent(fig).y1)
(ax0, ay0, ax1, ay1) = (ax.get_window_extent().x0,
                       ax.get_window_extent().y0,
                       ax.get_window_extent().x1,
                       ax.get_window_extent().y1)
#convert pixel coords to graphical coords
x0, x1 = ax.get_xlim()
y0, y1 = ax.get_ylim()
ratex = (x1-x0) / (ax1-ax0)
ratey = (y1-y0) / (ay1-ay0)
newx0 = (mx0 - ax0) * ratex + x0
newx1 = (mx1 - ax0) * ratex + x0
newy0 = (my0 - ay0) * ratey + y0 - 0.05
newy1 = (my1 - ay0) * ratey + y0 + 0.05
#box around legend marker
ax.add_patch(rect((newx0, newy0), newy1-newy0, newx1-newx0, edgecolor='k', alpha=0.5, facecolor='w'))
#convert pixel coords to graphical coords
tx0 = mx1
tx1 = lx1
ty0 = ly0
ty1 = ly1
newx0 = (tx0 - ax0) * ratex + x0
newx1 = (tx1 - ax0) * ratex + x0
newy0 = (ty0 - ay0) * ratey + y0
newy1 = (ty1 - ay0) * ratey + y0
#box around legend txt
ax.add_patch(rect((newx0, newy0), newy1-newy0, newx1-newx0, edgecolor='k', alpha=0.5, facecolor='w'))

this produces the following plot: plot

I'm not 100% sure why the boxes are off but you could modify this to make it work somehow...you could also use this to get coordinates for the legend entries, and draw a vector from those coordinates to coordinates on the corresponding data lines

2
  • 1
    Thanks a lot for the initial direction. I was able to get more precise coordinates using leg._legend_handle_box.get_children() followed by get_window_extent() for each element as mentioned in this answer: stackoverflow.com/a/44072076/13430696
    – dunkelheit
    Commented Jul 7, 2021 at 13:57
  • lmao what a mission..glad you figured it out
    – Derek Eden
    Commented Jul 7, 2021 at 15:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.