Displaying Plotly Graph Objects (go.Figure) in Dash Bootstrap Components

Hi;

I’m trying to use a Jumbotron container to display multiple graphs, including examples of the machine learning graphs found here: Knn classification in Python

These examples all use the plotly.graph_objects library.

I’ve created the graphs and know they work, but when I try to include them into the dashboard, the output displays empty graphs.

Here is my code for the jumbotron object:

# Jumbotron that will display an empty figure on first launch, on when a user changes a dataset or ml model
classificationModelOutput_jumbotron = dbc.Col(
    html.Div(
        [
            html.H2("Classification Model Output"),
            dcc.Markdown(
                """
                Bellow are 2 different visualizations of how the model understood the dataset. These graphs are called the following:
                1. Contour Map
                2. Decision Boundary
                How did your model do?
                """,
            ),
            html.Hr(className="my-2"),
            dbc.Row(
                [
                    dbc.Col(
                        [
                            scatterPlotModelCard
                        ]
                    ),
                    dbc.Col(
                        [
                            confusionMatrixCard
                        ]
                    )
                ]
            ),
            dbc.Row(
                [
                    dbc.Col(
                        [
                            contourMapCard
                        ]
                    ),
                    dbc.Col(
                        [
                            decisionBoundaryCard
                        ]
                    )
                ]
            ),

        ],
        className = "h-100 p-5 bg-white border rounded-3",
        id = "classificationModelOutput_jumbotron"
    )
)

Here is the code defining the graphs:

# Cards that will display Classification outputs
scatterPlotModelCard = dbc.Card(
    dcc.Graph(id = 'scatterPlot_model', config = config)
)

confusionMatrixCard = dbc.Card(
    dcc.Graph(id = 'confusionMatrix', config = config)
)

contourMapCard = dbc.Card(
    dcc.Graph(id = 'contourMap', config = config)
)

decisionBoundaryCard = dbc.Card(
    dcc.Graph(id = 'decisionBoundary', config = config)
)

And here is the output:

I’m using a similar pattern as above to show other graphs, but these graphs are using the plotly.express library.

Does anyone know if plotly.graph_objects can be used with bootstrap components? Or if there is something special that I need to do in order to use graph objects with dbc?

Thanks!

Hi @iacisme,

Bootstrap components are just “CSS wrappers”, hence they should work fine with both Plotly.go and Plotly.express modules.

May I see your Layout component and the callbacks that triggers your graph content.

Cheers!

HI @iacisme, I think you are mixing up the dash component dcc.Graph() and plotly.graph_objects.

What you are seeing in the image you posted is just some dash components dcc.Graph() without a figure object, i.e dcc.Graph(figure={}).

As for the figure object, you can use plotly.express or plotly.graph_objects. Under the hood, plotly.express uses plotly.graph_objetcs so technically there is no difference.

An example:

import dash
from dash import dcc
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import plotly.express as px

app = dash.Dash(__name__)

app.layout = dbc.Container(
    [
        dbc.Card(
            dcc.Graph(
                id='decisionBoundary_go',
                figure=go.Figure(go.Scatter(x=[1, 2], y=[1, 2]))
            )
        ),
        dbc.Card(
            dcc.Graph(
                id='decisionBoundary_px',
                figure=px.line(x=[1, 2], y=[1, 2])
            )
        )
    ]
)

if __name__ == "__main__":
    app.run_server(debug=True)

You could even use a different graphicing packacke for the figures, such als altair.

Sure, thanks for helping!

My component layout looks like this:

# App layout
app.layout = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(
                    [
                        # Memory locations
                        dataframe_mem,
                        ds_type_mem,
                        modelType_mem,
                        mlModelToBuild_mem,
                        ds_typeOld_mem,
                        mlModelToBuildOld_mem,
                        mlTrackingBuiltModel,

                        # App Components
                        # Top section
                        header1,
                        introText,
                        # What is analytics section
                        analyticsText,
                        # Regression
                        regressionPatternHeader,
                        regressionKinds_card,
                        # Classification
                        classificationPatternHeader,
                        classificationKinds_card,
                        analyticsExplanation_text,
                        
                        # Generate a pattern section
                        generateAPatternBanner,
                        chooseAPatternText,
                        # Dropdown section
                        dropdowns,
                        patternText,
                        # Displays the scatterplot graph and sliders
                        graph_card,
                        # Display jumbotrons
                        empty_jumbotron,
                        regression_jumbotron,
                        classification_jumbotron,
                        
                        # Machine learning section
                        machineLearningText1,
                        machineLearningFamily_card,
                        machineLearningText2,
                        classicalMLFamily_card,
                        machineLearningText3,
                        # Chosing a model
                        buildAModelBanner,
                        buildAModelText,
                        # Show the graph again, in case the user wants to change it
                        confirmGraph_card,
                        buildAModelText1,
                        buildAModelText2,
                        # Chose ml model buttons
                        mlMenuButtons_card,
                        buildAModelText3,
                        # Display the selected model, and dataset for the user to confirm
                        selectedML_model,
                        displayUserChoices_card,
                        
                        # Launch building the model
                        buildAModelText4,
                        emptyModelOutput_jumbotron,
                        #regressionModelOutput_jumbotron,
                        classificationModelOutput_jumbotron
                    ]
                ),
                dbc.Col(companyLogo_card, width = 1),
            ]
        ),
    ],
)

The jumbotron layout in question is called classificationModelOutput. Here is the callback:

""" Callback - Build and display a classification model output

Callback works by building a classificaiton machine learning model when the user clicks
the launch button. The criteria to build an train a classification model are:
1. The modelType == Classification
2. The 'Launch' ML button has be clicked
"""
@app.callback(
    [
        Output("scatterPlot_model", "figure"),
        Output("contourMap", "figure"),
        #Output(component_id = "decisionBoundary", component_property = "figure")
    ],[
        Input(component_id = "launchML_btn", component_property = "n_clicks"),
        Input(component_id = 'memFor_trackingBuiltModel', component_property = 'data')
    ],[
        State(component_id = "memFor_dsType", component_property = "data"),
        State(component_id = "memFor_mlModelToBuild", component_property = "data"),
        State(component_id = "memFor_dataset", component_property = "data"),
        State(component_id = "memFor_modelType", component_property = "data"),      
    ],
    prevent_initial_call = True
)
def launchModel(
    mlButton_click,
    modelBuilt, 
    dsType, 
    selectedModel, 
    dataset, 
    modelType
): 

    # if modelBuilt == False:
    #     print("No Update, modelBuilt = False")
    #     raise PreventUpdate
    
    if ctx.triggered_id == "launchML_btn":
        print("Button Clicked")
        if modelType == "Classification":
            
            # Load the dataset from memory
            df = pd.read_json(StringIO(dataset))

            # Split the dataset into 2 sets
            data, target = splitIntoDataAndTarget(df, modelType)

            # Split the data and target into training and testing sets
            data_train, data_test, target_train, target_test = createATrainTestSplit(data.values, target, modelType)

            # Build a machine learning model
            model = buildAClassificationModel().buildModel(dsType, selectedModel)

            # Train the model
            model.fit(data_train, target_train)

            # Check model performance
            score = model.score(data_test, target_test)

            # Gets the model name for visualizing
            # This method was used to show that you can extract this information directly from your ML model
            if len(model.steps) == 1:
                model_name = model.steps[0][0]
            elif len(model.steps) == 2:
                model_name = model.steps[1][0]

            # Create a scatterplot that will be used in helping visualize the model output.
            # This method was chosen because retrieving a figure stored in memory didn't work
            scatterPlot_figure = scatterPlotGenerator(df, dsType)

            # This section of code generates the contour map figure object and decision boundary that are used
            # to visualize the output of classificaiton models
            # Generate a countour map figure
            contourMap_figure = VisualizeAClassificationModel().generateACountourMap(df, model)

            # Combing the two data sets into one graph
            contourmap = go.Figure(data = scatterPlot_figure.data + contourMap_figure.data)

            # Update general plot layout
            contourmap.update_layout(
                title = f"{model_name[0]} Countour Map. Accuracy score = {score}",
                title_font_size= 20,
                #height = 600,
                #width = 800,
                legend_orientation = 'v',
                paper_bgcolor = 'rgba(0,0,0,0)',
                plot_bgcolor = 'rgba(0,0,0,0)',
                dragmode = 'drawopenpath'
            )
            print(type(scatterPlot_figure))
            print(type())
            return scatterPlot_figure.data, contourmap.data
    else:
        raise PreventUpdate

Note
This code is incomplete. I only have Scatterplot and Countour Map completed, I still have 2 more graphs to go, but this should be enough to test if it works.

Thanks for your help!

Hey @iacisme,

You have to return the figure, right now you are returning just the traces:

return scatterPlot_figure.data, contourmap.data

Try this:

return scatterPlot_figure, contourmap

Hey @AIMPED, thanks for looking into this.

Unfortunately, that didn’t work. I just removed the .data from both figures.

I’m going to look at your example in more detail, try it on my computer, and then see if I can replicate my issue using your code - or maybe use it to trouble shoot what I’m doing.

I’ll get back to you in a bit, I’m going to play around with it now.

Cheers!

–Igor

Hey AIMPED;

I modified your example as follows:

# Dash libraries
from dash import Dash, dcc, html, Input, Output, State, ctx 
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc

# Plotly Graphing Libraries
import plotly.express as px
import plotly.graph_objects as go

# CSS Themes
# refer to: https://dash-bootstrap-components.opensource.faculty.ai/docs/themes/explorer/
theme = dbc.themes.SPACELAB    
                               # BOOTSTRAP,  CERULEAN, COSMO, CYBORG, DARKLY, 
                               # FLATLY, JOURNAL, LITERA, LUMEN, LUX, MATERIA, 
                               # MINTY, MORPH, PULSE, QUARTZ, SANDSTONE, SIMPLEX, 
                               # SKETCHY, SLATE, SOLAR, SPACELAB, SUPERHERO, UNITED, 
                               # VAPOR, YETI, ZEPHY

# Initialize the app object
app = Dash(
    external_stylesheets = [theme],
)

app.layout = dbc.Container(
    [
        dbc.Card(
            dcc.Graph(
                id='decisionBoundary_go'
            )
        ),
        dbc.Card(
            dcc.Graph(
                id='decisionBoundary_px'
            )
        ),
        dbc.Button(
            "Click to draw graphs",
            id = "launch-button",
            className = "me-2",
            n_clicks = 0
        ),
    ]
)

@app.callback(
    [
        Output(component_id = "decisionBoundary_go", component_property = "figure"),
        Output(component_id = "decisionBoundary_px", component_property = "figure")
    ],
    Input(component_id = "launch-button", component_property = "n_clicks"),
    prevent_initial_call = True
)
def clickToDisplayGraphs(launch_btn):

    if ctx.triggered_id is None:
        raise PreventUpdate

    if ctx.triggered_id == "launch-button":
        goFigure = go.Figure(go.Scatter(x=[1, 2], y=[1, 2]))
        pxFigure = px.line(x=[1, 2], y=[1, 2])

        return goFigure, pxFigure

# Runs the app
if __name__ == "__main__":
    app2.run_server(
        debug = True,
        port = 8054,
        jupyter_mode = "external"
    )

To align it more to how I’m doing things. It works!

I’ll use these examples to figure out what I’m doing wrong, thanks for your help!

Cheers!

–Igor

2 Likes

Hey @iacisme Happy, that I could be of help!

Make sure, you are running the right app, I can’t see an app2 in the code snippet you provided. :upside_down_face: