Выбор одного художника из набора перекрывающихся художников в Matplotlib - PullRequest
1 голос
/ 07 мая 2019

Этот вопрос тесно связан с двумя ниже, но этот вопрос носит более общий характер.

Порядок событий выбора Matplotlib для перекрывающихся артистов

множественный выборсобытия, мешающие


Проблема:

При выборе перекрывающихся художников на одном холсте для каждого художника создаются отдельные события выбора.В приведенном ниже примере щелчок красной точки вызывает дважды on_pick, один раз для lines и один раз для points.Поскольку points находится над линией (учитывая их соответствующие значения zorder), я бы предпочел, чтобы для самого верхнего исполнителя было сгенерировано только одно событие выбора (в данном случае: points).

Пример :

line_with_overlapping_points

import numpy as np
from matplotlib import pyplot

def on_pick(event):
    if event.artist == line:
        print('Line picked')
    elif event.artist == points:
        print('Point picked')


# create axes:
pyplot.close('all')
ax      = pyplot.axes()

# add line:
x       = np.arange(10)
y       = np.random.randn(10)
line    = ax.plot(x, y, 'b-', zorder=0)[0]

# add points overlapping the line:
xpoints = [2, 4, 7]
points  = ax.plot(x[xpoints], y[xpoints], 'ro', zorder=1)[0]

# set pickers:
line.set_picker(5)
points.set_picker(5)
ax.figure.canvas.mpl_connect('pick_event', on_pick)

pyplot.show()

Грязное решение:

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

def on_press(event):
    if event.xdata is not None:
        x,y   = event.xdata, event.ydata  #mouse click coordinates
        lx,ly = line.get_xdata(), line.get_ydata()     #line point coordinates
        px,py = points.get_xdata(), points.get_ydata() #points
        dl    = np.sqrt((x - lx)**2 + (y - ly)**2)     #distances to line points
        dp    = np.sqrt((x - px)**2 + (y - py)**2)     #distances to points
        if dp.min() < 0.05:
            print('Point selected')
        elif dl.min() < 0.05:
            print('Line selected')


pyplot.close('all')
ax      = pyplot.axes()

# add line:
x       = np.arange(10)
y       = np.random.randn(10)
line    = ax.plot(x, y, 'b-', zorder=0)[0]

# add points overlapping the line:
xpoints = [2, 4, 7]
points  = ax.plot(x[xpoints], y[xpoints], 'ro', zorder=1)[0]

# set picker:
ax.figure.canvas.mpl_connect('button_press_event', on_press)

pyplot.show()

Сводка вопроса : Есть ли лучший способ выбрать лучшего художника из набора перекрывающихся художников?

В идеале, я бы хотел бытьможет сделать что-то вроде этого:

pyplot.set_pick_stack( [points, line] )

, подразумевая, что points будет выбрано над line для перекрывающего пика.

Ответы [ 2 ]

1 голос
/ 07 мая 2019

Может быть проще всего создать собственное событие на button_press_event событие.Чтобы продвинуть идею "set_pick_stack", выраженную в вопросе, это может выглядеть следующим образом.Идея состоит в том, чтобы сохранить набор исполнителей и при помощи button_press_event проверить, содержится ли это событие в исполнителе.Затем выполните обратный вызов для пользовательской функции onpick.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backend_bases import PickEvent

class PickStack():
    def __init__(self, stack, on_pick):
        self.stack = stack
        self.ax = [artist.axes for artist in self.stack][0]
        self.on_pick = on_pick
        self.cid = self.ax.figure.canvas.mpl_connect('button_press_event',
                                                     self.fire_pick_event)

    def fire_pick_event(self, event):
        if not event.inaxes:
            return
        cont = [a for a in self.stack if a.contains(event)[0]]
        if not cont:
            return
        pick_event = PickEvent("pick_Event", self.ax.figure.canvas, 
                               event, cont[0],
                               guiEvent=event.guiEvent,
                               **cont[0].contains(event)[1])
        self.on_pick(pick_event)

Использование будет выглядеть так:

fig, ax = plt.subplots()

# add line:
x       = np.arange(10)
y       = np.random.randn(10)
line,   = ax.plot(x, y, 'b-', label="Line", picker=5)

# add points overlapping the line:
xpoints = [2, 4, 7]
points,  = ax.plot(x[xpoints], y[xpoints], 'ro', label="Points", picker=5)


def onpick(event):
    txt = f"You picked {event.artist} at xy: " + \
          f"{event.mouseevent.xdata:.2f},{event.mouseevent.xdata:.2f}" + \
          f" index: {event.ind}"
    print(txt)

p = PickStack([points, line], onpick)

plt.show()

Идея состоит в том, чтобы предоставить список исполнителей в порядке, необходимом для событий выбора.Конечно, можно также использовать zorder для определения порядка.Это может выглядеть как

self.stack = list(stack).sort(key=lambda x: x.get_zorder(), reverse=True)

в функции __init__.

Поскольку в комментариях возникает вопрос, давайте посмотрим, почему matplotlib не выполняет эту фильтрацию автоматически.Ну, во-первых, я бы предположил, что это нежелательно, по крайней мере, в 50% случаев, когда вам нужно событие для каждого выбранного художника.Кроме того, для matplotlib гораздо проще просто создать событие для каждого артиста, который попал в событие мыши, чем отфильтровать его.Для первого вы просто сравниваете координаты (очень похоже на «грязное решение» из вопроса).Принимая во внимание, что трудно получить только самого высокого художника;конечно, в случае, если два художника имеют разные zorder, это было бы возможно, но если они имеют одинаковый zorder, то только порядок их появления в списках дочерних осей определяет, что находится впереди.«Pick_upmost_event» должен будет проверить весь набор дочерних осей, чтобы выяснить, какой из них выбрать.Это, как говорится, не является невозможным, но до сих пор, вероятно, никто не был уверен, что оно того стоитКонечно, люди могут открыть вопрос или представить реализацию в виде PR в matplotlib для такого «pick_upmost_event».

0 голосов
/ 07 мая 2019

Отредактировано: Во-первых, обычно неплохо отслеживать всех художников, которых вы рисуете; следовательно, я бы предложил сохранить словарь artists_dict со всеми отображаемыми элементами в виде ключей , который можно использовать для хранения некоторых полезных значений (например, в другом диктанте).

Помимо этого, приведенный ниже код основан на использовании таймера, который собирает инициированные события в list_artists, а затем обрабатывает этот список каждые 100 мс через on_pick(list_artists). В рамках этой функции вы можете проверить, был ли выбран один или несколько художников, а затем найти того, кто имеет самый высокий zorder, и сделать что-то с ним .

import numpy as np
from matplotlib import pyplot

artists_dict={}


def handler(event):
    print('handler fired')
    list_artists.append(event.artist)

def on_pick(list_artists):
    ## if you still want to use the artist dict for something:
    # print([artists_dict[a] for a in list_artists])

    if len(list_artists)==1:
        print('do something to the line here')

        list_artists.pop(0)## cleanup
    elif len(list_artists)>1:### only for more than one plot item
        zorder_list=[ a.get_zorder() for a in list_artists]
        print('highest item has zorder {0}, is at position {1} of list_artists'.format(np.max(zorder_list),np.argmax(zorder_list)))
        print('do something to the scatter plot here')
        print(list(zip(zorder_list,list_artists)))

        list_artists[:]=[]
    else:
        return

# create axes:
pyplot.close('all')
fig,ax=pyplot.subplots()

# add line:
x      = np.arange(10)
y      = np.random.randn(10)
line   = ax.plot(x, y, 'b-', zorder=0)[0]

## insert the "line” into our artists_dict with some metadata
#  instead of inserting zorder:line.get_zorder(), you could also 
#  directly insert zorder:0 of course.
artists_dict[line]={'label':'test','zorder':line.get_zorder()}

# add points overlapping the line:
xpoints = [2, 4, 7]
points  = ax.plot(x[xpoints], y[xpoints], 'ro', zorder=1)[0]

## and we also add the scatter plot 'points'
artists_dict[points]={'label':'scatters','zorder':points.get_zorder()}

# set pickers:
line.set_picker(5)
points.set_picker(5)


## connect to handler function
ax.figure.canvas.mpl_connect('pick_event', handler)
list_artists=[]
## wait a bit
timer=fig.canvas.new_timer(interval=100)
timer.add_callback(on_pick,list_artists)
timer.start()

pyplot.show()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...