Issue
Continue on from previous question .. Here
This time I need to plot two sets of data with different sizes on the same plot. The issue is, since the two sets of data have different sizes, some points will fall out of index. When I pick on certain point(s), I'll get a warning like this:
File "C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 224, in process
func(*args, **kwargs)
File "C:\Users\U240335\.spyder-py3\Spyder_Dingyi\untitled0.py", line 42, in onpick
IndexError: index 16 is out of bounds for axis 0 with size 10
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 224, in process
func(*args, **kwargs)
File "C:\Users\U240335\.spyder-py3\Spyder_Dingyi\untitled0.py", line 42, in onpick
IndexError: index 17 is out of bounds for axis 0 with size 10
And I would also like to show the value of the point (including the label) on the plot of which the cursor is clicking on and be able to do something like "clear all" after marking some points on the plot.
Here's the full code:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
a = np.random.uniform(0, 20, 30)
b = np.random.uniform(0, 20, 30)
x = np.random.uniform(0, 20, 30)
y = np.random.uniform(0, 20, 30)
z = [0]*10 + [1]*20
random.shuffle(z)
# x y data and legend labels
df1 = pd.DataFrame({"x1": a, "y1": b, 'z': z})
df2 = pd.DataFrame({"x2": x, "y2": y, 'z': z})
x1 = df1['x1'].loc[df1['z']==0].reset_index(drop=True).values
y1 = df1['y1'].loc[df1['z']==0].reset_index(drop=True).values
x2 = df2['x2'].loc[df1['z']==1].reset_index(drop=True).values
y2 = df2['y2'].loc[df1['z']==1].reset_index(drop=True).values
def overlay_plot2(x1,y1,x2,y2,title,xlabel,ylabel):
# define the picker event
def onpick(event):
ind = event.ind
print('\n%s:' % xlabel, x1[ind] if event.artist.get_label() == '0' else x2[ind], # use event.artist.getlabel() when labels are made
'%s:' % ylabel, y1[ind] if event.artist.get_label() == '1' else y2[ind],
event.artist.get_label())
# plot
fig, ax = plt.subplots(figsize=(8, 6), dpi = 120)
ax.scatter(x1, y1, c='b', label = '0',s=14, marker='x', picker=True)
ax.scatter(x2, y2, c='r', label = '1',s=14, marker='o', picker=True)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.legend(
loc="center left",
bbox_to_anchor=(1, 0.5),
)
ax.ticklabel_format(useOffset=False)
ax.tick_params(axis = 'x',labelrotation = 45)
plt.tight_layout()
# call the event
fig.canvas.mpl_connect('pick_event', onpick)
overlay_plot2(x1,y1,x2,y2,"title","xlabel","ylabel")
Solution
Before going too into the weeds with customizing picker events, have you taken a look at mplcursors? There is a fantastic on hover
feature that may be more of what you need. You can simply do mplcursors.cursor(hover=True)
to give a basic annotation with x, y, and label values. Or, you can customize the annotations. Here is some code that I use for one of my projects where I've customized everything (color, text, arrow, etc.). Maybe you can find some use for it as well?
Do at least look at my comment about how to correct your index error. If the below isn't what you want, give this answer a read and it should point you in the right direction on how to annotate on click.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
import mplcursors
a = np.random.uniform(0, 20, 30)
b = np.random.uniform(0, 20, 30)
x = np.random.uniform(0, 20, 30)
y = np.random.uniform(0, 20, 30)
z = [0]*10 + [1]*20
random.shuffle(z)
# x y data and legend labels
df1 = pd.DataFrame({"x1": a, "y1": b, 'z': z})
df2 = pd.DataFrame({"x2": x, "y2": y, 'z': z})
x1 = df1['x1'].loc[df1['z']==0].reset_index(drop=True).values
y1 = df1['y1'].loc[df1['z']==0].reset_index(drop=True).values
x2 = df2['x2'].loc[df1['z']==1].reset_index(drop=True).values
y2 = df2['y2'].loc[df1['z']==1].reset_index(drop=True).values
def overlay_plot2(x1,y1,x2,y2,title,xlabel,ylabel):
# define the picker event
def onpick(event):
ind = event.ind
print('\n%s:' % xlabel, x1[ind] if event.artist.get_label() == '0' else x2[ind], # use event.artist.getlabel() when labels are made
'%s:' % ylabel, y1[ind] if event.artist.get_label() == '0' else y2[ind],
event.artist.get_label())
# plot
fig, ax = plt.subplots(figsize=(8, 6), dpi = 120)
ax1 = ax.scatter(x1, y1, c='b', label = '0',s=14, marker='x', picker=True)
ax2 = ax.scatter(x2, y2, c='r', label = '1',s=14, marker='o', picker=True)
#mplcursors.cursor(hover=True)
@mplcursors.cursor(ax1, hover=2).connect("add")
def _(sel):
sel.annotation.set_text('X Value: {}\nY Value: {}\nLabel: {}'.format(round(sel.target[0],2), round(sel.target[1], 2), sel.artist.get_label()))
sel.annotation.get_bbox_patch().set(fc="blue", alpha=0.5)
sel.annotation.arrow_patch.set(arrowstyle="-|>", connectionstyle="angle3", fc="black", alpha=.5)
@mplcursors.cursor(ax2, hover=2).connect("add")
def _(sel):
sel.annotation.set_text('X Value: {}\nY Value: {}\nLabel: {}'.format(round(sel.target[0],2), round(sel.target[1], 2), sel.artist.get_label()))
sel.annotation.get_bbox_patch().set(fc="red", alpha=0.5)
sel.annotation.arrow_patch.set(arrowstyle="-|>", connectionstyle="angle3", fc="black", alpha=.5)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.legend(
loc="center left",
bbox_to_anchor=(1, 0.5),
)
ax.ticklabel_format(useOffset=False)
ax.tick_params(axis = 'x',labelrotation = 45)
plt.tight_layout()
# call the event
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
overlay_plot2(x1,y1,x2,y2,"title","xlabel","ylabel")
Example Output Graphs (my mouse is hovering over these points):
Answered By - Michael S.
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.