FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[ENH]: API discussion for grouped bar charts · Issue #24313 · matplotlib/matplotlib · GitHub

Repository navigation

[ENH]: API discussion for grouped bar charts #24313

Closed

Description

Problem

Currently, if one wants to draw multiple categories of bars side-by-side as in https://matplotlib.org/devdocs/gallery/lines_bars_and_markers/barchart.html, one has to calculate the bar positions manually. This is really a nuissance and too low level for any user-facing interface. Actually, when asked I recommend to use pandas plotting functions for that if possible, which is really embarrasing.

There have been stalled attempts to do this (issure 10610, PR #11048). Additionally, this often gets related to a function for stacked bars #14086.

Proposed solution

I'd like to pick up this topic and come up with a reasonable API. This topic is really complex and one can easily get lost in various details. For the design procedure, I take a bottom-up appoach by starting with a basic function grouped_bar() that does only expose the minimal functionality to get the plot done. I then intended to additional parameters one by one as they fit in. - So don't be concerned that the first proposal here is quite basic.

Terminology I'll use label for the x-values, i.e. 'G1' .. 'G5' in above example, and group for the categories, i.e. 'Tea'/'Coffee'.


For now: Only vertical orientation

I'll limit the discussion here to 'vertical'. We can have a separate discussion whether we want to add an orientation parameter or make a grouped_barh. Both are technically easy and only an API design decision that's orthogonal to the rest of the API.

For now: Only grouped layout, no stacked layout

To keep things simple, I'll limit myself to grouped for now, because:

  • Stacked is somewhat simpler than grouped as you only need to insert the bottom values in multiple calls. For two bars it's just the heights of the firsts, and a cumsum for more than two.
  • We may build a separate stacked_bar() function, if the first bullet point is considered too cumbersome.
  • It's conceivable to unite both in one function as poposed in Feature: Plot multiple bars with one call #11048 and realized in DataFrame.plot.bar, but that needs careful additional consideration.

either way let's defer stacked bars to later.

Minimal API

We want to be able to rewrite https://matplotlib.org/devdocs/gallery/lines_bars_and_markers/barchart.html as

grouped_bar(labels, [tea_means, coffee_means], group_labels=['Tea', 'Coffee']) 

Thus, the minimal API is:

def grouped_bar(x, heights, *, group_labels=None):
    """
    Parameters
    -----------
    x : array-like of str
         The labels.
    heights : list of array-like:
         An iterable of array-like: The iteration runs over the groups.
         Each individual array-like is the list of label values for that group.
    group_labels : array-like of str, optional
         The labels of the data groups.
    """

I'll soon expand on the minimal API, answering a lot of questions from #11048 (comment). But before that, please speak up in case you have fundamental concerns with adding such functionality at all or with the bottom-up design approach. OTOH if you think this is worth pursuing, please give a 👍.

Activity

  1. story645 commented on Oct 30, 2022

    Member

    Having messed around w/ implementing this (stacked+grouped), yeah I guess mostly same questions as that comment. I think where it gets really messy is how do you set up the broadcasting on the encodings/aesthetics?

    • Do we allow folks to only color and hatch by group or are they allowed to do it by individual bar/segment? (I have concerns about trying to do anything smart here/continue to believe in vectorize all the things but then that should be pushed down to bar)
    • Are we being explicit that we don't do computation and they have to pass in the pivot table? (and my bias is that they pass in the pivot table and then tell us which axes is the groups, which is kind of what pandas is doing, or I guess we be explicit about groups/values)
    • also how do we handle custom widths? disallow it, require all groups to have the same, disallow it only for stacked charts?
    • do we allow an initial set of x positions or handle that all internally?

    My bias is to make it 4 functions that kind of call all the same things underneath rather than 1 function that underneath dispatches to the 4 functions that call the same things w/ different parameters. But also then right, we can start with the 4 and if there's enough annoyance add the 1.

  2. timhoffm commented on Oct 30, 2022

    MemberAuthor

    @story645 I have answers (and opinions 😄) on all of these. I will hold off diving into the details for a bit to see whether there is general high-level feedback.

  3. story645 commented on Oct 31, 2022

    Member

    Uh @mwaskom any opinions? Also @ksunden since stacked bars are my fave discrete topology ;)

  4. mwaskom commented on Oct 31, 2022

    No particular thoughts here; I doubt that seaborn would use this API.

  5. oscargus commented on Oct 31, 2022

    Member

    I think it would be nice (although I realize pretty hard) to have a single bar-call that can do grouped and stacked. Or at least be able to relatively easy do both grouped and stacked (as in the same bar graph). I cannot say that I have a particular use case in mind, but considering that plots tend to run out of dimensions it may be worth considering if we are going for it.

    I have not read up, but I think the 2D-path is probably to be preferred? Now, of course that can be introduced via the grouped_bar approach. (For stacked and grouped, 3D?)

  6. tacaswell commented on Oct 31, 2022

    Member

    I do think we will want to handle stacked vs adjacent eventually (bokeh calls adjacent "doged" https://docs.bokeh.org/en/latest/docs/gallery/bar_dodged.html). In both cases you are answering the question of "I have N bars that should be 'at the same point' how do I de-conflict them?". Another reason to merge the two cases into one is so that they can have a more coherent story around how to make legend entries for the set at once. Should probably do this with an eye for how to both stack and dodge?

    I am 👍🏻 on this (as I still agree with what I wrote in the linked 2018 comment even though I have no memory of writing it) over all.

  7. timhoffm commented on Nov 1, 2022

    MemberAuthor

    All agreed. It should be fairly to have a multi_bar() method that that can handle grouped or stacked bars. I'm more sceptical on grouped+stacked because that adds an additional dimension to the inputs. - Likely the best solution for that is still a sequence of grouped bar plots with offsets (similar what one has currently do for stacking).

  8. jklymak commented on Nov 1, 2022

    Member

    Where our api gets in trouble is guessing what the user wants. I'd have two methods for these two concepts.

  9. mwaskom commented on Nov 1, 2022

    Is it worth mentioning the precedent that hist has these as options within the same method?

  10. story645 commented on Nov 1, 2022

    Member

    Is it worth mentioning the precedent that hist has these as options within the same method?

    As a pro or con?

  11. mwaskom commented on Nov 1, 2022

    Heh. Yeah, for consistency this would suggest there should be only one method, but then again I think the hist API is kind of a cautionary tale...

  12. story645 commented on Nov 1, 2022

    Member

    for consistency this would suggest there should be only one method

    we have .bar and .barh and that could easily be one method w/ an orientation parameter...

    I can sort of see both sides of this, in that if it's separate methods than grouped will have to take bottom so it can stack or stacked will have to take widths so it can group or we have a long gallery example "groupstack" using .bar to make a group-stack.

    But the data structure for stacked group is gonna be messy and my bias is if we do it we make a 3D array, where we explicitly document which axis means what, so changing what's stacked and what's grouped is a transpose and we only infer when stacked=False or grouped=False. Which which one, stacked or grouped, gets to be the default?

    Also I have concerns about the discoverability, given folks often miss that they can do streamplots with stackplot because mostly only the area chart is shown.

  13. mwaskom commented on Nov 2, 2022

    we have .bar and .barh and that could easily be one method w/ an orientation parameter...

    Ah, the way hist does it! :D

  14. timhoffm commented on Nov 2, 2022

    MemberAuthor

    If viable, parameters have the advantage of not leading to combinatory explosion.

    We likely don't want end up with

    • bar()
    • barh()
    • grouped_bar()
    • grouped_barh()
    • stacked_bar()
    • stacked_barh()
  15. 6 remaining items

  16. timhoffm commented on Nov 3, 2022

    MemberAuthor

    Did you try to solve this, or do you even have a solution ready? I think this is not trivial as the edge and data have different units, so even fine-tuning the distance would be brittle. IMHO the only proper solution would be a "draw line on the inside" concept, which OTOH results in fat lines if the edges have the same color.

    I think for now, we have to live with the overlap.

  17. mwaskom commented on Nov 3, 2022

    Agreed, this can’t be handled easily by positional adjustments. I think it needs to be solved in the way the path artist is drawn, but for now my workaround is to set a clip path on the patch. This works but has some weird artifacts in raster plots (AFK or else I’d demonstrate).

  18. tacaswell commented on Nov 3, 2022

    Member

    very offtopic:

    This is the issue with path stroking issue that came up breifly when we were talking about the AWS ask.

    The issue here is that when we stroke a path the notional infinitly thin path goes down the center of the drawn line, with half of the line width on either side. What I think we want here is the ability to stroke the path either inside our outside. On very quick search it looks like this was proposed in for SVG but not implemented (https://www.w3.org/TR/SVG/painting.html#SpecifyingStrokePaint). We could probably do this at draw time if we have a algorithm to shrink or grow an arbitrary path perpendicular to itself in screen coordinates (which we have very late in the draw process).

  19. mwaskom commented on Nov 5, 2022

    Ah, yeah, if this is the behavior of SVG primitives then I guess there's not much to do about it. FWIW my clipping hack works ok with relatively thick lines:

    But has a subtle artifact in other cases (even with "retina" mode DPI):

  20. tacaswell commented on Nov 6, 2022

    Member

    I strongly suspect the issue is that in the second case the lines are an odd-handful of pixel wide, the lines pixel-snap in Agg (to avoid anti-aliasing thin lines and making them look "fuzzy") and I would assume that other renderers do as well. If the clip path is similarly snapped then one side of the rectangles can be 1 pixel narrower and with very thin lines that is enough to catch our eye. The same effect is probably happening with the thicker lines but we are just less sensitive to it.

    For the examples I'm guessing "do not draw the edges" is not an option?

  21. added a commit that references this issue on Sep 11, 2024
  22. added a commit that references this issue on Dec 18, 2024
  23. QuLogic commented on Jun 21, 2025

    Member

    Is there any more to be done here now that #28560 is in?

  24. timhoffm commented on Jun 21, 2025

    MemberAuthor

    No, this can be closed.

  25. added this to the v3.11.0 milestone on Jun 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL