holoviz / hvplot

A high-level plotting API for pandas, dask, xarray, and networkx built on HoloViews
https://hvplot.holoviz.org
BSD 3-Clause "New" or "Revised" License
1.12k stars 108 forks source link

[Bug] Legend order backwards for stacked bar plots #551

Open mullimanko opened 3 years ago

mullimanko commented 3 years ago

This example here shows it well: https://hvplot.holoviz.org/reference/pandas/bar.html

Enabling the legend in a stacked bar plot displays the legend items in the opposite order of how they are stacked. One would expect them to be in the same order.

Unbenannt

This might be somehow related: https://github.com/bokeh/bokeh/issues/9135

jlstevens commented 3 years ago

Thanks for the suggestion!

I agree the order should match. I'll file this as bug though it is fairly minor as things are still understandable without the ordering be right.

@philippjfr Labelling this as a good first issue as it might be utterly trivial but depending on the internals it be the opposite. What do you think?

marfel commented 2 years ago

Same issue here, for an area plot. I think it would be even more useful to be able to specify the order manually. It seems to be alphabetical by default? Anyway, in my case I have one series of data (red) that produces ugly kinks in the curves, and it would be great to have the option of placing it at the top of the stacked graph. image

jbednar commented 2 years ago

This issue is about the legend rather than the main plot (and seems like an easy fix if someone wants to find the right spot in the code to stick [::-1]!!!). In any case, controlling the order in the main plot is definitely useful, and I would guess that it can be determined by reordering the columns in the underlying dataframe.

marfel commented 2 years ago

Nope, there seems to be some active reordering going on. For the plot above, the columns of the DataFrame were

Alts
Ober
Unte
Bron
Volk
Wohn
stas-sl commented 2 years ago

Also stumbled upon kind of related issue. Looks like you can specify order for bar chart, but for area it sorts alphabetically.

df = pd.DataFrame({'a': [2, 3, 1], 'b': [1, 2, 3], 'c': [3, 2, 1]})
df.hvplot.bar(y=['b', 'c', 'a'], stacked=True)
image
df = pd.DataFrame({'a': [2, 3, 1], 'b': [1, 2, 3], 'c': [3, 2, 1]})
df.hvplot.area(y=['b', 'c', 'a'], stacked=True)
image
marfel commented 2 years ago

Duh. Good to know, thanks! Interestingly, the legend is still ordered exacty the other way around than the graphs.

jbednar commented 2 years ago

That's promising; seems like flipping the legend order could be a quick fix in all these cases. I believe Bokeh determines the legend order, and it looks like Bokeh has already rejected a user request to reverse the default order, but does offer a way to reverse it: https://github.com/bokeh/bokeh/issues/8901 I'd assume that somewhere in the HoloViews bokeh plotting code for charts the same fix could be applied.

As for why area charts are sorted, I briefly looked through the Area, AreaPlot, and AreaMixIn classes and couldn't find any explicit sorting; that's a mystery! I do think HoloViews shouldn't be reordering them, which would be a separate issue to open and track down.

stas-sl commented 2 years ago

I do think HoloViews shouldn't be reordering them, which would be a separate issue to open and track down.

Agree, I opened it there https://github.com/holoviz/holoviews/issues/5235

philippjfr commented 10 months ago

We can argue about whether this behavior is desirable but hvPlot being a reimplementation of the pandas .plot API one can argue that consistency with pandas behavior should be the primary concern and that is the case:

df = pd.DataFrame({'a': [2, 3, 1], 'b': [1, 2, 3], 'c': [3, 2, 1]})
df.plot.bar(y=['b', 'c', 'a'], stacked=True)

download (36)

jbednar commented 10 months ago

Maybe the first step is to file a PR to fix that on Pandas? It's strongly desirable there as well.

philippjfr commented 10 months ago

Just to record what I was trying (which didn't work but I think should have) and then should have allowed us to provide a fix in HoloViews:

df = pd.DataFrame({'a': [2, 3, 1], 'b': [1, 2, 3], 'c': [3, 2, 1]})

legend = []
def hook(plot, element):
    plot.handles["plot"].legend[0].items[0].label.transform = CustomJSTransform(v_func="return xs.reverse()")

df = pd.DataFrame({'a': [2, 3, 1], 'b': [1, 2, 3], 'c': [3, 2, 1]})
df.hvplot.bar(y=['b', 'c', 'a'], stacked=True).opts(hooks=[hook])