Search code examples
pythonmatplotlibanimationplotlyvisualization

How to create an animated 2D histogram chart in python


I have the code to create my 2D histogram as below. It works for creating a statics chart but I need to create an animated heatmap which change with 'minute_play' column. How can I do that? The picture of the data sample and the output are attached. Below is the code I used to create the 2D histogram (heatmap)

enter image description here

enter image description here

multicolor = ["#ffffff","#e2eeff", "#c3d8f9","#80bdff", "#42b0f5",
              "#a0e242", "#d1cb1c", "#a06a20", "#872a10", "#630d02", "#000000"]
bins = 100

densities,_,_ = np.histogram2d(heatmap_df.params_pos_x, heatmap_df.params_pos_y, bins=bins)
percentiles = [0,1,10,20,30,40,50,60,70,80,99,100]

bounds = []
for i in percentiles:
 bounds.append(np.percentile(np.unique(densities),i))

cmaplist = multicolor
cmap = mpl.colors.LinearSegmentedColormap.from_list(
           'Custom cmap',
            cmaplist,
            len(percentiles))
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
plt.hist2d(heatmap_df.params_pos_x,
           heatmap_df.params_pos_y,
      bins = bins,
      norm = norm,
       cmap = cmap)
cbar = plt.colorbar(ticks = bounds)
plt.show()

I have try to follow this but did not really understand what to do next to migrate my code to this guide

https://plotly.com/python/v3/heatmap-animation/


Solution

  • Here is an example of how to achieve this together with sample data. In my case, I did it in jupyter notebook:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    import matplotlib.colors as mcolors
    from IPython.display import HTML
    
    np.random.seed(0)
    num_entries = 1000
    num_minutes = 10
    
    minute_play = np.random.choice(range(num_minutes), size=num_entries)
    params_pos_x = np.random.uniform(0, 100, num_entries)
    params_pos_y = np.random.uniform(0, 100, num_entries)
    
    heatmap_df = pd.DataFrame({
        'minute_play': minute_play,
        'params_pos_x': params_pos_x,
        'params_pos_y': params_pos_y
    })
    
    bins = 50
    multicolor = ["#ffffff", "#e2eeff", "#c3d8f9", "#80bdff", "#42b0f5",
                  "#a0e242", "#d1cb1c", "#a06a20", "#872a10", "#630d02", "#000000"]
    percentiles = [0, 1, 10, 20, 30, 40, 50, 60, 70, 80, 99, 100]
    
    densities, _, _ = np.histogram2d(heatmap_df['params_pos_x'], heatmap_df['params_pos_y'], bins=bins)
    bounds = [np.percentile(np.unique(densities), i) for i in percentiles]
    
    cmap = mcolors.LinearSegmentedColormap.from_list('Custom cmap', multicolor, len(percentiles))
    norm = mcolors.BoundaryNorm(bounds, cmap.N)
    
    fig, ax = plt.subplots()
    cbar = fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, ticks=bounds)
    cbar.set_label('Density')
    
    def update(frame):
        ax.clear()  # Clear the previous plot
        minute_data = heatmap_df[heatmap_df['minute_play'] == frame]
        hist = ax.hist2d(minute_data['params_pos_x'], minute_data['params_pos_y'], bins=bins, cmap=cmap, norm=norm)
        ax.set_title(f'Minute: {frame}')
    
    ani = animation.FuncAnimation(fig, update, frames=np.arange(heatmap_df['minute_play'].min(), heatmap_df['minute_play'].max() + 1),
                                  repeat=False)
    
    HTML(ani.to_jshtml())
    

    which gives

    enter image description here

    To save it:

    ani.save('animated_heatmap.mp4', writer='ffmpeg')
    
    

    or

    ani.save('animated_heatmap.gif', writer='imagemagick')