如何在 python 中使用鼠标移动堆栈图中的图形

How to move a graph in a stack plot with the mouse in python

问题

我想用鼠标移动堆栈图中的单个图并立即更新图。这些值不仅应该在绘图(图形)中更改,还应该更新包含值的列表,以便我可以将它们打印在文件中。

问题

是否可以使用 matplotlib 在 python 中实现它,或者您能推荐我其他的东西吗?实施需要在 python 内。向我推荐了 Matplotlib,但是如果这个包不可行或者有更好的包请发表评论。如果您有代码示例,请随时分享。

例子

x 轴代表时间(从今天到未来),y 轴代表资源量。当 != 0 的值将被剪切时,无法将图形移动到过去(左)。您可以将任何图形移动到未来(右),没有任何东西被剪切。 例如,我无法将 "O" 向左移动,但我可以将 "R" 和 "Y" 向左移动(仅一次)。

在示例中我使用了只有 6 个条目的列表,但您可以想象它们总是足够长。

值:

x-ax = [0, 1, 2, 3, 4, 5]
y-ax = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

   R = [0, 1, 2, 1, 1, 1]
   Y = [0, 2, 1, 2, 1, 0]
   O = [5, 2, 1, 1, 1, 1]

after moving Y one right
   R = [0, 1, 2, 1, 1, 1]
   Y = [0, 0, 2, 1, 2, 1]
   O = [5, 2, 1, 1, 1, 1]

我有什么

我定义了一个图形并添加了一个子图,现在我使用 stackplot 函数绘制我的数据。

fig = plt.figure()
ax1 = fig.add_subplot(111)
stackPlots = ax1.stackplot(x-ax, R, Y, O)

感谢"ImportanceOfBeingErnest"!我添加了 3 个事件并将它们连接起来

cid_press = fig.canvas.mpl_connect('button_press_event', on_press)
cid_release = fig.canvas.mpl_connect('button_release_event', on_release)
cid_move = fig.canvas.mpl_connect('motion_notify_event', motion_notify)

def on_press(event):
if(event.button == 1):
    for stack_p in stackPlots:
        contains, attrd = stack_p.contains(event)
        if contains:
            selected_stack = stack_p
            break
    if not contains:
        return

    # I can change the color, but how can I move it?
    selected_stack .set_facecolors('black')
    plt.draw()

    print("clicked on", attrd, selected_stack)


    #print('you pressed', event.button, event.xdata, event.ydata)

def on_release(event):
    if (event.button == 1):
        print('you released', event.button, event.xdata, event.ydata)

def motion_notify(event):
    if (event.button == 1):
        return
        print('you moved', event.button, event.xdata, event.ydata)

现在,当我单击图表时,它会将颜色更改为黑色。所以我知道我找到了正确的,但我想移动它。 "stackplot" 方法 returns 列表 "PolyCollection"。我想我应该重做整个情节,但我需要找出哪个 "PolyCollection" 对应于哪个数组 (R, Y, O)...

感谢您的帮助!

我认为这个问题需要分成两个主要部分。由于移动堆栈图的堆栈不仅是图形移动,而且需要使用更改的数据绘制新的堆栈图,因此 (a) 首先找到一种计算新数据的方法 (a) =32=](b) 然后提供一种使用新数据重做绘图的方法。

(a) 沿数据中的列移动行

我们需要一种方法将数组中的一行向左或向右移动,这样输出数组在执行移动的一侧可能多一列。这可以通过以下方式完成:

import numpy as np

class Moveable():
    def __init__(self, x,y):
        self.x = x
        self.y = y

    def move(self, row, by):
        if by >0:
            for i in range(by):
                self.moveright(row)
                self.sanitize()
        else:
            for i in range(-int(by)):
                self.moveleft(row)
                self.sanitize()

    def moveright(self,row):
        x = np.zeros(len(self.x)+1)
        x[:len(self.x)] = self.x[:]
        x[-1] = self.x[-1]+1
        y = np.zeros((self.y.shape[0], len(self.x)+1))
        y[:, :len(self.x)] = self.y[:,:]
        y[row,0] = 0
        y[row,1:] = self.y[row,:]
        self.x=x
        self.y=y

    def moveleft(self,row):
        x = np.zeros(len(self.x)+1)
        x[1:len(self.x)+1] = self.x[:]
        x[0] = self.x[0]-1
        y = np.zeros((self.y.shape[0], len(self.x)+1))
        y[:, 1:len(self.x)+1] = self.y[:,:]
        y[row,-1] = 0
        y[row,:-1] = self.y[row,:]
        self.x=x
        self.y=y

    def sanitize(self):
        if (self.y[:,0] == 0.).all():
            self.x = self.x[1:]
            self.y = self.y[:,1:]
        if (self.y[:,-1] == 0.).all():
            self.x = self.x[:-1]
            self.y = self.y[:,:-1]

用法例如是

x = [0, 1, 2, 3, 4, 5]

R = [0, 1, 2, 1, 1, 1]
Y = [0, 2, 1, 2, 1, 0]
O = [5, 2, 1, 1, 1, 1]

m= Moveable(np.array(x), np.array([R, Y, O]))
m.move(row=2, by=1)
print(m.x)  # prints [ 1.  2.  3.  4.  5.  6.]
print(m.y)  # [[ 1.  2.  1.  1.  1.  0.]
            #  [ 2.  1.  2.  1.  0.  0.]
            #  [ 5.  2.  1.  1.  1.  1.]]
# Note that 0 is not part of the x array any more, 
# but that we have 6 as new column on the right side of matrix

(b) 拖动鼠标时的新堆栈图

现在我们可以使用上面的方法在鼠标选择一个堆栈并向左或向右移动一定量后用新的堆栈图更新轴。

import matplotlib.pyplot as plt

class StackMover():
    def __init__(self, ax, x,y, **kw):
        self.m = Moveable(np.array(x), np.array(y))
        self.xp = None
        self.ax = ax
        self.kw = kw
        self.stackplot = self.ax.stackplot(self.m.x, self.m.y, **self.kw)
        self.c1 = self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
        self.c2 = self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)

    def on_press(self,event):
        self.xp = None
        if(event.button != 1): return
        self.row = 0
        for stack_p in self.stackplot:
            contains, attrd = stack_p.contains(event)
            if contains:
                break
            self.row += 1
        if not contains:
            return
        self.xp = event.xdata


    def on_release(self,event):
        if(event.button != 1): return
        if self.xp != None:
            by = int(event.xdata - self.xp)
            self.m.move(self.row, by)
            self.ax.clear()
            self.stackplot = self.ax.stackplot(self.m.x, self.m.y, **self.kw)
        self.ax.figure.canvas.draw_idle()
        self.xp = None


x = [0, 1, 2, 3, 4, 5]

R = [0, 1, 2, 1, 1, 1]
Y = [0, 2, 1, 2, 1, 0]
O = [5, 2, 1, 1, 1, 1]

fig, ax = plt.subplots()
stackPlots = StackMover(ax, x, [R, Y, O])

plt.show()

结果可能如下所示

这里我忽略了motion_notify_event,因为我们其实只需要鼠标拖动的起点和终点就可以得到栈来更新