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()
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
@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") –