I am trying to use interactive plotting with matplotlib to manually correct automatically-generated scatterplot data. (The program attempts to detect peaks in timeseries data and place a point on each one, but there are some artifacts in the timeseries data where peaks are inappropriately detected.)
I have successfully written code that plots the timeseries and overlays the scatterplot data, and then the user can add or remove points by clicking. However, I would like the script to save the edited point series as a new array and pass it to other functions etc. I can't really call the function without mouseclick events, so I don't know how to return the data.
Code is adapted from here: Interactively add and remove scatter points in matplotlib
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(16,4))
a_input = np.sin(range(100))*np.random.normal(20,10,100)
b_input = [ 5, 15, 25, 30, 40, 50, 75, 85]
a = plt.plot(range(len(a_input)),a_input,color='red')[0]
b = plt.scatter(b_input,a_input[b_input],color='grey',s=50,picker=5)
newpeaks = np.array([])
def add_or_remove_point(event):
global a
xydata_a = np.stack(a.get_data(),axis=1)
xdata_a = a.get_xdata()
ydata_a = a.get_ydata()
global b
xydata_b = b.get_offsets()
xdata_b = b.get_offsets()[:,0]
ydata_b = b.get_offsets()[:,1]
global newpeaks
#click x-value
xdata_click = event.xdata
#index of nearest x-value in a
xdata_nearest_index_a = (np.abs(xdata_a-xdata_click)).argmin()
#new scatter point x-value
new_xdata_point_b = xdata_a[xdata_nearest_index_a]
#new scatter point [x-value, y-value]
new_xydata_point_b = xydata_a[new_xdata_point_b,:]
if event.button == 1:
if new_xdata_point_b not in xdata_b:
#insert new scatter point into b
new_xydata_b = np.insert(xydata_b,0,new_xydata_point_b,axis=0)
#sort b based on x-axis values
new_xydata_b = new_xydata_b[np.argsort(new_xydata_b[:,0])]
#update b
b.set_offsets(new_xydata_b)
newpeaks = b.get_offsets()[:,0]
plt.draw()
elif event.button == 3:
if new_xdata_point_b in xdata_b:
#remove xdata point b
new_xydata_b = np.delete(xydata_b,np.where(xdata_b==new_xdata_point_b),axis=0)
#print(new_xdata_point_b)
#update b
b.set_offsets(new_xydata_b)
newpeaks = b.get_offsets()[:,0]
plt.draw()
print(len(newpeaks))
return newpeaks
fig.canvas.mpl_connect('button_press_event',add_or_remove_point)
plt.show()
Interestingly, b.get_offsets() does remain updated outside of the function, so if I reassign newpeaks manaully in the commandline after the script completes, I get the right updated data. But if I place the assignment
newpeaks = b.get_offsets()[:,0]
after the function in the script, it gets run first and does not update when the plot is closed. I would like it to either update automatically as soon as the plot is closed or update the global variable somehow instead of having to do it manually. Any advice is welcome!