Control distance between stacked bars?

Is there a means to control the distance between stacked bars in the example below, i.e. between the “cat” and “dog” (black arrow) and between “10 minutes” and “30 minutes” (yellow arrow)?

import plotly.graph_objects as go
x = [
    ["10 minutes", "10 minutes", "30 minutes", "30 minutes", "60 minutes", "60 minutes"],
    ["Cat", "Dog", "Cat", "Dog", "Cat", "Dog"]
]
fig = go.Figure()
fig.add_bar(x=x,y=[1,2,3,4,5,6], name="increase")
fig.add_bar(x=x,y=[9,8,7,6,5,4], name="decrease")
fig.add_bar(x=x,y=[5,5,5,5,5,5], name="unchanged")

fig.update_layout(barmode="stack", showlegend=True, template='plotly',
                  #bargap=0.3,
                  bargroupgap=0.2,
                  #height=500,
                  #width=1000,
                 )
fig.update_yaxes(ticks='outside',
                 dtick=2,
                 ticklen=10,
                 linewidth=1,
                 linecolor="black",
                 tickfont=dict(size=20)
                )
fig.update_xaxes(ticks="", tickfont=dict(size=12),                 
                )
fig.show()

Got a little further, but now the 10 minutes, 30 minutes, and 60 minutes are gone; any suggestions on this?

import plotly.graph_objects as go
x = [
    ["10 minutes", "10 minutes", "30 minutes", "30 minutes", "60 minutes", "60 minutes"],
    ["Cat", "Dog", "Cat", "Dog", "Cat", "Dog"]
]

fig = go.Figure()
fig.add_bar(x=[1,1.5,2.5,3,4,4.5],y=[1,2,3,4,5,6], name="increase")
fig.add_bar(x=[1,1.5,2.5,3,4,4.5],y=[9,8,7,6,5,4], name="decrease")
fig.add_bar(x=[1,1.5,2.5,3,4,4.5],y=[5,5,5,5,5,5], name="unchanged")

fig.update_layout(barmode="stack", showlegend=True, template='plotly_white',
                  height=500,
                  width=1000,
                  legend=dict(title='', orientation="h", traceorder='normal', x=0.55, y=1,
                  bgcolor='rgba(0,0,0,0)', bordercolor='rgba(0,0,0,1)', borderwidth=0,
                  font_size=16,
)
                 )

fig.update_yaxes(showline=True, showgrid=False, linewidth=0.5, linecolor='black',
                 ticks='outside',
                 dtick=2,
                 ticklen=10,
                 tickfont=dict(size=20),
                 range=[0,20]
                )

fig.update_xaxes(title='', tickvals=[1,1.5,2.5,3,4,4.5],
                           ticktext=["Cat", "Dog",
                                     "Cat", "Dog",
                                     "Cat", "Dog"],
                           ticks="outside", tickfont_size=12, linecolor="black", linewidth=1
                ) 


fig.show()

gives

And now with error bars and time via annotations; perhaps this is useful for someone too.

import plotly.graph_objects as go

y1_values=[5,4,4,5,6,5]
y2_values=[9,8,7,6,5,4]
y3_values=[4,6,7,7,7,9]

y1_errors = [0.5, 0.6, 0.4, 0.7, 0.5, 0.8]
y2_errors = [0.4, 0.3, 0.5, 0.7, 0.3, 0.5]
y3_errors = [0.6, 0.2, 0.3, 0.4, 0.4, 0.4]

fig = go.Figure()
#config = dict({'scrollZoom': True})


# Bars for increase with error bars
fig.add_bar(x=[1,1.5,2.2,2.7,3.4,3.9],y=y3_values, name="Increase", marker_color='#FF6692',
            error_y=dict(type="data", array=y3_errors, thickness=2, width=10),
            base=[sum(x) for x in zip(y1_values, y2_values)]
           )

# Bars for decrease with error bars
fig.add_bar(x=[1,1.5,2.2,2.7,3.4,3.9],y=y2_values, name="Decrease", marker_color='#19D3F3',
            error_y=dict(type="data", array=y2_errors, thickness=2, width=10),
            base=y1_values
           )

# Bars for unchanged with error bars
fig.add_bar(x=[1,1.5,2.2,2.7,3.4,3.9],y=y1_values, name="Unchanged", marker_color='#00CC96',
            error_y=dict(type="data", array=y1_errors, thickness=2, width=10),
            base=[0]*6
           )

# Annotation for bar1 and bar2
fig.add_annotation(text="10 minutes",
                  xref="paper", yref="paper",
                  x=0.07, y=-0.18,
                  showarrow=False,
                  font_size=20
                  )

# Annotation for bar3 and bar4
fig.add_annotation(text="30 minutes",
                  xref="paper", yref="paper",
                  x=0.5, y=-0.18,
                  showarrow=False,
                  font_size=20
                  )

# Annotation for bar5 and bar6
fig.add_annotation(text="60 minutes",
                  xref="paper", yref="paper",
                  x=0.93, y=-0.18,
                  showarrow=False,
                  font_size=20
                  )

# Layout
fig.update_layout(barmode="stack", showlegend=True, template='plotly_white',
                  height=700,
                  width=1000,
                  legend=dict(title='', orientation="h", traceorder='normal', x=0.46, y=1.05,
                  bgcolor='rgba(0,0,0,0)', bordercolor='rgba(0,0,0,1)', borderwidth=0,
                  font_size=20
                             )
                 )

fig.update_yaxes(showline=True, showgrid=False, linewidth=0.5, linecolor='black',
                 title='<b>Percent</b>', titlefont=dict(size=24), title_standoff=30,
                 ticks='outside',
                 dtick=2,
                 ticklen=10,
                 tickfont=dict(size=20),
                 range=[0,20]
                )

fig.update_xaxes(title='', tickvals=[1,1.5,2.2,2.7,3.4,3.9],
                           ticktext=["<b>Cat</b><br>n = 15", "<b>Dog</b><br>n = 15",
                                     "<b>Cat</b><br>n = 15", "<b>Dog</b><br>n = 15",
                                     "<b>Cat</b><br>n = 15", "<b>Dog</b><br>n = 15"],
                           ticks="", tickfont_size=20, linecolor="black", linewidth=1
                ) 

fig.show()

Hi @windrose !
Seems you found what you were looking for, I was going to propose to use subplots.
Here an example, if that can inspire you: :slightly_smiling_face:

import plotly.express as px

data = {
    'x': ['Cat', 'Dog'] * 9,
    'y': [1, 2, 3, 4, 5, 6, 9, 8, 7, 6, 5, 4, 5, 5, 5, 5, 5, 5],
    'color': [*["increase"] * 6, *["decrease"] * 6, *["unchanged"] * 6],
    'facet_col': [*["10 minutes"] * 2, *["30 minutes"] * 2, *["60 minutes"] * 2] * 3,
}

fig = px.bar(data, x="x", y="y", color="color", facet_col="facet_col")

# little customisations
fig.for_each_annotation(lambda a: a.update(text=''))# remove the facet titles
fig.update_layout(plot_bgcolor='rgba(0,0,0,0)')
fig.update_yaxes(title_text="", ticks="outside", linecolor="black", row=1, col=1)

# To modify the gaps between subplots, you can modify the domain of their xaxis
def domains_calculator(gap, n_plot):
    """
    little helper to calculate the range of subplots domains
    """
    plot_width = (1 - (n_plot-1) * gap) / n_plot
    return [
        [i * (plot_width + gap), i * (plot_width + gap) + plot_width]
        for i in range(n_plot)
    ]

domains = domains_calculator(gap=0.05, n_plot=3)

fig.update_xaxes(title_text="10 minutes", domain=domains[0], row=1, col=1)
fig.update_xaxes(title_text="30 minutes", domain=domains[1], row=1, col=2)
fig.update_xaxes(title_text="60 minutes", domain=domains[2], row=1, col=3)

# and to modify the gap between the subplots bars, you can modify their width
fig.update_traces(width=0.7)

fig.show()

1 Like

@Skiks Awesome, thanks a lot for taking the time to help me out. Yours is much more skifull, I guess! Thanks a lot again.

You are welcome!
There is never only one way to do things, as long as you get what you need! :smile:

1 Like