Search code examples
pythonplotlyplotly-dash

Dual plotly toggles on single graph - python


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)

Solution

  • 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.