2025, Oct 28 07:00

Color Each X-Axis Group (Not Traces) in a Plotly Express Grouped Bar Chart with update_traces

Learn to color each x-axis group in a Plotly Express grouped bar chart using update_traces(marker_color), overriding trace palettes for groupwise colors.

Color each x-axis group in a Plotly Express grouped bar chart

When building grouped bar charts in Plotly Express from a wide-form DataFrame, it’s common to want each x-axis group to share a single color, rather than coloring by series. A straightforward approach like setting a color palette often changes the color of entire traces (columns) across all groups, which isn’t what you want if your goal is “one color per group.”

Reproducing the setup

The example below creates a grouped bar chart from a 3×3 DataFrame. The attempt to control colors uses a discrete sequence, which applies per trace and leads to uniform coloring across groups.

import plotly.express as px
import pandas as pd
df_ph_levels = pd.DataFrame([[8.33, 8.36, 8.36], [8.21, 8.18, 8.21], [7.50, 7.56, 7.64]], index=["4 °C", "37 °C", "37 °C + 5 % CO<sub>2"])
plot_ph = px.bar(df_ph_levels, barmode="group", color_discrete_sequence=["black"]*3)
# Layout tuning
plot_ph = plot_ph.update_layout(
    showlegend=False,
    xaxis_title_text=None,
    yaxis_title_text="pH",
    width=900,
    height=800,
    margin=dict(t=0, b=0, l=0, r=0),
    yaxis_range=(6.5, 8.6)
)
plot_ph

What’s going on and why colors don’t change per group

With wide-form input, Plotly Express creates one trace per DataFrame column. The parameter that feeds a color palette, such as a discrete sequence, controls trace colors. That’s why you can recolor the first, second, or third column across the entire chart, or make everything a single color, but you can’t directly set a different color for each x-axis group using that mechanism.

The fix: color by bar position using update_traces

To color by group, pass an array of colors to the bars inside each trace and remove the discrete sequence. This is done by calling update_traces with marker_color set to a list of desired colors. Each trace will then use the same color per bar position, making all bars in a given x-group share the same color.

import plotly.express as px
import pandas as pd
df_ph_levels = pd.DataFrame([[8.33, 8.36, 8.36], [8.21, 8.18, 8.21], [7.50, 7.56, 7.64]], index=["4 °C", "37 °C", "37 °C + 5 % CO<sub>2"])
plot_ph = px.bar(df_ph_levels, barmode="group")
# Layout tuning
plot_ph = plot_ph.update_layout(
    showlegend=False,
    xaxis_title_text=None,
    yaxis_title_text="pH",
    width=900,
    height=800,
    margin=dict(t=0, b=0, l=0, r=0),
    yaxis_range=(6.5, 8.6)
)
# Assign colors per x-group
plot_ph.update_traces(marker_color=["#0C3B5D", "#EF3A4C", "#FCB94D"]) 
plot_ph.show()

Why this detail matters

Choosing whether colors encode traces or groups affects how people read the chart. If you need each category on the x-axis to stand out as a block, this approach keeps the visual mapping clean and avoids fighting with palette settings that operate at the trace level.

Conclusion

If a discrete palette keeps recoloring entire series instead of x-axis groups, switch to update_traces with marker_color and provide the colors you want for the groups. Avoid combining that with a color_discrete_sequence so nothing overrides your per-bar colors. This small adjustment gives precise control over how grouped bars are colored and makes the chart communicate your intent clearly.

The article is based on a question from StackOverflow by Václav Bočan and an answer by strawdog.