2015-11-06 3 views
11

я хочу сделать следующее, используя Matplotlib:Matplotlib: реагирование на события нажатия

  1. Создайте линию между двумя точками, выполнив следующие действия: я. Дважды щелкните по холсту, используя левую кнопку (созданная первая точка) ii. Либо перетащите мышь в (или просто нажмите) вторую точку ii. Проведите линию между первой и второй точкой

  2. Поместите зеленый (или любой другой цвет) круг на холст, выполнив следующие действия: i. Двойной щелчок на холсте, используя ПРАВИЛЬНУЮ кнопку

  3. Так как я, вероятно, ошибаюсь при двойном щелчке, я хочу, чтобы можно было выбрать построенный круг (или линию) и нажать кнопку удаления, чтобы удалить выбранный элемент ,

Назад в старые добрые времена VB, это была 15-минутная работа. Потратив несколько часов на это, у меня кончились идеи.

Это то, что я до сих пор:

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() 

Помимо удаления функциональности, которую я даже не обходили пока, почему мой код не выполняет требования 1 и 2?

Что я делаю неправильно ?, что более важно, как я могу исправить код, чтобы получить требуемую функциональность?

+0

Все, что я могу сказать, было бы намного проще в Tkinter. Я понимаю, что это может быть полезно, но посмотрите на него, если у вас есть определенная гибкость. – sunny

+0

@sunny: Я не прочь использовать TKinter (хотя я никогда не использовал его раньше). Если вы можете предоставить некоторый код, чтобы начать меня, это, вероятно, будет достаточно хорошим ... (мне понадобится функция масштабирования и панорамирования, поскольку - поскольку matplotlib обеспечивает это «из коробки») –

ответ

9

Вы почти там, но ваша логика отправляет код, чтобы нарисовать линию двойным щелчком, не сохраняя, где был двойной щелчок, поэтому для этого требуется два клика для рисования строки. Кроме того, вам нужно было нарисовать холст в коде круга. Вот минимально пересмотренный вариант, что делает требование 1 и 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() 

Обратите внимание, что Matplotlib не может быть лучшим или простым способом реализации этих требований - также ось будет автоматически изменять масштаб на чертеж первой линии, как она стоит. Вы можете изменить это, установив xlim и ylim. например следующим образом:

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

Для реализации требования 3, вы будете иметь, чтобы сохранить объект и определенно прослушиванием комбинационного нажатия удалить, чтобы удалить его. Вот версия, сочетающая все вышеперечисленное. Я старался как можно больше придерживаться вашего дизайна. Я сохраняю ссылку на выбранный объект в соответствующем объекте осей. Возможно, вы захотите реализовать свою собственную структуру данных для хранения выбранного объекта, если вам не нравится вставлять его в текущую ось. Я протестировал его немного, но есть, вероятно, последовательности нажатия и нажатия клавиш, которые могут смутить логику.

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

Спасибо - это, кажется, большинство из того, что я хочу, тем, что я могу использовать его, чтобы фактически выполнить работу, которую я хочу сделать.Только небольшая проблема заключается в том, что я действительно не понимаю, что она делает, поэтому почти невозможно будет поддерживать или даже расширять ее (мой оригинальный фрагмент был в значительной степени скопирован с сайта matplotlib). Не могли бы вы прокомментировать ваш фрагмент, так что это понятно, так что я могу принять ваш ответ? Спасибо –

+0

@HomunculusReticulli OK - Я не понимал, что это был пример с исправленным соединением. Я прокомментирую пример кода –

+0

@HomunculusReticulli Done. Я добавил комментарий и упростил дизайн немного теперь, когда я знаю, что ваша первоначальная структура не была конкретным дизайном, а скорее скопирована. дайте мне знать, если что-то еще неясно –

Смежные вопросы