Search code examples
python-3.xpandasplotlyplotly-python

How to find the x coordinate on a grouped bar chart on plotly express?


Following on from the response here How to specify the x coordinate on a grouped bar chart on plotly?

I would like to get ALL the x coords, programatically.

I can get a working solution by playing around with hard coded numbers like:

import plotly.express as px
import plotly.graph_objects as go
import numpy as np

df = px.data.tips()
print(df.head())
fig = px.histogram(
    df, x="sex", y="total_bill", color="time", barmode="group", height=400
)

# Add secondary x-axis that overlays categorical xaxis
fig.layout.xaxis2 = go.layout.XAxis(
    overlaying='x', range=[0, 2], showticklabels=False)

# Add a line traces, and associate with the second xaxis
bargap = 0.2
for i in range(2):
    x = bargap/2 + (1-bargap)/4
    y = [1111]
    print(111,x, y)
    scatt = fig.add_scatter(x=[i+x], y=y, xaxis='x2',
                            showlegend=False, line={'color': 'gray'})
    x *=2.3
    print(22222,x, y)
    scatt = fig.add_scatter(x=[i+x], y=y, xaxis='x2',
                            showlegend=False, line={'color': 'gray'})
fig

But I need a dynamic solution that works when I change color to day:

fig = px.histogram(
    df, x="sex", y="total_bill", color="day", barmode="group", height=400
)

and then have 4 boxes in the sub plot. In the data I'm working with there's always a different number of plots and sub plots.

I really hope the devs from plotly can help with this, it's the first major limitation I have come up against using plotly.


Solution

  • Found it.

    import plotly.express as px
    
    df = px.data.tips()
    
    # Use primary and secondary in your function to vary
    
    primary = 'day'
    secondary = 'sex'
    
    fig = px.histogram(df, x=primary, y="total_bill", color=secondary, barmode="group", height=400)
    
    nPri = len(df[primary].unique())
    nSec = len(df[secondary].unique())
    
    N = nPri
    
    # Group centers
    xrange = N-1  # the underlying x-axis is scaled with N automatically
    xGp = [xrange * n / (N-1) for n in range(N)]
    
    # Plot group centers
    for ix in xGp:
        fig.add_vline(x=ix)
    
    bargap = 0.2  # I tried it with some variation. Still works.
    fig.layout.bargap = bargap
    
    # Calculate bar sizing
    gpWidth = 1 - bargap
    barWidth = gpWidth / nSec
    
    # Left and right edges of each group
    gpEdges = [(x - gpWidth / 2, x + gpWidth / 2) for x in xGp]
    
    for ix1, ix2 in gpEdges:
    
        gpRange = ix2 - ix1 # group range
    
        xB = [ix1 + barWidth/2 + barWidth * n for n in range(nSec)]  # list of x coordinates of each bar in the group
        print(xB)
    
        for x in xB:
            fig.add_vline(x=x, line_color='blue')
    
    fig.show()