0

this is more likely the first time to use Dash board together with plotly and dont have clear understanding how the code works and where the issues come from. It will be appreciated if you give some suggestion. I am trying to import two csv files by using uploading buttons and dropdownbar should show all variable name and once i select multiple variable names from dopdown bar then they should be plotted in the x-y plot.

The code works to import two different csv files and showing variables in dropdownbar, but plotting doesnt work. I think the code is not able to transfer value correct to plotly functions. Can you review the code and where the source of problems are ? Thanks.

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State
import base64
import io
import plotly.express as px
import dash_table

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
# Initialize the app
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# Define the layout
app.layout = html.Div([
    html.H1('CSV File Comparison Dashboard'),
    html.Div([
        html.H2('File A'),
        dcc.Upload(
            id='upload-data-A',
            children=html.Div([
                'Drag and Drop or ',
                html.A('Select Files')
            ]),
            style={
                'width': '50%',
                'height': '60px',
                'lineHeight': '60px',
                'borderWidth': '1px',
                'borderStyle': 'dashed',
                'borderRadius': '5px',
                'textAlign': 'center',
                'margin': '10px'
            },
            multiple=False
        ),
        html.Div(id='file-name_A'),
        dcc.Dropdown(
            id='variable-dropdown-A',
            options=[],
            value=[],
            multi=True
        ),
        dcc.Graph(
            id='graph-A'
        )
    ], style={'width': '49%', 'display': 'inline-block', 'vertical-align': 'top'}),
    html.Div([
        html.H2('File B'),
        dcc.Upload(
            id='upload-data-B',
            children=html.Div([
                'Drag and Drop or ',
                html.A('Select Files')
            ]),
            style={
                'width': '50%',
                'height': '60px',
                'lineHeight': '60px',
                'borderWidth': '1px',
                'borderStyle': 'dashed',
                'borderRadius': '5px',
                'textAlign': 'center',
                'margin': '10px'
            },
            multiple=False
        ),
        html.Div(id='file-name_B'),
        dcc.Dropdown(
            id='variable-dropdown-B',
            options=[],
            value=[],
            multi=True
        ),
        dcc.Graph(
            id='graph-B'
        )
    ], style={'width': '49%', 'display': 'inline-block', 'vertical-align': 'top'})
])

# Define the callback for updating the variable dropdown menus
@app.callback([Output('variable-dropdown-A', 'options'), Output('variable-dropdown-A', 'value')],
              [Input('upload-data-A', 'contents')],
              prevent_initial_call=True)

def update_dropdowns_A(list_of_contents_A):
    # Initialize empty options for each dropdown
    options_A = []

    # Check if a csv file has been uploaded for file A
    if list_of_contents_A:
        # Read the csv file
        content_type, content_string = list_of_contents_A.split(',')
        decoded = base64.b64decode(content_string)
        df_A = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
        

        # Add the column names to the options for file A
        options_A = [{"label": col, "value": col} for col in df_A.columns]
        return options_A, df_A.columns[0]
    else:
        # Return the options for both dropdowns
        return [], None

@app.callback([Output('variable-dropdown-B', 'options'), Output('variable-dropdown-B', 'value')],
              [Input('upload-data-B', 'contents')],
              prevent_initial_call=True)

def update_dropdowns_B(list_of_contents_B):
    # Initialize empty options for each dropdown

    options_B = []

    # Check if a csv file has been uploaded for file B
    if list_of_contents_B:
        # Read the csv file
        content_type, content_string = list_of_contents_B.split(',')
        decoded = base64.b64decode(content_string)
        df_B = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
        

        # Add the column names to the options for file A
        options_B = [{"label": col, "value": col} for col in df_B.columns]
        return options_B, df_B.columns[0]
    else:
        # Return the options for both dropdowns
        return [], None




# Define the callback for updating the graphs
@app.callback([Output('graph-A', 'figure'), Output('graph-B', 'figure')],
              [Input('variable-dropdown-A', 'value'), Input('variable-dropdown-B', 'value')],
              [State('upload-data-A', 'contents'), State('upload-data-B', 'contents')],
              prevent_initial_call=True)
def update_graphs(variables_A, variables_B, contents_A, contents_B):
    
    if not variables_A or not variables_B:
        return {}, {}
    # # Initialize empty dataframes for each file
    df_A = pd.DataFrame()
    df_B = pd.DataFrame()

    # # Check if a csv file has been uploaded for file A
    # if contents_A is not None:
    #     # Read the csv file
    #     content_type, content_string = contents_A.split(',')
    #     decoded = base64.b64decode(content_string)
    #     df_A = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
    
    # # Check if a csv file has been uploaded for file B
    # if contents_B is not None:
    #     # Read the csv file
    #     content_type, content_string = contents_B.split(',')
    #     decoded = base64.b64decode(content_string)
    #     df_B = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
    
    # Initialize empty figures for each graph
    fig_A = go.Figure()
    fig_B = go.Figure()

    # Check if any variables have been selected for file A

    for var in variables_A:
        fig_A.add_trace(go.Scatter(
            x=df_A['time'],
            y=df_A[var],
            mode='lines',
            name=var
        ))

    fig_A.update_layout(title='Time Series Plot',
                      xaxis_title='Time',
                      yaxis_title='Value')

    # Check if any variables have been selected for file B

    for var in variables_B:
        fig_B.add_trace(go.Scatter(
            x=df_B['time'],
            y=df_B[var],
            mode='lines',
            name=var
        ))

    fig_B.update_layout(title='Time Series Plot',
                      xaxis_title='Time',
                      yaxis_title='Value')

    # Return the figures for both graphs
    return fig_A, fig_B

# Update file name
@app.callback(Output('file-name_A', 'children'),
              [Input('upload-data-A', 'filename')])
def update_file_name(filename):
    if filename is not None:
        return html.P(f'File loaded: {filename}')
    else:
        return html.P('No file loaded')
    
@app.callback(Output('file-name_B', 'children'),
              [Input('upload-data-B', 'filename')])
def update_file_name(filename):
    if filename is not None:
        return html.P(f'File loaded: {filename}')
    else:
        return html.P('No file loaded')


# Run the app
if __name__ == '__main__':
    app.run_server(debug=True, port = 8000)

Explanation on the issue and furtuer comments on what to do in case if i need to extend the number of plots using subplot functions.

James
  • 5
  • 2

1 Answers1

0

There are a lot of things that we could discuss about your code, but just focusing on your issue I have made these modifications.

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State
import base64
import io
import plotly.express as px
import dash_table

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
# Initialize the app
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# Define the layout
app.layout = html.Div(
    [
        dcc.Store(id="a"),
        dcc.Store(id="b"),
        html.H1("CSV File Comparison Dashboard"),
        html.Div(
            [
                html.H2("File A"),
                dcc.Upload(
                    id="upload-data-A",
                    children=html.Div(["Drag and Drop or ", html.A("Select Files")]),
                    style={
                        "width": "50%",
                        "height": "60px",
                        "lineHeight": "60px",
                        "borderWidth": "1px",
                        "borderStyle": "dashed",
                        "borderRadius": "5px",
                        "textAlign": "center",
                        "margin": "10px",
                    },
                    multiple=False,
                ),
                html.Div(id="file-name_A"),
                dcc.Dropdown(
                    id="variable-dropdown-A", options=[], value=[], multi=True
                ),
                dcc.Graph(id="graph-A"),
            ],
            style={"width": "49%", "display": "inline-block", "vertical-align": "top"},
        ),
        html.Div(
            [
                html.H2("File B"),
                dcc.Upload(
                    id="upload-data-B",
                    children=html.Div(["Drag and Drop or ", html.A("Select Files")]),
                    style={
                        "width": "50%",
                        "height": "60px",
                        "lineHeight": "60px",
                        "borderWidth": "1px",
                        "borderStyle": "dashed",
                        "borderRadius": "5px",
                        "textAlign": "center",
                        "margin": "10px",
                    },
                    multiple=False,
                ),
                html.Div(id="file-name_B"),
                dcc.Dropdown(
                    id="variable-dropdown-B", options=[], value=[], multi=True
                ),
                dcc.Graph(id="graph-B"),
            ],
            style={"width": "49%", "display": "inline-block", "vertical-align": "top"},
        ),
    ]
)


# Define the callback for updating the variable dropdown menus
@app.callback(
    [
        Output("variable-dropdown-A", "options"),
        Output("variable-dropdown-A", "value"),
        Output("a", "data"),
    ],
    [Input("upload-data-A", "contents")],
    prevent_initial_call=True,
)
def update_dropdowns_A(list_of_contents_A):
    # Check if a csv file has been uploaded for file A
    if list_of_contents_A:
        # Read the csv file
        content_type, content_string = list_of_contents_A.split(",")
        decoded = base64.b64decode(content_string)
        df_A = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
        print(df_A.head())

        # Add the column names to the options for file A
        options_A = [{"label": col, "value": col} for col in df_A.columns]
        return options_A, df_A.columns, df_A.to_json(orient="records")
    else:
        # Return the options for both dropdowns
        return [], None


@app.callback(
    [
        Output("variable-dropdown-B", "options"),
        Output("variable-dropdown-B", "value"),
        Output("b", "data"),
    ],
    [Input("upload-data-B", "contents")],
    prevent_initial_call=True,
)
def update_dropdowns_B(list_of_contents_B):
    # Check if a csv file has been uploaded for file B
    if list_of_contents_B:
        # Read the csv file
        content_type, content_string = list_of_contents_B.split(",")
        decoded = base64.b64decode(content_string)
        df_B = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
        print(df_B.head())

        # Add the column names to the options for file A
        options_B = [{"label": col, "value": col} for col in df_B.columns]
        return options_B, df_B.columns, df_B.to_json(orient="records")
    else:
        # Return the options for both dropdowns
        return [], None


# Define the callback for updating the graphs
@app.callback(
    [Output("graph-A", "figure"), Output("graph-B", "figure")],
    [Input("variable-dropdown-A", "value"), Input("variable-dropdown-B", "value")],
    [State("a", "data"), State("b", "data")],
    prevent_initial_call=True,
)
def update_graphs(variables_A, variables_B, contents_A, contents_B):
    if not variables_A or not variables_B:
        return {}, {}
    df_A = pd.read_json(contents_A, orient="records")
    df_B = pd.read_json(contents_B, orient="records")
    # Initialize empty figures for each graph
    fig_A = go.Figure()
    fig_B = go.Figure()
    # Check if any variables have been selected for file A
    for var in variables_A:
        fig_A.add_trace(go.Scatter(x=df_A["time"], y=df_A[var], mode="lines", name=var))
    fig_A.update_layout(
        title="Time Series Plot", xaxis_title="Time", yaxis_title="Value"
    )
    # Check if any variables have been selected for file B
    for var in variables_B:
        fig_B.add_trace(go.Scatter(x=df_B["time"], y=df_B[var], mode="lines", name=var))
    fig_B.update_layout(
        title="Time Series Plot", xaxis_title="Time", yaxis_title="Value"
    )

    # Return the figures for both graphs
    return fig_A, fig_B


# Update file name
@app.callback(Output("file-name_A", "children"), [Input("upload-data-A", "filename")])
def update_file_name(filename):
    if filename is not None:
        return html.P(f"File loaded: {filename}")
    else:
        return html.P("No file loaded")


@app.callback(Output("file-name_B", "children"), [Input("upload-data-B", "filename")])
def update_file_name(filename):
    if filename is not None:
        return html.P(f"File loaded: {filename}")
    else:
        return html.P("No file loaded")


# Run the app
if __name__ == "__main__":
    app.run_server(debug=True, port=8000)

The main issue that you have is that Dash does not store data to local memory when you process the csv files. That means that when your update_dropdowns_x completes the respective df_A and df_B are deleted. When your update_graphs is triggered there is no data for it to work with. I have added two dcc.Store objects to persist the data from the csv between callbacks. You will notice that I did not pass the dataframe because html requires the data to be in text/json. That is why the df_A.to_json(orient="records") was used as the output. The code was then processed with black to correct minor formatting errors. I also set the dropdowns to automatically include all channels, but you can add the [0] back to the return to have the state that your original app was at. Here is a screenshot. since you didn't post an example of the csv files I just made something up.

enter image description here

RK Replogle
  • 131
  • 1
  • 6
  • Thanks RK for your kind comments. I thought "state" works as "dcc.Store" to persiste the data from the imported csv files, but there are difference i can see. Just follow up questions are then Can i regard this dcc.Store is needed per csv files to be imported ? or would there also be some technical detials to be considered if i want to call persisted data in dcc.store to multiple plots by selecting the number of subplots from the same dropdown menu ? Thanks. – James May 01 '23 at 21:35
  • I'm not sure that I follow your question exactly, but the dcc.Store can persist any data that can be stored as a json string. You could have nested dictionaries with numerous fig objects or subplots. For your example I kept the data separate, but you could easily combine them into a single Store. – RK Replogle May 02 '23 at 19:55
  • Thanks, your comments follow my questions. by the way, when i run the code you modified. I can see different type of error "Callback error updating variable-dropdown-A.options, variable-dropdown-A.value, a.data" if i just run the code, meaning that i didnt load csv files yet. Are there something i need to be aware of ? – James May 03 '23 at 20:05
  • I get a lot of errors in the terminal but they are for deprecated import statements that were in your original code. This makes me assume that you are using an old version of Dash which might be causing your error on initialization. I don't think that you specified your OS or version numbers but I've checked the modified code on Windows 10, OSX (don't have that machine nearby), and Dash 2.9.3 and don't get any errors before loading files. I can't put a screen shot in the comments, but you can see from the screen shot in the answer that the debugging ball does not indicate any errors. – RK Replogle May 04 '23 at 21:34
  • Ok thanks, it might be the reason then,,, due to some IT security issues, we have limited version of python packages and i can clearly see that our Dash is version 1.x..... I will try to install the latest version somehow to see if there are still some issues. thanks. – James May 08 '23 at 13:11
  • Have you tried something like this? pip3.5 install dash --trusted-host pypi.python.org An idea from https://stackoverflow.com/questions/21468550/pip-not-working-behind-firewall – RK Replogle May 09 '23 at 14:38