hvplot icon indicating copy to clipboard operation
hvplot copied to clipboard

[Bug] Legend order backwards for stacked bar plots

Open mullimanko opened this issue 3 years ago • 11 comments

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

mullimanko avatar Jan 24 '21 16:01 mullimanko

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?

jlstevens avatar Jan 25 '21 16:01 jlstevens

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

marfel avatar Mar 11 '22 14:03 marfel

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.

jbednar avatar Mar 11 '22 19:03 jbednar

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

marfel avatar Mar 14 '22 07:03 marfel

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

stas-sl avatar Mar 14 '22 17:03 stas-sl

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

marfel avatar Mar 15 '22 09:03 marfel

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.

jbednar avatar Mar 15 '22 11:03 jbednar

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

stas-sl avatar Mar 15 '22 13:03 stas-sl

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)

philippjfr avatar Dec 06 '23 15:12 philippjfr

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

jbednar avatar Dec 06 '23 15:12 jbednar

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])

philippjfr avatar Dec 06 '23 16:12 philippjfr