flav-io / flavio

A Python package for flavour physics phenomenology in the Standard model and beyond
http://flav-io.github.io/
MIT License
71 stars 62 forks source link

Contour plot labels with matplotlib >= 3.5.0 #178

Closed MJKirk closed 2 years ago

MJKirk commented 2 years ago

When using a version of matplotlib >= 3.5.0, the contour plot legends from flavio don't show up correctly (see attached photos)

v3 4 3 v3 5 0

I suspect the relevant change is that in v3.4.3, contour returned a LineCollection but now returns a PathCollection (see https://matplotlib.org/stable/api/prev_api_changes/api_changes_3.5.0.html?highlight=pathcollection#contourset-always-use-pathcollection), and so set_label as called here: https://github.com/flav-io/flavio/blob/b7ac308ac83cd7168d80c3382b5d65415bc64679/flavio/plots/plotfunctions.py#L698 has different behaviour, but that's as far as I got investigating (for now at least - happy to keep digging around).

peterstangl commented 2 years ago

Hi @MJKirk thank you for reporting and investigating!

I took a quick look and it seems like set_label is a method of artist.Artist from which both LineCollection and PathCollection inherit this method. So I don't really understand why the behavior of this method should be different. And in both cases, a label is actually added. Just in the second case, obviously the colored line is missing.

MJKirk commented 2 years ago

So I've investigated a bit more - LineCollection has a get_color method (which seems to be used when creating the legend (see https://github.com/matplotlib/matplotlib/blob/b80bc91827e4b49caa90027ec00e6a8eacf18214/lib/matplotlib/legend_handler.py#L391-L407) while PathCollection has get_edgecolor (which has the colour we want in the legend) and get_facecolor (which gives an empty array). PathCollection seems to be mostly used for scatterplots, which do seem to be able to get sensible legends - I'm guessing maybe what's really being drawn is a zero size circle or something. Maybe there is some way to alter what is being drawn...

This issue https://github.com/matplotlib/matplotlib/issues/11134 also like it might contain something useful I'm enjoying my matplotlib code dive, so I'll keep playing

MJKirk commented 2 years ago

Just using the comments as a notebook, I think this is useful: https://stackoverflow.com/a/57030843 and https://stackoverflow.com/a/57030414

MJKirk commented 2 years ago

So after some playing around, I still don't understand why the new PathCollection doesn't draw a legend marker properly here, while it does when e.g. using plt.scatter. However, using the legend_elements() method I learnt about from https://github.com/matplotlib/matplotlib/issues/11134, plus inspiration from the solution of https://stackoverflow.com/questions/57024194/how-to-create-an-ax-legend-method-for-contourf-plots-that-doesnt-require-pass/57030414#57030414, I have a solution that works nicely, in both matplotlib v3.4.3 and v3.5.0 - see this commit.

The legend_elements function returns a LineCollection for each of the contours drawn, plus default labels with the contour value. They are designed to be added to the legend manually I think (see the matplotlib issue).

To make it automatic, I do a little hack by adding the LineCollection to the list of the current axes containers. This list gets checked for things with labels to add to the legend when plt.legend is called, so then everything works as expected. This is a bit nasty - I suspect this is not how the container list is supposed to be used, and I don't understand enough of the architecture of matplotlib to really be sure whether this is liable to break in the future.

MJKirk commented 2 years ago

@peterstangl, shall I make my fix into a pull request? It's not perfect, but I think it's good enough

peterstangl commented 2 years ago

@peterstangl, shall I make my fix into a pull request? It's not perfect, but I think it's good enough

@MJKirk, I think it looks good and it would be nice if you would put it into a PR. Thanks!