5

Here is the problem I would like to solve: I would like to be able to interactively (i) remove scatter points (grey dots), (ii) add new scatter points, by clicking on the plot.

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(figsize=(16,4))
a = np.sin(range(100))*np.random.normal(20,10,100)
b = [ 5, 15, 25, 30, 40, 50, 75, 85]

aa = plt.plot(range(len(a)),a,color='red')
bb = plt.scatter(b,a[b],color='grey',s=50)

enter image description here

themachinist
  • 1,413
  • 2
  • 17
  • 22
  • 1
    Since this is not a code writing service, you may start by looking at the [event handling](http://matplotlib.org/users/event_handling.html) page. – ImportanceOfBeingErnest Mar 22 '17 at 09:45
  • 1
    i think my question is not unlike most questions found on stackoverflow. i think you're being a little harsh. i've been reading the event handling page, but would appreciate some direction from fellow users. – themachinist Mar 22 '17 at 09:51
  • @themachinist - I appreciate that you have at least done some research on event handling, but surely you could have followed that up with an attempt to write some code, however bad. Then you can describe exactly what it isn't doing that you want it to (or what it _is_ doing that you _don't_ want it to ;-)) and you aren't subject to a barrage of opinion. – holdenweb Mar 22 '17 at 16:37
  • As for whether SO is a code-writing service, one could construe much of the content as code that was written for the community. I've now uploaded an answer to my initial question. – themachinist Mar 23 '17 at 07:39
  • 1
    Well done! So if you want a better way to do it, accept your own answer and then post a new question asking how to improve the (working, well done again!) code you wrote. – holdenweb Mar 23 '17 at 12:16

2 Answers2

2

This may not be elegant, but it works. Left-click to add point, right-click to remove point.

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)

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]

    #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)
            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)
        plt.draw()

fig.canvas.mpl_connect('button_press_event',add_or_remove_point)
themachinist
  • 1,413
  • 2
  • 17
  • 22
0

On my current version of mpl (3.5), the pick event has a .ind property which tells you which element in the scatter array you clicked on.

It is actually a list, because it could be that you picked more than one point.

So there is no need to compute the nearest distance to find the index. You can just use xdata_nearest_index_a = event.ind[0] (or iterate over the indices if there are multiple entries in event.ind and remove all the points).

nvaytet
  • 61
  • 6