-1

Problem

I want to move a single graph in a stack plot with the mouse and update the plot instantly. The values shouldn't only change in the plot (graphical), the list with the values should also be updated, so that I can print them in a file.

Question

Is it possible to implement this in python with matplotlib or would you recommend me something else? The implementation need to be in python. Matplotlib was recommended to me, but if it's not possible with this package or there is a better one please leave a comment. When you have a code example feel free to share it.

Example

The x-axis represent time (from today to the future) and the y-axis an anmount of resources. It isn't possible to move a graph to the past (left) when a value would be cut of that is != 0. You can move any graph to the future (right), nothing get's cut. for example I can't move "O" to the left, but I can move "R" and "Y" (only one time) to the left.

In the example I used lists with only 6 entries, but you could imagine that they are always long enought.

Values:

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]

Example plot - move one graph

What I have

I define a figure and add a subplot, now I use the stackplot function to plot my data.

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

Thanks to "ImportanceOfBeingErnest"! I added 3 Events and connected them

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)

When I click on a Graph now it changes it's color to black. So I know I've got the right one, but I want to move it. The "stackplot" Method returns a List of "PolyCollection". I think that I should redo the whole plot, but I need to find out which "PolyCollection" correspond with which array (R, Y, O)...

Thanks for your help!

clfaster
  • 1,450
  • 19
  • 26
  • 1
    "When you have a code example feel free to share it." You know this site is not a code-writing service, right? Maybe you should have a read of [How to Ask](https://stackoverflow.com/help/how-to-ask). What have you tried so far? – tmdavison Jul 28 '17 at 09:23
  • Hi thanks for your answer, maybe you understand me wrong. I'm not asking for code. I'm never worked with matplotlib and just hoped that someone with some experience with it can tell if that what I want to achieve with it is possible and maybe point me in some direction. I worked throw the documentation and found so far only methods for events on other plot types. – clfaster Jul 28 '17 at 09:30
  • The update is fine, it shows at which point you are stuck. Now what remains unclear is what should happen to the data if you move one stack out of the range. The example you give is a very special case, since moving a data point with value 0 out of the range does not change anything. What if the last point in yellow stack was unequal to zero? – ImportanceOfBeingErnest Jul 28 '17 at 13:21
  • I updated the example, hope it's understandable. "What if the last point in yellow stack was unequal to zero?" The value will still be ploted, when you would move the window to the right you would see that value. – clfaster Jul 28 '17 at 13:48

1 Answers1

1

I think this problem needs to be separated into two main parts. Since moving one stack of the stackplot is not only a graphical movement, but requires to draw a new stackplot with changed data, one would (a) first find a way to calculate new data and (b) then provide a way that the plot is redone with that new data.

(a) Move row along columns in data

We need a way to move one row inside an array to the left or right, such that the output array may have one column more on the side towards which the movement is performed. This could be done in the following way:

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]

The usage would e.g. be

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) New stackplot when mouse is dragged

Now we can use the above to update an axes with a new stackplot once the mouse selects a stack and is moved by some amount to the left or right.

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

The result may then look like this

enter image description here

I have neglected the motion_notify_event here, as we actually only need the start and end point of the mouse drag to obtain the stack to update.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712