I've got two separate toggle's that initiate color and size on a plotly scatter graph. These work fine independently but I'm hoping to combine them so both color and size are updated when both toggles are on.
I am unable to call a single scatter and then use update_trace
because I need to assign discrete values to the species
column.
I'm unaware if color and size can be updated to an existing figure but am content to change.
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
import plotly.graph_objs as go
df = px.data.iris()
color_dict = {'versicolor': 'brown', 'setosa': 'orange', 'virginica': 'pink'}
size_dict = {'versicolor': 4, 'setosa': 6, 'virginica': 10}
sizemap = df["species"].map(size_dict)
df = df.sort_values(by = 'species', ascending= False).reset_index(drop = True)
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.P("Toggle for the Color"),
dcc.Checklist(
id="color_toggle",
options=[{"label": "", "value": True}],
value=[],
inline=True
),
html.P("Toggle for the Size"),
dcc.Checklist(
id="size_toggle",
options=[{"label": "", "value": True}],
value=[],
inline=True
),
html.Div(
dcc.Graph(id="chart"),
),
]
)
@app.callback(
Output("chart", "figure"),
[Input("color_toggle", "value"),
Input("size_toggle", "value")]
)
def update_output(color_on, size_on):
fig = px.scatter(df, x="sepal_width", y="sepal_length", hover_data=["species"])
if color_on:
fig = px.scatter(df, x="sepal_width", y="sepal_length", color = "species", color_discrete_map = color_dict)
if size_on:
fig = go.Figure(data=go.Scatter(
x=df["sepal_width"],
y=df["sepal_length"],
mode = 'markers',
marker=dict(size=sizemap)
))
# return both color and szie
#if color_on & size_on:
return fig
if __name__ == "__main__":
app.run_server(debug=True)
Note you can also set the marker size
directly using px.scatter()
, referring to a specific column as for color
. First :
# NB. `sizemap` would be invalid because you are sorting the df *after* this line
#sizemap = df["species"].map(size_dict)
# Create a column instead
df["size"] = df["species"].map(size_dict)
And the callback :
@app.callback(
Output("chart", "figure"),
[Input("color_toggle", "value"),
Input("size_toggle", "value")]
)
def update_output(color_on, size_on):
fig = px.scatter(
df,
x="sepal_width",
y="sepal_length",
color="species" if color_on else None,
size="size" if size_on else None,
color_discrete_map=color_dict,
hover_data={"species": True, "size": False}
)
fig.update_traces(marker_sizemode='diameter', marker_sizeref=1)
return fig
NB. By default assigning a numerical array to the marker size turns the scatter into a bubble chart (markers are way bigger for the same given size). Setting marker sizemode
and sizeref
ensures we always get consistent marker size.