Plotly express how to separate symbol and color in legend

I’m using python and plotly.express, I use categorical variables for both symbol and color, but the legend doesn’t separate symbol and color, they are grouped together. Is there way to show a legend for symbol and color separately, similar to this question: https://stackoverflow.com/questions/47539539/separate-symbol-and-color-in-plotly-legend

The code there is in R, but I’m using python and plotly.express. I tried to use similar method listed in the stackoverflow and use go.scatter, and first draw the lines, then draw points, however, I don’t know how to make go.scatter take a color that’s a column with categorical values in my dataframe, or take a marker shape that’s a column with categorical values in my dataframe. Please advise. Thank you

Hi @songsy welcome to the forum! This is not possible at the moment because one legend item corresponds to one trace. If you want to emulate this behaviour, you’ll have to modify the name of traces and to add dummy traces so that the symbol appears in the legend, as in the example below

import plotly.express as px
df = px.data.tips()
fig = px.scatter(df, x='total_bill', y='tip', color='day', symbol='sex')
for i, trace in enumerate(fig.data):
    name = trace.name.split(',')
    if name[1] == ' Male':
        trace['name'] = ''
        trace['showlegend']=False
    else:
        trace['name'] = name[0]
fig.add_trace(go.Scatter(y=[None], mode='markers',
                         marker=dict(symbol='circle', color='black'),
                         name='Female',
                         ))
fig.add_trace(go.Scatter(y=[None], mode='markers',
                         marker=dict(symbol='diamond', color='black'),
                         name='Male',
                         ))
fig.update_layout(legend_title_text='')
fig.show()

2 Likes

Thank you so much! Really appreciate it!

1 Like

The question is quite old, but I created a general function based on the answer of @Emmanuelle .
It should work with both markers and lines.
It also removes the click event handling which does not make much sense for the added traces.
It will create legends similar to the ones from seaborn.

def compress_legend(fig):
   group1_base, group2_base  = fig.data[0].name.split(",")
   lines_marker_name = []
   for i, trace in enumerate(fig.data):
       part1,part2 = trace.name.split(',')
       if part1 == group1_base:
           lines_marker_name.append({"line": trace.line.to_plotly_json(), "marker": trace.marker.to_plotly_json(), "mode": trace.mode, "name": part2.lstrip(" ")})
       if part2 != group2_base:
           trace['name'] = ''
           trace['showlegend']=False
       else:
           trace['name'] = part1
   
   ## Add the line/markers for the 2nd group
   for lmn in lines_marker_name:
       lmn["line"]["color"] = "black"
       lmn["marker"]["color"] = "black"
       fig.add_trace(go.Scatter(y=[None], **lmn))
   fig.update_layout(legend_title_text='', 
                     legend_itemclick=False,
                     legend_itemdoubleclick= False)
   
1 Like

Hi! I’m trying to do something similar (or it sounds similar in principle), but I haven’t managed to modify this code to make it work. The thing is that I’m plotting a PCA with 167 samples, coded by region and population, which creates a gigantic legend. Is there a way to only show in my legend the color variable and ignore the markers?