2015-11-06 15 views
11

che voglio fare quanto segue, utilizzando matplotlib:Matplotlib: rispondere a eventi click

  1. creare una linea tra due punti, effettuando le seguenti operazioni: i. Fare doppio clic sulla tela utilizzando il pulsante Sinistra (primo punto creato) ii. Trascinare il mouse su (o semplicemente fare clic su) secondo punto ii. Hanno linea tracciata tra il primo e il secondo punto

  2. Posizionare un verde (o qualsiasi altro colore) cerchio sulla tela, nel modo seguente: i. Doppio clic su tela, utilizzando il pulsante DESTRO

  3. Poiché è probabile che si commettano errori quando si fa doppio clic, desidero poter selezionare un cerchio tracciato (o una linea) e premere il pulsante Elimina per eliminare l'elemento selezionato .

Torna ai bei vecchi tempi di VB, questo era un lavoro di 15 minuti. Dopo aver perso diverse ore su questo, ho esaurito le idee.

Questo è quello che ho finora:

import matplotlib.pyplot as plt 


class LineDrawer(object): 
    lines = [] 
    def draw_line(self): 
     ax = plt.gca() 
     xy = plt.ginput(2) 

     x = [p[0] for p in xy] 
     y = [p[1] for p in xy] 
     line = plt.plot(x,y) 
     ax.figure.canvas.draw() 

     self.lines.append(line) 


def onclick(event): 
    if event.dblclick: 
     if event.button == 1: 
      # Draw line 
      ld = LineDrawer() 
      ld.draw_line() # here you click on the plot 
     elif event.button == 3: 
      # Write to figure 
      plt.figtext(3, 8, 'boxed italics text in data coords', style='italic', bbox={'facecolor':'red', 'alpha':0.5, 'pad':10}) 
      circ = plt.Circle((event.x, event.y), radius=0.07, color='g') 
      ax.add_patch(circ) 
      plt.draw() 
     else: 
      pass # Do nothing 


def onpick(event): 
    thisline = event.artist 
    xdata = thisline.get_xdata() 
    ydata = thisline.get_ydata() 
    ind = event.ind 
    print ('onpick points:', zip(xdata[ind], ydata[ind])) 



fig, ax = plt.subplots() 

connection_id = fig.canvas.mpl_connect('button_press_event', onclick) 
fig.canvas.mpl_connect('pick_event', onpick) 


plt.tight_layout() 

plt.show() 

Oltre alla funzionalità di eliminazione, che non ho ancora saggiato ancora, perché è il mio codice non esegue requisiti 1 e 2?

Cosa sto facendo male ?, cosa più importante, come posso risolvere il codice per ottenere la funzionalità richiesta?

+0

Tutto quello che posso dire è che sarebbe molto più semplice in Tkinter. Capisco che potrebbe non essere d'aiuto, ma guarda se hai una certa flessibilità. – sunny

+0

@sunny: Non sono contrario all'utilizzo di TKinter (anche se non l'ho mai usato prima). Se puoi fornire del codice per iniziare, probabilmente sarà abbastanza buono ... (Avrò bisogno comunque di entrambe le funzionalità di zoom e pan, dato che matplotlib fornisce "out of the box") –

risposta

9

Ci sei quasi, ma la tua logica invia il codice per disegnare una linea con un doppio clic senza memorizzare dove era il doppio clic, quindi richiede due clic per disegnare una linea. Inoltre, era necessario disegnare la tela nel codice cerchio. Ecco una versione minimamente rivista che fa obbligo 1 e 2:

import matplotlib.pyplot as plt 


class LineDrawer(object): 
    lines = [] 
    def draw_line(self, startx,starty): 
     ax = plt.gca() 
     xy = plt.ginput(1) 
     x = [startx,xy[0][0]] 
     y = [starty,xy[0][1]] 
     line = plt.plot(x,y) 
     ax.figure.canvas.draw() 

     self.lines.append(line) 


def onclick(event): 
    if event.dblclick: 
     if event.button == 1: 
      # Draw line 
      ld = LineDrawer() 
      ld.draw_line(event.xdata,event.ydata) # here you click on the plot 
     elif event.button == 3: 
      # Write to figure 
      plt.figtext(3, 8, 'boxed italics text in data coords', style='italic', bbox={'facecolor':'red', 'alpha':0.5, 'pad':10}) 
      circ = plt.Circle((event.xdata, event.ydata), radius=0.07, color='g') 
      ax.add_patch(circ) 
      ax.figure.canvas.draw() 
     else: 
      pass # Do nothing 


def onpick(event): 
    thisline = event.artist 
    xdata = thisline.get_xdata() 
    ydata = thisline.get_ydata() 
    ind = event.ind 
    print ('onpick points:', zip(xdata[ind], ydata[ind])) 



fig, ax = plt.subplots() 

connection_id = fig.canvas.mpl_connect('button_press_event', onclick) 
fig.canvas.mpl_connect('pick_event', onpick) 


plt.tight_layout() 

plt.show() 

noti che matplotlib potrebbe non essere il modo migliore o più semplice da implementare queste esigenze - anche l'asse si auto ridimensionare il disegno della prima linea così com'è. È possibile modificare ciò risolvendo il xlim e ylim. per esempio. come segue:

ax.set_xlim([0,2]) 
ax.set_ylim([0,2]) 

Per attuare requisito 3, si sta andando ad avere per memorizzare l'oggetto scelto e ascoltare per un abbinamento pressione del tasto Canc per eliminarlo. Ecco una versione che combina tutto quanto sopra. Ho cercato di aderire al tuo design il più possibile. Memorizzo il riferimento all'oggetto selezionato nell'oggetto relativo degli assi. Potresti voler implementare la tua struttura dati per memorizzare l'oggetto selezionato se non ti piace inserirlo nell'asse corrente. L'ho provato un po ', ma probabilmente ci sono sequenze di click/keypress che potrebbero confondere la logica.

import matplotlib.pyplot as plt 

# function to draw lines - from matplotlib examples. Note you don't need 
# to keep a reference to the lines drawn, so I've removed the class as it 
# is overkill for your purposes 
def draw_line(startx,starty): 
     ax = plt.gca() 
     xy = plt.ginput(1) 
     x = [startx,xy[0][0]] 
     y = [starty,xy[0][1]] 
     line = ax.plot(x,y, picker=5) # note that picker=5 means a click within 5 pixels will "pick" the Line2D object 
     ax.figure.canvas.draw()   

def onclick(event): 
    """ 
    This implements click functionality. If it's a double click do something, 
    else ignore. 
    Once in the double click block, if its a left click, wait for a further 
    click and draw a line between the double click co-ordinates and that click 
    (using ginput(1) - the 1 means wait for one mouse input - a higher number 
    is used to get multiple clicks to define a polyline) 
    If the double click was a right click, draw the fixed radius circle 

    """ 
    if event.dblclick: 
     if event.button == 1: 
      # Draw line 
      draw_line(event.xdata,event.ydata) # here you click on the plot 
     elif event.button == 3: 
      # Write to figure 
      plt.figtext(3, 8, 'boxed italics text in data coords', style='italic', bbox={'facecolor':'red', 'alpha':0.5, 'pad':10}) 
      circ = plt.Circle((event.xdata, event.ydata), radius=0.07, color='g', picker = True) 
      ax.add_patch(circ) 
      ax.figure.canvas.draw() 
     else: 
      pass # Do nothing 


def onpick(event):  
    """ 
    Handles the pick event - if an object has been picked, store a 
    reference to it. We do this by simply adding a reference to it 
    named 'stored_pick' to the axes object. Note that in python we 
    can dynamically add an attribute variable (stored_pick) to an 
    existing object - even one that is produced by a library as in this 
    case 
    """ 
    this_artist = event.artist #the picked object is available as event.artist 
    # print(this_artist) #For debug just to show you which object is picked 
    plt.gca().picked_object = this_artist 

def on_key(event): 
    """ 
    Function to be bound to the key press event 
    If the key pressed is delete and there is a picked object, 
    remove that object from the canvas 
    """ 
    if event.key == u'delete': 
     ax = plt.gca() 
     if ax.picked_object: 
      ax.picked_object.remove() 
      ax.picked_object = None 
      ax.figure.canvas.draw() 


fig, ax = plt.subplots() 

#First we need to catch three types of event, clicks, "picks" (a specialised 
#type of click to select an object on a matplotlib canvas) and key presses. 
#The logic is - if it's a right double click, wait for the next click and draw 
#a line, if its a right double click draw a fixed radius circle. If it's a 
#pick, store a reference to the picked item until the next keypress. If it's 
#a keypress - test if it's delete and if so, remove the picked object. 
#The functions (defined above) bound to the events implement this logic 
connection_id = fig.canvas.mpl_connect('button_press_event', onclick) 
fig.canvas.mpl_connect('pick_event', onpick) 
cid = fig.canvas.mpl_connect('key_press_event', on_key) 

#set the size of the matplotlib figure in data units, so that it doesn't 
#auto-resize (which it will be default on the first drawn item) 
ax.set_xlim([0,2]) 
ax.set_ylim([0,2]) 
ax.aspect = 1 
plt.tight_layout() 

plt.show() 
+0

Grazie - sembra che faccia la maggior parte di ciò che voglio, in quanto posso usarlo per eseguire effettivamente il lavoro che voglio fare.Solo un piccolo problema è che non capisco cosa stia facendo, quindi sarà quasi impossibile mantenerlo o addirittura estenderlo (il mio frammento originale è stato in gran parte copiato dal sito matplotlib). Potresti commentare il tuo frammento, quindi è comprensibile, in modo che io possa accettare la tua risposta? Grazie –

+0

@HomunculusReticulli OK - Non mi ero reso conto che era un esempio patchato insieme. Commenterò il codice di esempio –

+0

@HomunculusReticulli Fatto. Ho aggiunto commenti e semplificato un po 'il design ora che so che la struttura originale non era un design particolare, ma piuttosto copiata. fammi sapere se tutto è ancora poco chiaro –

Problemi correlati