1

I am trying to animate a geopandas plot unsing FuncAnimation. Basically eu_private_map is a geopandas.geodataframe.GeoDataFrame and among other fields, it contains several columns with the values for parameter x in different EU states across 10 consecutive years. Each column are the values of x for one year.

from matplotlib.animation import FuncAnimation
from IPython.display import HTML
from mpl_toolkits.axes_grid1 import make_axes_locatable

fig = plt.figure() 
ax1 = fig.add_subplot(1,1,1)

def animate(year):
        ax1.clear()
        my_map = eu_private_map.plot(ax=ax1, column=year, vmin=0, vmax=30, legend=False)
        divider = make_axes_locatable(ax1)
        cax = divider.append_axes("right", size="5%", pad=0.05)
        plt.colorbar(my_map, cax=cax)

# the FuncAnimation function iterates through our animate function using the steps array
years = eu_private.columns.drop('country').tolist()
ani = FuncAnimation(fig, animate, years, interval=1000, blit=False)

HTML(ani.to_jshtml())

The colorbar is giving me a hard time. On the one hand, if I set legend = True, I do not like the fact that it does not have the same size (see figure) as the figure and it creates a weird effect (see figure). I tried this but I got an error: AttributeError: 'AxesSubplot' object has no attribute 'autoscale_None'. However I cannot spot the trouble in the code, any help to fix both issues at once? Thanks a lot

enter image description here enter image description here

G. Macia
  • 1,204
  • 3
  • 23
  • 38
  • 1
    You can circumvent the requirement for the animation function to return anything if you set `blit=False`. I don't think blitting makes any sense here,because you convert the animation to a video anyways. You may run into other issues when doing that, but it would make sense to update the question if that is the case. – ImportanceOfBeingErnest Mar 11 '19 at 17:38
  • You are very right, thanks a lot. Could you explain me what blit is doing? I do not get it from the documentation. Now the colorbar is giving issues so I will edit the question and tackle this instead. – G. Macia Mar 11 '19 at 18:00
  • 1
    [The docs say](https://matplotlib.org/api/animation_api.html#funcanimation) *"take an existing bitmap [..] and then 'blit' one more artist on top. Thus, by managing a saved 'clean' bitmap, we can only re-draw the few artists that are changing at each frame [...]."* The idea is hence to not redraw all the parts of the figure that stay the same throughout frames. This works when painting the graphics on screen, but for saving, the complete picture needs to be drawn for each frame anyways. Also `ax.clear()` renders blitting obsolete, because after clearing everything, nothing is the same. – ImportanceOfBeingErnest Mar 11 '19 at 18:15

3 Answers3

1

After trying many ways that failed to work for geopandas & animate, I ended up finding a solution to make it work being the main idea to separate the colorbar from the animate function to the init function of the plot.

from matplotlib.animation import FuncAnimation
from IPython.display import HTML
from mpl_toolkits.axes_grid1 import make_axes_locatable


fig = plt.figure(figsize=(20,10)) 
ax1 = fig.add_subplot(1,1,1)

def animate(year):
        ax1.clear()
        eu_private_map.plot(color='gainsboro', ax=ax1, linewidth=0.6, edgecolor='1')
        sm = eu_private_map.dropna().plot(ax=ax1, column=year, vmin=0, vmax=30, cmap = 'viridis',
                                              legend=False, linewidth=0.6, edgecolor='1')
        ax1.set_title(year, fontdict={'fontsize': '23'})
        ax1.axis('off')

def init():
    # Create colorbar as a legend
    sm = plt.cm.ScalarMappable(cmap='viridis', norm=plt.Normalize(vmin=0, vmax=30))
    # empty array for the data range
    sm._A = []
    # add the colorbar to the figure

    divider = make_axes_locatable(ax1)
    cax = divider.append_axes("right", size="5%", pad=0.5)


    cbar = fig.colorbar(sm, cax=cax)
    cbar.ax.tick_params(labelsize=14) 


# the FuncAnimation function iterates through our animate function using the steps array
years = eu_private.columns.drop('country').tolist()[::-1]
ani = FuncAnimation(fig, animate, years, interval=1000, blit=False, init_func=init)

HTML(ani.to_jshtml())
G. Macia
  • 1,204
  • 3
  • 23
  • 38
  • The solution was adapted from this original blog post (not that it is not dynamic): https://towardsdatascience.com/lets-make-a-map-using-geopandas-pandas-and-matplotlib-to-make-a-chloropleth-map-dddc31c1983d – G. Macia Mar 11 '19 at 23:12
  • 1
    Code is not runnable, name 'eu_private' is not defined. – swatchai Mar 12 '19 at 07:53
0

Hei, found a way to make a log scale color plot norm=matplotlib.colors.LogNorm() does the trick:

A logarithmic colorbar in matplotlib scatter plot

and you can also add vmin and vmax like this: norm=colors.LogNorm(vmin=vmin, vmax=vmax)

good to remember though it works only for vmin and vmax positive. E.g. norm=colors.LogNorm(vmin=0.1, vmax=1000000)

Sundeep Pidugu
  • 2,377
  • 2
  • 21
  • 43
0

For new answer-seekers who are stumbling on to this, the idea is to use init_func parameter for the FuncAnimation function. This was answered here.

the init_func will render the colorbar whereas the animate function will not render the colorbar.

Limitation: I am assuming the colorbar and its ticks do not change and this answer will keep the old colorbar intact while updating the chart axes.

ShouravBR
  • 685
  • 7
  • 10