0

I’m trying to update a plot dynamically within a for loop and I can’t get it to work. I wonder if anyone can help?

I get a bit confused between passing the figure vs axes and how to update. I’ve been trying to use

display.clear_output(wait=True)
display.display(plt.gcf())
time.sleep(2)

but it’s not doing what I want it to.

I'm trying to: 1. add objects to a grid (setupGrid2) 2. at a timestep - move each object in random direction (makeMove2) 3. update the position of each object visually on the grid (updateGrid2)

My problem is with 3. I'd like to clear the previous step, so that just the new location for each object is displayed. The goal to show the objects dynamically moving around the grid.

I'd also like to work with the ax object created in setupGrid2, so that I can set the plot variables (title, legend etc.) in one place and update that chart.

Grateful for any help.

Sample code below (for running in jupyter notebook):

 %matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import time
import pylab as pl
from IPython import display


def setupGrid2(norows,nocols,noobjects):
    #each object needs current grid position (x and y coordinate)
    objects = np.zeros(noobjects)
    ObjectPos = np.zeros(shape=(noobjects,2))

    #put objects randomly on grid
    for i in range (noobjects):
        ObjectPos[i][0] = np.random.uniform(0,norows)
        ObjectPos[i][1] = np.random.uniform(0,nocols)

    #plot objects on grid
    fig = plt.figure(1,figsize=(15,5))
    ax = fig.add_subplot(1,1,1)
    x,y  = zip(*ObjectPos)
    ax.scatter(x, y,c="b", label='Initial positions')
    ax.grid()
    plt.show()

    return ax,ObjectPos

def updateGrid2(ax,ObjPos):
    x,y  = zip(*ObjPos)
    plt.scatter(x, y)
    display.clear_output(wait=True)
    display.display(plt.gcf())
    time.sleep(0.1)

#move object in a random direction
def makeMove2(object,xpos,ypos):
    #gets a number: 1,2,3 or 4
    direction = int(np.random.uniform(1,4))

    if (direction == 1):
        ypos = ypos+1
    if (direction == 2):
        ypos = ypos - 1
    if (direction == 3):
        xpos = xpos+1
    if (direction == 4):
        xpos = xpos-1
    return xpos,ypos

def Simulation2(rows,cols,objects,steps):

    ax,ObjPos = setupGrid2(rows,cols,objects)

    for i in range(steps):
        for j in range (objects):
            xpos = ObjPos[j][0]
            ypos = ObjPos[j][1]
            newxpos,newypos = makeMove2(j,xpos,ypos)
            ObjPos[j][0] = newxpos
            ObjPos[j][1] = newypos
            updateGrid2(ax,ObjPos)

Simulation2(20,20,2,20) 
tmn103
  • 319
  • 1
  • 5
  • 16
  • Running the code in a jupyter notebook produces the desired animation for me. I guess you need to add a lot more details about where and how you run this, what happens (any warnings or so?) and what your system is. – ImportanceOfBeingErnest Jan 15 '18 at 11:17
  • I'm running it in jupyter, it's partially doing what I want. The two main aspects I want to adjust are: 1. the plot should clear after each time step (I only want two objects on the plot at any time at their updated locations) 2. I'd like to pass the ax object created in setupGrid2 so that I can only set the title and other plot variables in one place. thanks – tmn103 Jan 15 '18 at 11:19
  • It seems you want to update the scatter, instead of producing a new scatter for each frame. That would be shown in [this question](https://stackoverflow.com/questions/42722691/python-matplotlib-update-scatter-plot-from-a-function). Of course you can still use `display` when running this in jupyter instead of the shown solutions with `ion` or `FuncAnimation`. – ImportanceOfBeingErnest Jan 15 '18 at 11:43
  • Thanks. I'm not sure how to fully implement this though. Trying to work out how to update the scatter. What should I be entering in sc.set_offsets() in my example? Also, where should I call display? – tmn103 Jan 15 '18 at 14:10

1 Answers1

1

It seems you want to update the scatter, instead of producing a new scatter for each frame. That would be shown in this question. Of course you can still use display when running this in jupyter instead of the shown solutions with ion or FuncAnimation.

Leaving the code from the question mostly intact this might look as follows.

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import time
import pylab as pl
from IPython import display


def setupGrid2(norows,nocols,noobjects):
    #each object needs current grid position (x and y coordinate)
    objects = np.zeros(noobjects)
    ObjectPos = np.zeros(shape=(noobjects,2))

    #put objects randomly on grid
    for i in range (noobjects):
        ObjectPos[i,0] = np.random.uniform(0,norows)
        ObjectPos[i,1] = np.random.uniform(0,nocols)

    #plot objects on grid
    fig = plt.figure(1,figsize=(15,5))
    ax = fig.add_subplot(1,1,1)
    ax.axis([0,nocols+1,0,norows+1])
    x,y  = zip(*ObjectPos)
    scatter = ax.scatter(x, y,c="b", label='Initial positions')
    ax.grid()

    return ax,scatter,ObjectPos

def updateGrid2(ax,sc,ObjPos):
    sc.set_offsets(ObjPos)
    display.clear_output(wait=True)
    display.display(plt.gcf())
    time.sleep(0.1)

#move object in a random direction
def makeMove2(object,xpos,ypos):
    #gets a number: 1,2,3 or 4
    direction = int(np.random.uniform(1,4))

    if (direction == 1):
        ypos = ypos+1
    if (direction == 2):
        ypos = ypos - 1
    if (direction == 3):
        xpos = xpos+1
    if (direction == 4):
        xpos = xpos-1
    return xpos,ypos

def Simulation2(rows,cols,objects,steps):

    ax,scatter,ObjPos = setupGrid2(rows,cols,objects)

    for i in range(steps):
        for j in range (objects):
            xpos = ObjPos[j,0]
            ypos = ObjPos[j,1]
            newxpos,newypos = makeMove2(j,xpos,ypos)
            ObjPos[j,0] = newxpos
            ObjPos[j,1] = newypos
        updateGrid2(ax,scatter,ObjPos)

Simulation2(20,20,3,20)
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks very much for this. This works perfectly for two objects. Unfortunately I can’t extend beyond two. ‘objects’ should be able to take a value much higher than 2, I’d just set it to that for initial development. I see that sc.set_offsets(ObjPos.T) is taking a transpose. I can put this in a for loop to add each co-ordinate pair individually, but then I end up with a the plot refreshing for each pair, i.e. only one pair on the grid at any time. What I’m aiming for is each timestep to be displayed on the plot at each step, i.e. all objects should be displayed on each refresh. – tmn103 Jan 15 '18 at 20:50
  • I didn't actually look too closely into the code itself. Now it should work for any number of objects. – ImportanceOfBeingErnest Jan 15 '18 at 20:56
  • Great! That's got it. Thank you very much for your help! – tmn103 Jan 15 '18 at 21:02