Search code examples
pythonpandasmatplotlibdelaunaymatplotlib-animation

Animate Delaunay triangulation - Python


Is it possible to animate Delaunay triangulation using Matplotlib? The following plots vertices grouped by Item and Time. I'm hoping to animate this instead of plotting each iteration.

I may also have time points that don't contain enough points to adequately plot the triangulation. For these time points, I'm just hoping to pass that period and move onto the subsequent time point.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Delaunay
import matplotlib.animation as animation
import matplotlib.gridspec as gridspec

# data frame containing time points without adequate points (3)
#df = pd.DataFrame({
#    'Time' : [1,1,1,1,1,1,1,2,2,2,2,2,2,2,3,3],  
#    'Item' : ['A','B','A','B','A','B','A','A','B','A','B','A','B','B','A','B'],                  
#    'X' : [5, 5, 6, 6, 4, 3, 3, 4, 4, 3, 2, 5, 4, 5, 1, 2], 
#    'Y' : [5, 6, 6, 5, 5, 6, 5, 6, 3, 1, 4, 6, 7, 4, 5, 6],                         
#        })

fig = plt.figure(figsize = (8,10))

grid = gridspec.GridSpec(1, 2)
gridsize = (1, 2)

ax = plt.subplot2grid(gridsize, (0, 0))
ax2 = plt.subplot2grid(gridsize, (0, 1))

A_coord = df.loc[df['Item'] == 'A']
B_coord = df.loc[df['Item'] == 'B']

def make_points(x):
    return np.array(list(zip(x['X'], x['Y'])))

A_points = A_coord.groupby(['Time']).apply(make_points)
B_points = B_coord.groupby(['Time']).apply(make_points)

for p in A_points:
    tri = Delaunay(p)
    a_del = ax.triplot(*p.T, tri.simplices, color = 'orange')

for p in B_points:
    tri = Delaunay(p)
    b_del = ax.triplot(*p.T, tri.simplices, color = 'purple')

#def animate(i) :

    #a_del.set_data#()
    #b_del.set_data#()    

#ani = animation.FuncAnimation(fig, animate, blit = False)

Edit 2:

I'm hoping to keep the figure, axes stable as I plot other objects on it. As such, I just want to animate the change in the triangulation.

df = pd.DataFrame({
    'Time' : [1,1,1,1,1,1,1,2,2,2,2,2,2,2],  
    'Item' : ['A','B','A','B','A','B','A','A','B','A','B','A','B','B'],                  
    'X' : [5, 5, 6, 6, 4, 3, 3, 4, 4, 3, 2, 5, 4, 5], 
    'Y' : [5, 6, 6, 5, 5, 6, 5, 6, 3, 1, 4, 6, 7, 4],                         
        })


A_coord = df.loc[df['Item'] == 'A']
B_coord = df.loc[df['Item'] == 'B']

def make_points(x):
    return np.array(list(zip(x['X'], x['Y'])))

A_points = A_coord.groupby(['Time']).apply(make_points)
B_points = B_coord.groupby(['Time']).apply(make_points)

A_points = A_points.values
B_points = B_points.values

fig = plt.figure(figsize = (8,10))

grid = gridspec.GridSpec(2, 2)
gridsize = (2, 2)

ax = plt.subplot2grid(gridsize, (0, 0), colspan = 2)
ax.set_xlim(0, 20)
ax.set_ylim(0, 20)

ax2 = plt.subplot2grid(gridsize, (1, 0))
ax3 = plt.subplot2grid(gridsize, (1, 1))

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12,8))

def one_frame(i):

    ax[0].clear();ax[1].clear()

    try:
        a_points = np.unique(A_points[i],axis=0)
        tri_a = Delaunay(a_points)
        ax[0].triplot(*a_points.T, tri_a.simplices, color = 'orange')
    except Exception:
        pass

    try:
        b_points = np.unique(B_points[i],axis=0)
        tri_b = Delaunay(b_points)
        ax[1].triplot(*b_points.T, tri_b.simplices, color = 'purple')
    except Exception:
        pass


ani = animation.FuncAnimation(fig,one_frame, blit = False)

enter image description here


Solution

  • It is possible mate, try this code

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.spatial import Delaunay
    import matplotlib.animation as animation
    
    # data frame containing time points without adequate points (3)
    df = pd.DataFrame({
       'Time' : [1,1,1,1,1,1,1,2,2,2,2,2,2,2,3,3],  
       'Item' : ['A','B','A','B','A','B','A','A','B','A','B','A','B','B','A','B'],                  
       'X' : [5, 5, 6, 6, 4, 3, 3, 4, 4, 3, 2, 5, 4, 5, 1, 2], 
       'Y' : [5, 6, 6, 5, 5, 6, 5, 6, 3, 1, 4, 6, 7, 4, 5, 6],                         
           })
    A_coord = df.loc[df['Item'] == 'A']
    B_coord = df.loc[df['Item'] == 'B']
    
    def make_points(x):
        return np.array(list(zip(x['X'], x['Y'])))
    
    A_points = A_coord.groupby(['Time']).apply(make_points)
    B_points = B_coord.groupby(['Time']).apply(make_points)
    
    A_points = A_points.values
    B_points = B_points.values
    
    fig, ax = plt.subplots(nrows=1,ncols=2,figsize=(12,8))
    
    def one_frame(i):
        
        ax[0].clear();ax[1].clear()
        
        try:
            a_points = np.unique(A_points[i],axis=0)
            tri_a = Delaunay(a_points)
            ax[0].triplot(*a_points.T, tri_a.simplices, color = 'orange')
        except Exception as e:
            print("frame %i, point a can't print because of \n%s" % (i,e))
        
        try:
            b_points = np.unique(B_points[i],axis=0)
            tri_b = Delaunay(b_points)
            ax[1].triplot(*b_points.T, tri_b.simplices, color = 'purple')
        except Exception as e:
            print("frame %i, point b can't print because of \n%s" % (i,e))
    
    ani = animation.FuncAnimation(fig,one_frame,range(3), blit = False)
    ani.save('test.gif', writer='pillow', fps=1)
    

    The output is

    output


    UPDATE

    It is possible to keep the fig and ax, the idea is to remove the triangles (Line2D object) of the last frame at the beginning of each frame by

    for item in triangles_a:
        try:
            item.remove()
        except Exception as e:
            continue 
    for item in triangles_b:
        try:
            item.remove()
        except Exception as e:
            continue
    

    Remove the triangles won't affect other parts of the fig and ax. For example, in the example below, the two circles won't be affected during the animation.

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.spatial import Delaunay
    import matplotlib.animation as animation
    
    # data frame containing time points without adequate points (3)
    df = pd.DataFrame({
       'Time' : [1,1,1,1,1,1,1,2,2,2,2,2,2,2,3,3],  
       'Item' : ['A','B','A','B','A','B','A','A','B','A','B','A','B','B','A','B'],                  
       'X' : [5, 5, 6, 6, 4, 3, 3, 4, 4, 3, 2, 5, 4, 5, 1, 2], 
       'Y' : [5, 6, 6, 5, 5, 6, 5, 6, 3, 1, 4, 6, 7, 4, 5, 6],                         
           })
    A_coord = df.loc[df['Item'] == 'A']
    B_coord = df.loc[df['Item'] == 'B']
    
    def make_points(x):
        return np.array(list(zip(x['X'], x['Y'])))
    
    A_points = A_coord.groupby(['Time']).apply(make_points)
    B_points = B_coord.groupby(['Time']).apply(make_points)
    
    A_points = A_points.values
    B_points = B_points.values
    
    fig = plt.figure(figsize = (8,10))
    grid = gridspec.GridSpec(2, 2)
    gridsize = (2, 2)
    
    ax0 = plt.subplot2grid(gridsize, (0, 0), colspan = 2)
    ax1 = plt.subplot2grid(gridsize, (1, 0), colspan = 1)
    ax2 = plt.subplot2grid(gridsize, (1, 1), colspan = 1)
    
    ax1.set_xlim(0,8);ax1.set_ylim(0,8)
    ax2.set_xlim(0,8);ax2.set_ylim(0,8)
        
    # things that won't be affected 
    circle_0 = plt.Circle((4,4), 2, color='violet',fill=False)
    ax1.add_artist(circle_0)
    
    circle_1 = plt.Circle((5,4), 2, color='deepskyblue',fill=False)
    ax2.add_artist(circle_1)
        
    triangles_a,triangles_b = [],[]
    def one_frame(i):
        
        global triangles_a,triangles_b
        for item in triangles_a:
            try:
                item.remove()
            except Exception as e:
                continue 
        for item in triangles_b:
            try:
                item.remove()
            except Exception as e:
                continue
        
        try:
            a_points = np.unique(A_points[i],axis=0)
            tri_a = Delaunay(a_points)
            obj_a = ax1.triplot(*a_points.T, tri_a.simplices, color = 'orange')
            triangles_a.extend(obj_a)
        except Exception as e:
            print("frame %i, point a can't print because of \n%s" % (i,e))
        
        try:
            b_points = np.unique(B_points[i],axis=0)
            tri_b = Delaunay(b_points)
            obj_b = ax2.triplot(*b_points.T, tri_b.simplices, color = 'purple')
            triangles_b.extend(obj_b)
        except Exception as e:
            print("frame %i, point b can't print because of \n%s" % (i,e))
    
    ani = animation.FuncAnimation(fig,one_frame,range(3), blit = False)
    ani.save('test.gif', writer='pillow', fps=1)
    

    the output

    output