JackMcKew / pandas_alive

Create stunning, animated visualisations with Pandas & Matplotlib as easy as calling `df.plot_animated()`
MIT License
582 stars 100 forks source link

Passing `legend=True` to plot_animated on a GeoDataFrame creates one legend for each frame #38

Open hraftery opened 3 years ago

hraftery commented 3 years ago

Describe the bug Creating a GeoDataFrame using geopandas in a format suitable for plot_animated applies a matplotlib colour map to each frame. In matplotlib, to display the colour map on the graph you turn on the "legend". This can be done by passing legend=True as an argument to the plot method.

pandas_alive permits supplying extra arguments to plot_animated which are passed on to plot. Unfortunately, matplotlib draws the colour map legend as another axis, so when used in plot_animated, each successive frame gets an additional colour map, ruining the effect.

To Reproduce This is as per the documention, with the addition of legend=True

import geopandas
import pandas_alive
import contextily

gdf = geopandas.read_file('data/italy-covid-region.gpkg')
gdf.index = gdf.region
gdf = gdf.drop('region',axis=1)

map_chart = gdf.plot_animated(filename='examples/example-geo-polygon-chart.gif',basemap_format={'source':contextily.providers.Stamen.Terrain}, legend=True)

Expected behavior The legend to appear in the animation, as it does for a single call to plot.

Screenshots

Screen Shot 2021-10-10 at 4 22 46 pm

Additional context The following diff on pandas_alive/geocharts.py fixes the issue:

184a185,191
>         # Added by HR211010. If the user passes "legend=True", only apply to first frame.
>         if i == 0:
>             try:
>                 self.kwargs.pop("legend")
>             except KeyError:
>                 pass
> 
hraftery commented 3 years ago

Ah, I knew I would break something with making a fix with only a sliver of experience with the package. Turns out my fix breaks for animate_multiple_plots, not because the legend is a funny figure element rather than part of the axes, but because animate_multiple_plots re-plots. My fix blows away the legend argument for good, so it is not good.

Here's a fix that preserves the original argument for subsequent plotting, but only applies to the first frame each time.

97a98,104
>         # Added by HR211011. If the user passes "legend=True", only apply to first frame.
>         self.kwargs_without_legend = self.kwargs.copy()
>         try:
>             self.kwargs_without_legend.pop("legend")
>         except KeyError:
>             pass
> 
182c189
<             **self.kwargs,
---
>             **(self.kwargs if i==0 else self.kwargs_without_legend),