Custom legends for stacked bar charts with pattern_shape

So Iā€™m trying to create a bar chart where each bar is stacked and each has a specific color. In order to make the stacked part distinguishable, I tried both with marker_pattern_shape and marker_opacity. Both solutions works fine for the plots, but the legend is less than ideal; with the pattern if the color of the first bar (which is the one the legend will use) is a light color (making the pattern hard to identify); with the opacity since the legend marker does not reflect the difference.

The ideal solution would be if you could customize the color and size of markers in the legend, and/or if opacity was reflected in the markers in the legend.

animals=['seal', 'orangutans', 'monkeys']
animal_colors=['teal', 'orange', 'grey']

fig = go.Figure(data=[
    go.Bar(name='SF Zoo', x=animals, y=[20, 14, 23], marker_color=animal_colors),
    go.Bar(name='LA Zoo', x=animals, y=[12, 18, 29], marker_color=animal_colors, marker_pattern_shape='/')
])
# Change the bar mode
fig.update_layout(barmode='stack')
fig.show()
animals=['seal', 'orangutans', 'monkeys']
animal_colors=['teal', 'orange', 'grey']

fig = go.Figure(data=[
    go.Bar(name='SF Zoo', x=animals, y=[20, 14, 23], marker_color=animal_colors),
    go.Bar(name='LA Zoo', x=animals, y=[12, 18, 29], marker_color=animal_colors, marker_opacity=0.8)
])
# Change the bar mode
fig.update_layout(barmode='stack')
fig.show()

Cheers!

So I went ahead and figured out a solution using the fig.write_html methods post_script javascript injection keyword.

def fade_plotly_legend(labels_opacity: dict) -> str:
    """Create javascript code that changes the opacity of the legend markers in a plotly figure. 

    Args:
        labels_opacity (dict): A dictionary mapping the position of the legend markers to the opacity of the corresponding marker.

    Returns:
        str: JavaScript code that changes the opacity of the legend markers in a plotly figure.
        
    >>> fig.write_html("plot.html", post_script=fade_plotly_legend({0: 0.7, 1: 1, 2: 0.5}))
    """
    javascript = 'document.querySelector("g.traces:nth-child({n_child}) > g:nth-child(2) > g:nth-child(3) > g:nth-child(1) > path:nth-child(1)").style.opacity="{opacity}"'
    return ";".join(
        [
            javascript.format(n_child=n_child+1, opacity=opacity)
            for n_child, opacity in labels_opacity.items()
        ]
    )

And then you can simply add it like in the following example

animals=['seal', 'orangutans', 'monkeys']
animal_colors=['teal', 'orange', 'grey']

fig = go.Figure(data=[
    go.Bar(name='SF Zoo', x=animals, y=[20, 14, 23], marker_color=animal_colors),
    go.Bar(name='LA Zoo', x=animals, y=[12, 18, 29], marker_color=animal_colors, marker_opacity=0.8)
])
# Change the bar mode
fig.update_layout(barmode='stack')
# Note that while you build your stacked barchart from the bottom and up,
# the  legend markers seem to be indexed from top to bottom.
fig.write_html("plot.html", post_script=fade_plotly_legend({0: 0.8, 1: 1})) 

Hope this may help others until legend-customization is implemented in the package :slight_smile:

Changing color this way did not work, since interacting with the plot overwrites the element styles.