Create custom legend by hand

Hi,
I have the following problem and I would appreciate help.
I set two colors based on a criteria, but now I don’t see a way to represent that.
The color is saved in a column of a pandas dataframa.
I simply need a legend that has a green marker and a red marker with two different
texts. Red is for an Outlier/ for noise green is ok.

fig[x] = go.Scatter(y=df[x],
                                    mode="markers",
                                    marker=dict(
                                            color=df[isNoise]))

Cheers,
Blusser

Hi,

Would you be able to use plotly.express instead? Then you could simply do:

fig = px.scatter(df, y="x", ..., color="isNoise", color_discrete_map={"true": "red", "false": "green"} 

The discrete map assumes that the column isNoise has categories “true”/“false”, but it can be any string (“Outlier”/“OK” if this is what you want the legend to show).

If you still want to use go.Figure, you can simply create two scatter traces, one for each color. Then the legend will be shown because you have two traces (with one trace it will default to showlegend=False). If you want to rename the traces, you can simply provide name="what-you-want-the-legend-label-to-be".

Hope this helps!

I am using Plotly with graph objects because I have subplots. As far as I know, this is not possible with plotly exp.?
The code posted ist the minimal example. At the bottom I attached the rest of the code.

So what you are telling me, it is not possible to create a simple legend by hand?

Thanks for your help.


outliers={'X': 0,'Y': 0,'Z': 0 }
sigma={'X': 0,'Y': 0,'Z': 0 }

def analyse( filename):
    df = pd.read_csv(filename, float_precision='round_trip') # df= dfframe
    listOfAxis=["X", "Y", "Z"]
    fig={'X': None,'Y': None,'Z': None, }
    for x in listOfAxis:
        isNoise='is Noise '+str(x)
        #denoise(df, x,isNoise)
        sigma[x]=denoise(df, x,isNoise)
    df = pd.read_csv(filename, float_precision='round_trip') # df= dfframe

    for x in listOfAxis:
        isNoise='is Noise '+str(x)
        countNoise(df, x,isNoise,sigma[x])
        fig[x] = go.Scatter(y=df[x], mode="markers",
                                    marker=dict(
                                            color=df[isNoise]))
    print("#########"+str(filename)+"#########")
    strX=" Störungen auf der X-Achse: "+str(round(outliers["X"]/len(df.index),3))+"%"
    strY=" Störungen auf der Y-Achse: "+str(round(outliers["Y"]/len(df.index),3))+"%"
    strZ=" Störungen auf der Z-Achse: "+str(round(outliers["Z"]/len(df.index),3))+"%"
    print(strX)
    print(strY)
    print(strZ)
    print("###########################")
    #df = pd.concat([row_df, df], ignore_index=True)
    fig_main = make_subplots(
                                rows    = 3,  
                                cols    = 1,
                                #specs=[[{"type": "scatter"},{"type": "scatter"},{"type": "scatter"}]],             
                                 specs=[[{"type": "scatter"}],[ {"type": "scatter"}], [{"type": "scatter"}]],             
                                subplot_titles  =   ("Flussdichte X-Achse", "Flussdichte Y-Achse", "Flussdichte Z-Achse"))
    fig_main.add_trace(fig['X'], row=1, col=1)
    fig_main.add_trace(fig['Y'], row=2, col=1)
    fig_main.add_trace(fig['Z'], row=3, col=1)
    
    fig_main.add_annotation(dict(font=dict(color='black',size=20),
                                        x=0,
                                        y=0.70,
                                        showarrow=False,
                                        text=strX+strY+strZ,
                                        textangle=0,
                                        xanchor='left',
                                        xref="paper",
                                        yref="paper"))
    fontsize=30
    
    fig_main.update_layout( title_text=filename,
                   #height=595, 
                   #width=842,
                   width=3508,
                   height=2480 ,
                   title_font_size=fontsize,
                   legend_font_size=fontsize,
                   legend_grouptitlefont_size=fontsize,
                   legend_title_font_size=fontsize,
                   font_size=fontsize,
                   hoverlabel_font_size=fontsize,
                   showlegend=True,
                   hoverlabel_grouptitlefont_size=fontsize,
                   title={                                           #https://community.plotly.com/t/margins-around-graphs/11550/4
                            "yref"      : "container",
                            "y"         : 0.99,
                            "yanchor"   : "top"   
                          }) 
    fig_main.update_annotations(font_size=fontsize)
    plotly.offline.plot(fig_main, filename='page.html')


It depends on what is your intention with the subplots. You can for example use facets, which creates and handles the subplots internally. In your case it is actually very simple to adapt to use values in listOfAxis as facet_row and isNoise as color. All you need to do is to redefine the columns X, Y and Z => (value, axis), so each row of the data will have a single “coordinate” (x, y or z), and similar to isNoise. Like:

From: 

X | Y | Z | isNoiseX | isNoiseY | isNoiseZ
--- | --- | --- | --- | --- | --- 
0.5 | 1 | 2 | 1 | 0 | 1 

To: 

value | axis | isNoise
0.5  | X   | 1
1  | Y   | 0
2  | Z  | 1

No, never said that. :slight_smile: You can in principle create a fake trace just to create a legend item, but this is IMO inefficient in your case. Here is a recent post where I elaborate a bit on how to do it, just if you are curious…

My suggestion remains the same: use two traces instead of one, one for each color. Here is a quick example from a different (but very relatable) context:

import plotly.express as px
import plotly.graph_objects as go

df = px.data.stocks()

tickers = ["GOOG", "AAPL"]

fig = go.Figure().set_subplots(len(tickers), 1, shared_xaxes=True, subplot_titles=tickers)

df = df[["date"] + tickers]

for tick in tickers:
    df[f"{tick}_diff"] = df[tick].diff()


for idx, tick in enumerate(tickers):
    _red_df = df[df[f"{tick}_diff"] < 0]
    _green_df = df[df[f"{tick}_diff"] >= 0]
    fig.add_trace(
        go.Scatter(
            x=_red_df["date"],
            y=_red_df[tick],
            mode="markers",
            marker=dict(
                color="red"
            ),
            name=f"{tick} - Down"
        ),
        row=idx+1,
        col=1
    )
    fig.add_trace(
        go.Scatter(
            x=_green_df["date"],
            y=_green_df[tick],
            mode="markers",
            marker=dict(
                color="green"
            ),
            name=f"{tick} - Up"
        ),
        row=idx+1,
        col=1
    )

Output:

If you want a single legend item per color, then you just need to assign a legendgroup for the traces and hide duplicates. Like this:

for idx, tick in enumerate(tickers):
   # This is "equivalent" to your isNoise column 
    _red_df = df[df[f"{tick}_diff"] < 0]
    _green_df = df[df[f"{tick}_diff"] >= 0]
    fig.add_trace(
        go.Scatter(
            x=_red_df["date"],
            y=_red_df[tick],
            mode="markers",
            marker=dict(
                color="red"
            ),
            name=f"Down (all traces)",
            legendgroup="down",
            showlegend = idx == 0
        ),
        row=idx+1,
        col=1
    )
    fig.add_trace(
        go.Scatter(
            x=_green_df["date"],
            y=_green_df[tick],
            mode="markers",
            marker=dict(
                color="green"
            ),
            name=f"Up (all traces)",
            legendgroup="up",
            showlegend = idx == 0
        ),
        row=idx+1,
        col=1
    )

Would that be what you are looking for?

2 Likes

Alright thank you, then I will do as recommended.
Does that not decrease speed if i plot two dataframes insted of just choosing diffrent colors?

A bit, perhaps, but not by a lot… Why do you need it to be fast?

I tried it and it’s not really noticable. I have more Timeconsumning parts.

Mainly ease of use.