Search code examples
pythonplotly-python

How to format plotly legend when using marker color?


I want to follow up on this post: Plotly: How to colorcode plotly graph objects bar chart using Python?.

When using plotly express, and specifying 'color', the legend is correctly produced as seen in the post by vestland.

This is my plotly express code:

data = {'x_data': np.random.random_sample((5,)),
        'y_data': ['A', 'B', 'C', 'D', 'E'],
        'c_data': np.random.randint(1, 100, size=5)
       }

df = pd.DataFrame(data=data)

fig = px.bar(df,
             x='x_data',
             y='y_data',
             orientation='h',
             color='c_data',
             color_continuous_scale='YlOrRd'
            )
fig.show()

But when using go.Bar, the legend is incorrectly displayed as illustrated here:

enter image description here

This is my code using graph objects:

bar_trace = go.Bar(name='bar_trace',
               x=df['x_data'],
               y=df['y_data'],
               marker={'color': df['c_data'], 'colorscale': 'YlOrRd'},
               orientation='h'
              )

layout = go.Layout(showlegend=True)

fig = go.FigureWidget(data=[bar_trace], layout=layout)

fig.show()

I'm learning how to use FigureWidget and it seems it can't use plotly express so I have to learn how to use graph objects to plot. How do I link the legend to the data such that it works like the plotly express example in vestland's post.


Solution

  • This really comes down to understanding what a high level API (plotly express) does. When you specify color in px if it is categorical it creates a trace per value of categorical. Hence the below two ways of creating a figure are mostly equivalent. The legend shows an item for each trace, not for each color.

    import pandas as pd
    import plotly.express as px
    import plotly.graph_objects as go
    import numpy as np
    
    df = pd.DataFrame({"x":np.linspace(0,10,10), "y":np.linspace(5,15,10), "color":np.random.choice(list("ABCD"),10)})
    
    px.bar(df, x="x", y="y", color="color", orientation="h").show()
    
    fig = go.Figure()
    for g in df.groupby("color"):
        fig.add_trace(go.Bar(x=g[1]["x"], y=g[1]["y"], name=g[0], orientation="h"))
    
        
    fig
    

    supplementary based on comments

    • you do not have to use graph objects if you are using FigureWidget() as demonstrated by second figure, create with plotly express and then generate FigureWidget()
    • for continuous data normal pattern is to use a single trace and a colorbar (also demonstrated in second figure). However if you want a discrete legend, create a trace per value in c_data and use https://plotly.com/python-api-reference/generated/plotly.colors.html sample_colorscale()
    import plotly.express as px
    import plotly.colors
    import plotly.graph_objects as go
    import numpy as np
    import pandas as pd
    
    # simulate data frame...
    df = pd.DataFrame(
        {
            "x_data": np.linspace(0, 10, 10),
            "y_data": np.linspace(5, 15, 10),
            "c_data": np.random.randint(0, 4, 10),
        }
    )
    
    # build a trace per value in c_data using graph objects ... correct legend !!??
    bar_traces = [
        go.Bar(
            name="bar_trace",
            x=d["x_data"],
            y=d["y_data"],
            marker={
                "color": plotly.colors.sample_colorscale(
                    "YlOrRd",
                    d["c_data"] / df["c_data"].max(),
                )
            },
            orientation="h",
        )
        for c, d in df.groupby("c_data")
    ]
    
    layout = go.Layout(showlegend=True)
    
    fig = go.FigureWidget(data=bar_traces, layout=layout)
    fig.show()
    
    fig = px.bar(
        df,
        x="x_data",
        y="y_data",
        color="c_data",
        orientation="h",
        color_continuous_scale="YlOrRd",
    )
    fig = go.FigureWidget(data=fig.data, layout=fig.layout)
    fig.show()
    

    enter image description here