Plotly Scatter Button to Select Column for Colorbar

I am trying to make a scatterplot where the column of the df for the colorbar is selected via a button/dropdown.

In pseudocode, what I am trying to do based on this:

px.scatter(df, x='A', y='B')
button1 = dict(method = "restyle",
                        args = ['color', df['Y']]
                        label = "Y")
button1 = dict(method = "restyle",
                        args = ['color', df['Z']]
                        label = "Z")
fig.update_layout(updatemenus=[dict(buttons=[button1, button2])])

However, the dropdown shows the options but is not actually adjusting the colorbar. Any tips?

I have also tried the suggestion in here by just changing “y” to “color” in the button args but it does not change the colorbar when I do this.

Thank you for the help.

Hi @friendlypanda ,

I have been trying this.
restyle method is used to modify data attributes like colorscales, but I am affraid it cannot be use to modify color arguments.

The best approach that I have been using is update method.
You can refer to this link below.

Or if you want practical solution this code below maye can help you a bit.

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

df = px.data.iris()

fig = go.Figure()


fig.add_trace(
    go.Scatter(x=list(df.sepal_width),
               y=list(df.sepal_length),
               mode='markers',
               marker=dict(color=list(df.petal_width),colorscale="Blues"),
               name="Color By petal_width",

               ))

fig.add_trace(
    go.Scatter(x=list(df.sepal_width),
               y=list(df.sepal_length),
               mode='markers',
               marker=dict(color=list(df.petal_length),colorscale="Viridis"),
               name="Color By petal_length",
               visible=False,
               ))


button1 = dict(method = "update",
                        args = [{"visible": [True, False]}],
                        label = "petal_width")
button2 = dict(method = "update",
                        args = [{"visible": [False, True]}],
                        label = "petal_length")
fig.update_layout(updatemenus=[dict(buttons=[button1, button2])])

fig.update_traces(marker_size=10)
fig.show()

Hope this help.

Hi,

Thank you for the response. I have tried changing the method to “update”, and unfortunately still cannot get the expected behavior. A mock piece of code is below. The idea is the actual colorbar changes based on which column is selected but in this code, when I change the dropdown, the colorbar does not change.

Hope you are able to help with this.

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

df = pd.DataFrame({'test1': [1, 2, 3, 4], 'test2':[4, 5, 4, 7], 'test3':[7,8,10, 13]})
fig = px.scatter(df,x='test1', y="test2", color='test1', width=600, height=400, hover_data=['test1','test2'])

buttonlist = []
for col in df.columns:
    buttonlist.append(
    dict(
        args=['color', [df[str(col)]]],
        label=str(col),
        method='update'
        )
    )

fig.update_layout(
    title="Test data",
    updatemenus=[
        go.layout.Updatemenu(
            buttons=buttonlist,
            direction="down",
            pad={"r": 5, "t": 5},
            showactive=False,
            x=0.1,
            xanchor="left",
            y=1.1,
            yanchor="top"
        ),
    ],
    autosize=True
)
fig.show()

Hi @friendlypanda ,

Here modification of the code from your previous response.

The idea using method “update” is created 3 traces, and the selected dropdown will show the trace with color based on certain column and hide the other traces.

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

df = pd.DataFrame({'test1': [1, 2, 3, 4], 'test2':[4, 5, 4, 7], 'test3':[7,8,10, 13]})
# fig = px.scatter(df,x='test1', y="test2", color='test1', width=600, height=400, hover_data=['test1','test2'])

## this lines below are to define 3 traces that have different colorscales based on certain column.
fig = go.Figure()
fig.add_trace(
    go.Scatter(x=list(df.test1),
               y=list(df.test2),
               mode='markers',
               marker=dict(color=list(df.test1)),
               name="test1",

               ))

fig.add_trace(
    go.Scatter(x=list(df.test1),
               y=list(df.test2),
               mode='markers',
               marker=dict(color=list(df.test2)),
               name="test2",
               visible=False,
               ))

fig.add_trace(
    go.Scatter(x=list(df.test1),
               y=list(df.test2),
               mode='markers',
               marker=dict(color=list(df.test3)),
               name="test3",
               visible=False,
               ))

buttonlist = []
## This commented lines below are your previous code
# for col in df.columns:
#     buttonlist.append(
#     dict(
#         args=['color', [df[str(col)]]],
#         label=str(col),
#         method='update'
#         )
#     )
## This lines are modified version using method update
for idx,col in enumerate(list(df.columns)):
    buttonlist.append(
    dict(
        # the idea is to show the trace of selected dropdown and hide the other traces.
        args=[{"visible": [idx==0, idx==1, idx==2]}],
        label=str(col),
        method='update'
        )
    )

fig.update_layout(
    title="Test data",
    updatemenus=[
        go.layout.Updatemenu(
            buttons=buttonlist,
            direction="down",
            pad={"r": 5, "t": 5},
            showactive=False,
            x=0.1,
            xanchor="left",
            y=1.1,
            yanchor="top"
        ),
    ],
    autosize=True
)
fig.show()

Hope this help.

Ah - this does indeed work. However it uses Plotly not Plotly express which does raise a view more questions:

Is there a way to do the “hover_data” parameter of plotly express with several columns - i.e. before it would have been hover_data=['test1, ‘test2’, ‘test3’].

Also, is there a way to do the category_oirders parameter - in the real data we do not have numerical categories so setting the order s very important.

Lastly - is there a way to show and then title the colorbar based on which trace is visible?

Thank you very much for the help.

Hi @friendlypanda ,

To do “hover_data” using plotly graph objects you can use customdata and hovertemplate.

Yes you can set category order on plotly graph object.
for example you want to sort the x axis categories

fig.update_xaxes(categoryorder='category ascending')

Link below will be give you complete example to handle category order.

To show colorbar title you can add on marker attribute on every traces.

fig.add_trace(
    go.Scatter(x=list(df.test1),
               y=list(df.test2),
               mode='markers',
               marker=dict(color=list(df.test1),
                                 colorbar=dict(title="Colorbar"),), # add colorbar title
               name="test1",

               ))

Hope this help.

Hi,

This was very helpful. One issue I am still running into is that the actual data is not numerical so when I do marker=dict(color=list(df.text1)), I get the below error.

With Plotly express, when I pass in a column of strings, each gets assigned a color and the colorbar is more like a legend. Is this possible with graph_objects?

           turquoise, violet, wheat, white, whitesmoke,
            yellow, yellowgreen
      - A number that will be interpreted as a color
        according to scatter.marker.colorscale
      - A list or array of any of the above

You can transform your categorical series that indicates color as list of number by using map function.

# convert list of text to list of number
 list(map(lambda x : df.text1.unique().tolist().index(x),df.text1.tolist()))

if you put into marker attribute

marker=dict(color=list(map(lambda x : df.text1.unique().tolist().index(x),df.text1.tolist()))),

Hi,

Thanks for the response.

This works to do the coloring but from this point I am not sure how to do the legend. My desired behavior is identical to the plotly express behavior - it’ll have the categories and their corresponding color.

Let me know if this makes sense.

For example, the Plotly Express version is:

df = pd.DataFrame({'test1': [1, 2, 3, 4], 'test2':[10, 8, 1, 10],'test5':['a', 'b','c', 'd']})
fig2 = px.scatter(df, x='test1', y='test2', color='test5', width=600, height=400)
fig2.show()

And this is the result with plotly express:

Hi @friendlypanda,

I see,
This lines below maybe a hack of modified code, but I hope this is help you solve your issues.

I try to create traces using Plotly express, I add 2 additional columns of dataframe, so the df have 5 columns ['test1','test2','test3','test4','test5'].

df = pd.DataFrame({'test1': [1, 2, 3, 4], 'test2':[4, 5, 4, 7], 'test3':['a','b','c', 'd'],'test4':['E','F','G', 'H'],'test5':['W','X','Y', 'Z']})

The dropdown menu options will be [‘test3’,‘test4’,‘test5’], so the color will be using this 3 columns.

fig1 = px.scatter(df,x='test1', y="test2", color='test3', width=600, height=400, hover_data=['test1','test2'])
fig2 = px.scatter(df,x='test1', y="test2", color='test4', width=600, height=400, hover_data=['test1','test2'])
fig3 = px.scatter(df,x='test1', y="test2", color='test5', width=600, height=400, hover_data=['test1','test2'])

Creating traces by using Plotly Express will keep the discreate color feature on the traces, so the it will show the categorical color instead continuous colorbar. ( And actually you can use Plotly graph objects as well but you need create all traces manually)

Get all traces from all plotly express figures by using fig1.data, fig2.data,fig3.data into new Figure objects fig.

# Get the Sactter traces and add into new Figure object `fig` 
fig = go.Figure()
fig.add_traces(fig1.data) # fig1.data is tuple of traces
fig.add_traces(fig2.data)
fig.add_traces(fig3.data)

For initial view set the visible traces just for test3

fig.for_each_trace(
    lambda trace: trace.update(visible=False) if trace.name not in df.test3.unique().tolist() else ()
)

To update legend by selected dropdown options, you need to add this line to args attribute.

{"legend": {"title": {"text": col}}}  # Update the legend of discrete color

Finally set test3 as initial legend title

fig.update_layout(
    title="Test data",
    legend_title_text="text3", # initial color legend
    ..

And here is the complete code.

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

df = pd.DataFrame({'test1': [1, 2, 3, 4], 'test2':[4, 5, 4, 7], 'test3':['a','b','c', 'd'],'test4':['E','F','G', 'H'],'test5':['W','X','Y', 'Z']})

## Create 3 figures with different color based on 3 column.
fig1 = px.scatter(df,x='test1', y="test2", color='test3', width=600, height=400, hover_data=['test1','test2'])
fig2 = px.scatter(df,x='test1', y="test2", color='test4', width=600, height=400, hover_data=['test1','test2'])
fig3 = px.scatter(df,x='test1', y="test2", color='test5', width=600, height=400, hover_data=['test1','test2'])

# Get the Sactter traces and add into new Figure object `fig` 
print(fig1.data)
fig = go.Figure()
fig.add_traces(fig1.data) # fig1.data is tuple of traces
fig.add_traces(fig2.data)
fig.add_traces(fig3.data)

# For initial view set the visible traces just for `test3` 
fig.for_each_trace(
    lambda trace: trace.update(visible=False) if trace.name not in df.test3.unique().tolist() else ()
)

buttonlist = []

## This lines are modified version using method update
for col in ["test3","test4","test5"]:
    buttonlist.append(
    dict(
        # the idea is to show the trace of selected dropdown and hide the other traces.
        args=[{"visible": [col=='test3']*len(df.test3.unique()) \
                          + [col=='test4']*len(df.test4.unique()) \
                          + [col=='test5']*len(df.test5.unique())
                },
                {"legend": {"title": {"text": col}}}  # Update the legend of discrete color
              ],
        label=str(col),
        method='update'
        )
    )

fig.update_layout(
    title="Test data",
    legend_title_text="text3", # initial color legend
    updatemenus=[
        go.layout.Updatemenu(
            buttons=buttonlist, 
            direction="down",
            pad={"r": 5, "t": 5},
            showactive=False,
            x=0.1,
            xanchor="left",
            y=1.1,
            yanchor="top"
        ),
    ],
    autosize=True
)
fig.show()

Thank you very much! This will make the plots I am making much nicer, so I really appreciate the help.

1 Like