1

I'm new to matplotlib, python and programming in general. I've been looking for an answer to this one for a while now and came up with nothing. Maybe I'm just not asking the question in a way that makes sense.
I have different kinds of plots (plot, scatter, plot_date, bar, etc), with multiple plots per axes, that I'm annotating interactively on mouse hover. So far I've been annotating individual points (or bars). This is what the code looks like (found the annotation part on this answer and modified it):

tabs = [{'SOUTH CAROLINA': 1662, 'IDAHO': 10343, 'VIRGINIA': 9689, 'KANSAS': 3634, 'NEBRASKA': 5024, 'TEXAS': 5134},
    {'SOUTH CAROLINA': 8015, 'IDAHO': 57541, 'VIRGINIA': 54272, 'KANSAS': 20370, 'NEBRASKA': 29011, 'TEXAS': 29910},
    {'SOUTH CAROLINA': 15000, 'IDAHO': 40000, 'VIRGINIA': 40000, 'KANSAS': 25000, 'NEBRASKA': 20000, 'TEXAS': 25000}]

vals = len(tabs)
xticks = np.arange(len(tabs[0]))
xlabels = list(tabs[0].keys())

fig, ax = plt.subplots(figsize=(8, 5))

plots = []
for i in range(len(tabs)):
    plots.append(
                ax.bar(
                        xticks - 0.7 / 2. + i / float(vals) * 0.7,
                        list(tabs[i].values()),
                        width=0.7/float(vals),
                        align='edge',
                        label=i
                    )
                )

# annotation template
annot = ax.annotate(
                    "",
                    xy = (0,0),
                    xytext = (10, 20),
                    textcoords = "offset points",
                    arrowprops = dict(arrowstyle = 'wedge', fc = 'black')
                    )

annot.set_visible(False)

# update annotation with bar data
def update_annot(i, bar):
    x = bar.get_x() + bar.get_width() / 2.
    y = bar.get_y() + bar.get_height()
    annot.xy = (x, y)
    text = f'{list(tabs[0].keys())[i]}: {y}'
    annot.set_text(text)
    annot.set_bbox(
                dict(
                    boxstyle='round',
                    lw=0,
                    alpha=0.8
                    )
                )


# activate annotation on hover
def hover(event):
    vis = annot.get_visible()
    if event.inaxes:
        for i, plot in enumerate(plots):
            for bar in plot:
                cont, _ = bar.contains(event)
                if cont:
                    update_annot(i, bar)
                    annot.set_visible(True)
                    fig.canvas.draw_idle()
                    return
    if vis:
        annot.set_visible(False)
        fig.canvas.draw_idle()

ax.set(title='title', xlabel='xlabel', ylabel='ylabel')
ax.set_xticks(xticks)
ax.set_xticklabels(xlabels, rotation=45)
ax.legend()
ax.grid(axis='y')
fig.tight_layout()

fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()

This is what I get when I run this script.
when I hover over one of the bars I get a text box with its x and y values. But now I want to annotate all points with the same x value, so that if I hover over one of them, all the rest are annotated as well. That is, in the example image I would like all 3 columns representing a single state to be annotated when I hover over any of them.
I tried grouping each column by using zip() on the list of the plot objects, but then I can't make several annotations appear at once. Grouping the columns into lists is no good since they are not matplotlib objects and can't be picked on events, and iterating over them obviously picks them one at a time. Any help will be much appreciated!

OPearl
  • 349
  • 3
  • 14
  • I have problems understanding this. There should only ever be one bar at any x position, else one bar would be hidden by the other? – ImportanceOfBeingErnest Mar 18 '19 at 12:52
  • please provide a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve), including mock-up data of the final plot so that someone can help you – Diziet Asahi Mar 18 '19 at 12:53
  • @DizietAsahi Thank you! I edited the question. I hope this makes more sense. – OPearl Mar 18 '19 at 13:46
  • First step, if you want to show three annotations, would be to create 3 annotations. Then inside `update_annot` you can modify the content of all three of them and of course you need to set them all visible. – ImportanceOfBeingErnest Mar 18 '19 at 16:16
  • Thanks @ImportanceOfBeingErnest. I tried something like that. The problem is I don't know in advance how many bars per x value I'll get, so I don't know how many annotations I'll need. I tried creating the annotations inside a loop like I'm doing with the bar plot, but I couldn't make it work. – OPearl Mar 19 '19 at 08:58
  • You get as many bars as you have elements in `tabs` - or fewer. Creating those in the loop seems like a correct approach. I cannot know why it failed for you without seeing any code. – ImportanceOfBeingErnest Mar 19 '19 at 12:27

0 Answers0