Different colors for bars in barchart by their value

Hi! I have a barchart and want to color bars that have value<30% with color1, value<50% with color2 , value<89% with color3 and >=90% with color4. How can i do it dinamically as i dont know how many bars will be in each category and cant set color manually?

Hey @roman,
To color the bars with different colors define a list of colors of length equal to the number of bars,
and insert it as a value for the key color in the dict marker.

Here is a short example:

y=np.array([10, 12, 15, 19, 28, 25, 31, 33, 43])
color=np.array(['rgb(255,255,255)']*y.shape[0])
color[y<20]='rgb(204,204, 205)'
color[y>=20]='rgb(130, 0, 0)'

data=[dict(type='bar',
       y=y,
       marker=dict(color=color.tolist()) 
      )]
5 Likes

How do we write this code so that we can create legend entries though? e.g. ‘good’, ‘bad’ ‘ugly’ for 'green, ‘orange’, ‘red’?

This is pretty basic functionality that is missing either in the docs or worse in the Plotly package.

An example of my problem is in extending the ‘Customizing Individual Bar Colors’ example in the docs to show a customized legend:

How does one show the legend whilst retaining a sorted order of the bars? Any help would be much appreciated.

Were you able to add custom legends for different colors ?

Unfortunately not but it is a problem I have to revisit some point soon so will post a solution if I find one. I produce most of my solutions in dash, so it is likely that i will use dash to present a custom built legend usig standard html components and let plotly render the bar graph without a legend.

Hi @SDesai, and @eddy_oj,

Legend entries show up on a per-trace basis, so you would need to use a separate bar trace for each group (not each bar, just each group of bars that you want to share a single legend entry).

Here’s a full example that does the binning and grouping using pandas

# imports
import plotly.graph_objs as go
import numpy as np
import pandas as pd

# Define bar properties
bar_heights = [10, 12, 15, 19, 25, 28, 31, 33, 43, 50, 64, 72, 88, 105]
bins = [0, 25, 40, 80, 200]
labels = ['Ugly', 'Bad', 'Good', 'Great']

colors = {'Ugly': 'red',
          'Bad': 'orange',
          'Good': 'lightgreen',
          'Great': 'darkgreen'}

# Build dataframe
df = pd.DataFrame({'y': bar_heights,
                   'x': range(len(bar_heights)),
                   'label': pd.cut(bar_heights, bins=bins, labels=labels)})
df.head()
        y	x	label
0	10	0	Ugly
1	12	1	Ugly
2	15	2	Ugly
3	19	3	Ugly
4	25	4	Ugly
bars = []
for label, label_df in df.groupby('label'):
    bars.append(go.Bar(x=label_df.x,
                       y=label_df.y,
                       name=label,
                       marker={'color': colors[label]}))

go.FigureWidget(data=bars)

Hope that helps!
-Jon

5 Likes

Thanks Jon,

This does help for some scenarios, thank you for taking the time to create this for us. Depending on the use case, sort order does not necessarily correspond to color order. For example a the “8” index in your bar chart might belong to the “ugly” category for some reason - attributes like the color need to be independent of the order of the trace list like in other visualisation software, such as Tableau. It is an issue I need to raise on Plotly’s github.

Best regards

Ed

Hi Ed,

Perhaps I’m not understanding what you mean, but the colors in my example are independent of the order of the input data. labels defines the legend order and colors defines the color mapping for each legend entry. Here’s what it looks like if I randomly shuffle bar_heights by running np.random.shuffle(bar_heights) after defining bar_heights as above.

-Jon

2 Likes

Hi
thank you for the example Jon!
I am not familiar with DataFrame. I tried to modify it to have name (instead of index in the X axis) but it groups by grades and not compliant with the order of the data as input.

Are you guys serious? Oh my god. Do you realise that in ggplot you just need to pass fill = ‘variable_name’ and it does it automatically ?

I come from a background in R using ggplot2, I am now using mostly Python. I thought I could try ggplot for python but it’s a mess. I have then read about plotly. Seems to work nicely at the beginning, but then I cannot create a bar chart with the bars coloured according to a variable without writing a for loop? Please tell me I am missing something here…

2 Likes

I do not get your point, you can also pass just colors like shown here.

import plotly.graph_objects as go

colors = ['lightslategray',] * 5
colors[1] = 'crimson'

fig = go.Figure(data=[go.Bar(
    x=['Feature A', 'Feature B', 'Feature C',
       'Feature D', 'Feature E'],
    y=[20, 14, 23, 25, 22],
    marker_color=colors # marker color can be a single color value or an iterable
)])
fig.update_layout(title_text='Least Used Feature')

This page shows how to control discrete colors in Plotly v4 with Plotly Express, and it works a lot like ggplot: https://plotly.com/python/discrete-color/

I should add that you can also apply continuous colors to bars: https://plotly.com/python/colorscales/

1 Like

How can this be done with px.bar?

1 Like

You have to specify, e.g. color_discrete_sequence=["red", "green", "blue", "goldenrod", "magenta"] in fig = px.bar, see Discrete Colors | Python | Plotly

Thanks windrose, i managed to get it working with: .update_traces(marker = dict(color=list(map(SetColor, filtered_df[‘Score’]))))

your suggestion didnt work how i wanted it.

1 Like

What about labels using plotly express?

I am not sure; does something like

fig.add_annotation(text="p = 0.5111",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.23, y=0.640, showarrow=False,
                   font=dict(size=12, color="black")
                  )

work for you?

ill try it, im trying to add something like this, so the y value and the text associated:

def SetColor(y):
if(y <= 1):
return “red”
elif(y <= 2):
return “orange”
elif(y <= 3):
return “yellow”
elif(y <= 4):
return “lightgreen”
elif(y <= 5 | 6):
return “green”
elif(y <= 7):
return “darkgreen”
elif(y <= 8):
return “silver”
elif(y <= 9):
return “gold”
def Setlabel(y):
if(y <= 1):
return “Very low (1)”
elif(y <= 2):
return “Low (2)”
elif(y <= 3):
return “Below average (3)”
elif(y <= 4):
return “Average (4)”
elif(y <= 5 | 6):
return “Average (5, 6)”
elif(y <= 7):
return “Above average (7)”
elif(y <= 8):
return “High (8)”
elif(y <= 9):
return “Very High (9)”

That may work, can i align that to a series on my y df[‘score’] value?