Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Dangerous link detected Error in Dash Debug Window after upgrading from 2.14.2 to 2.15.0 #2743

Closed
ghaarsma opened this issue Feb 2, 2024 · 17 comments · Fixed by #2756
Closed

Comments

@ghaarsma
Copy link

ghaarsma commented Feb 2, 2024

With Dash 2.14.2 all is good. After upgrade to 2.15.0, we receive a Dangerous link detected:: in the Dash Debug Window.

  • Dash versions
dash                        2.15.0
dash-bootstrap-components   1.5.0
dash-core-components        2.0.0
dash-html-components        2.0.0
dash-table                  5.0.0
  • Could be front end related, but should be on latest version of Chrome
    • Windows 10
    • Chrome
    • Version 121.0.6167.140 (Official Build) (64-bit)

It looks like the actual website is running OK and functional and it is possible not affected by the reported JavaScript error.
Full Err Msg:

(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser's console.)
Error: Dangerous link detected:: 

    at http://localhost:5000/_dash-component-suites/dash/dcc/dash_core_components.v2_13_0m1706882908.js:2:52506

    at commitHookEffectListMount (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:19866:28)

    at commitPassiveHookEffects (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:19904:13)

    at HTMLUnknownElement.callCallback (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:182:16)

    at Object.invokeGuardedCallbackDev (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:231:18)

    at invokeGuardedCallback (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:286:33)

    at flushPassiveEffectsImpl (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:22988:11)

    at unstable_runWithPriority (http://localhost:5000/_dash-component-suites/dash/deps/react@16.v2_15_0m1706882909.14.0.js:2685:14)

    at runWithPriority$1 (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:11174:12)

    at flushPassiveEffects (http://localhost:5000/_dash-component-suites/dash/deps/react-dom@16.v2_15_0m1706882909.14.0.js:22955:14)

image

@alexcjohnson
Copy link
Collaborator

Thanks @ghaarsma - we closed an XSS vulnerability in v2.15 -> #2732
It looks like perhaps you have a blank href in a dcc.Link component, is that right? @T4rk1n we can presumably let blanks pass through the validation?

@ghaarsma
Copy link
Author

ghaarsma commented Feb 2, 2024

I do have an html.A where we set href to "", also several dcc.Link where we set href to "/". We also have locations where we set href within a callback dynamically.

I did do a quick try to give them a temporary value. This did not make the problem go away. My testing was not comprehensive or I might not understand the problem al together.

@T4rk1n
Copy link
Contributor

T4rk1n commented Feb 5, 2024

dcc.Link.href is marked as required so I thought it was going to be always there and didn't put a check on that like the other ones in html.

@pat1510
Copy link

pat1510 commented Feb 9, 2024

@ann-marie-ward could you please explain me the detail steps to avoid this 'Dangerous link error' ? Please see that I am new to software development , hence please do not presume about my software development knowledge ? I am getting this error when I try to save data table on web app to the csv. The error looks similar to what @ghaarsma posted in his comment.

@AnnMarieW
Copy link
Collaborator

Hi @pat1510
Look for a component that looks like:

dcc.Link("link name", href="")

and change it to:

dcc.Link("link name", href="/")

If that doesn't work, could you please make a minimal example so I could run it, and see the error?

@ghaarsma
Copy link
Author

ghaarsma commented Feb 9, 2024

I figured out where the trouble spot was (dynamic building of dcc.Links), after changing href="" to href="/", we had no more issues in the Dash Debug Window.

Thank you to everyone who responded and helped out solving the issue.

@pat1510
Copy link

pat1510 commented Feb 9, 2024 via email

@AnnMarieW
Copy link
Collaborator

@pat1510
The target='_blank' will open a blank browser tab
I don't get an error when I run your example. Can you make a complete example that shows the error?

@pat1510
Copy link

pat1510 commented Feb 9, 2024 via email

@AnnMarieW
Copy link
Collaborator

AnnMarieW commented Feb 9, 2024

Hi @pat1510 I can't see your attachment. Can you please copy the code directly as a comment here? You can enclose the code with three back ticks so it's formatted correctly.

You can also upload files by clicking here:
image

@pat1510
Copy link

pat1510 commented Feb 10, 2024

import dash
import numpy as np
from dash import dcc, html
from dash.dash_table import DataTable
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
from dash.dependencies import Input, Output
from base64 import b64encode

# Demo DataFrame
data = {
    'Plastic': ['A', 'A', 'B', 'B', 'A', 'B','A', 'A', 'B', 'B', 'A', 'B'],
    'Make': [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],
    'Block': [10, 20, 30, 10, 20, 30, 10, 20, 30, 10, 20, 30],
    'Measurement': [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],
    'Actual_Yield': [85, 90, 88, 92, 87, 91, 85, 90, 88, 92, 87, 91],
    'Min': [80, 85, 85, 88, 82, 90, 80, 85, 85, 88, 82, 90],
    'Max': [88, 92, 90, 94, 89, 94, 88, 92, 90, 94, 89, 94]
}

df = pd.DataFrame(data)

# Calculate average yield for each grade and additional filters
average_yield_per_grade = df.groupby(['Plastic', 'Make', 'Block', 'Measurement'])['Actual_Yield'].mean().reset_index()

# Calculate overall average yield
overall_avg_yield = df['Actual_Yield'].mean()

app = dash.Dash(__name__)

app.layout = html.Div(className='row', children=[
    # Left column for Production Line Chart
    html.Div(className='six columns', children=[
        html.H1(children='KPI Dashboard', style={'textAlign': 'center'}),

        # Dropdown for selecting plastic grades
        dcc.Dropdown(
            id='plastic-dropdown',
            options=[
                {'label': plastic, 'value': plastic} for plastic in df['Plastic'].unique()
            ],
            value=df['Plastic'].iloc[0],  # Set default value to the first grade in the dataframe
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Plastic Grade'
        ),

        # Dropdown for selecting Make
        dcc.Dropdown(
            id='make-dropdown',
            options=[],  # Will be populated dynamically
            value='',  # Set default value to empty
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Make'
        ),

        # Dropdown for selecting Block
        dcc.Dropdown(
            id='block-dropdown',
            options=[],  # Will be populated dynamically
            value='',  # Set default value to empty
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Block'
        ),

        # Dropdown for selecting Measurement
        dcc.Dropdown(
            id='measurement-dropdown',
            options=[],  # Will be populated dynamically
            value='',  # Set default value to empty
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Measurement'
        ),

        # Line Chart
        dcc.Graph(
            id='production-line-chart',
            figure=px.line(title='Default Actual_Yield Trend')  # Set default figure
        ),
        # Download Button
        html.A(html.Button('Download Data as Excel'), id='download-link', href='/', download='your_file_name.xlsx',
               target='_blank')
    ]),

    # Center column for DataTable
    html.Div(className='four columns', children=[
        html.H3(children='Selected Combinations Data Table', style={'textAlign': 'center'}),

        DataTable(
            id='selected-data-table',
            columns=[
                {'name': 'Plastic', 'id': 'Plastic'},
                {'name': 'Make', 'id': 'Make'},
                {'name': 'Block', 'id': 'Block'},
                {'name': 'Measurement', 'id': 'Measurement'},
                {'name': 'Actual_Yield', 'id': 'Actual_Yield'},
                {'name': 'Min', 'id': 'Min'},
                {'name': 'Max', 'id': 'Max'},
                # Add more columns as needed
            ],
            style_table={'height': '300px', 'overflowY': 'auto'},
        ),
    ]),

    # Right column for Yield Gauge Charts
    html.Div(className='six columns', children=[
        # Yield Gauge Chart for Selected Grade
        html.Div(className='graph-container', children=[
            dcc.Graph(id='yield-gauge-chart-selected-grade',
                      style={'width': '700px', 'height': '500px', 'marginLeft': '280px'}
                      ),
        ]),

        # Yield Gauge Chart for Overall Average
        html.Div(className='graph-container', children=[
            dcc.Graph(id='yield-gauge-chart-overall-average',
                      style={'width': '700px', 'height': '500px', 'marginLeft': '-10px'}
                      ),
        ]),
    ], style={'display': 'flex'}),

    # Interval component for updating gauge value
    dcc.Interval(
        id='interval-component',
        interval=1 * 1000,  # in milliseconds (1 second)
        n_intervals=0
    ),
])


# Callback to populate Make dropdown based on selected plastic grade
@app.callback(
    Output('make-dropdown', 'options'),
    [Input('plastic-dropdown', 'value')]
)
def update_make_dropdown(selected_plastic):
    make_options = [{'label': str(make), 'value': make} for make in
                    df[df['Plastic'] == selected_plastic]['Make'].unique()]
    return make_options


# Callback to populate Block dropdown based on selected plastic grade and Make
@app.callback(
    Output('block-dropdown', 'options'),
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value')]
)
def update_block_dropdown(selected_plastic, selected_make):
    # Check if selected_make is not empty
    if not selected_make:
        return []

    block_options = [{'label': str(block), 'value': block} for block in
                     df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make))]['Block'].unique()]
    return block_options


# Callback to populate Measurement dropdown based on selected plastic grade, Make, and Block
@app.callback(
    Output('measurement-dropdown', 'options'),
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value')]
)
def update_measurement_dropdown(selected_plastic, selected_make, selected_block):
    # Check if selected_block is not empty
    if not selected_block:
        return []

    measurement_options = [{'label': str(measurement), 'value': measurement} for measurement in
                           df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                              (df['Block'] == float(selected_block))]['Measurement'].unique()]
    return measurement_options


# Callback to update line chart based on selected plastic grade and filters
@app.callback(
    Output('production-line-chart', 'figure'),
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value'),
     Input('measurement-dropdown', 'value'),
     Input('interval-component', 'n_intervals')]
)
def update_line_chart(selected_plastic, selected_make, selected_block, selected_measurement, n_intervals):
    # Check if any selected value is empty
    if any(value is None or value == '' for value in
           [selected_plastic, selected_make, selected_block, selected_measurement]):
        return px.line(title='No data available')  # Return a default line chart with a title

    selected_df = df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                     (df['Block'] == float(selected_block)) & (df['Measurement'] == float(selected_measurement))].copy()

    if selected_df.empty:
        return px.line(title='No data available')  # Return a default line chart with a title

    selected_df['Count'] = range(1, len(selected_df) + 1)

    # Check if 'Min' and 'Max' columns are present in the DataFrame
    if 'Min' not in selected_df.columns or 'Max' not in selected_df.columns:
        return px.line(title='Missing data columns')  # Return a default line chart with a title

    line_chart = px.line(selected_df, x='Count', y=['Min', 'Max'],
                         title=f'Plastic-{selected_plastic}, Make-{selected_make}, Block-{selected_block}, Measurement-{selected_measurement} Min'
                               f' and Max Trend')
    # Update marker properties for Min and Max
    line_chart.update_traces(
        line=dict(width=2),  # Adjust the width of the line
        mode='markers+lines',  # Show markers and lines
        marker=dict(size=10)  # Adjust the size of the marker
    )
    line_chart.update_layout(xaxis_title='Count')

    return line_chart


@app.callback(
    [Output('yield-gauge-chart-selected-grade', 'figure'),
     Output('yield-gauge-chart-overall-average', 'figure')],
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value'),
     Input('measurement-dropdown', 'value'),
     Input('interval-component', 'n_intervals')]
)
def update_gauge_charts(selected_plastic, selected_make, selected_block, selected_measurement, n_intervals):
    # Check if any selected value is None or an empty string
    if any(value is None or value == '' for value in
           [selected_plastic, selected_make, selected_block, selected_measurement]):
        return go.Figure(), go.Figure()  # Return default figures

    # Check if selected_block and selected_measurement are not None or empty string before converting to float
    selected_block_float = float(selected_block) if selected_block and selected_block != '' else None
    selected_measurement_float = float(
        selected_measurement) if selected_measurement and selected_measurement != '' else None

    selected_df = df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                     (df['Block'] == selected_block_float) & (df['Measurement'] == selected_measurement_float)].copy()

    if selected_df.empty:
        return go.Figure(), go.Figure()

    selected_df['Count'] = range(1, len(selected_df) + 1)

    current_yield = average_yield_per_grade.loc[
        (average_yield_per_grade['Plastic'] == selected_plastic) &
        (average_yield_per_grade['Make'] == int(selected_make)) &
        (average_yield_per_grade['Block'] == selected_block_float) &
        (average_yield_per_grade['Measurement'] == selected_measurement_float), 'Actual_Yield'].values

    if len(current_yield) > 0:
        current_yield = current_yield[0]
    else:
        current_yield = 0

    steps_selected_grade = [
        dict(range=[0, current_yield], color="green"),
        dict(range=[current_yield, 100], color="red")
    ]

    yield_gauge_chart_selected_grade = go.Figure(go.Indicator(
        mode='gauge+number',
        value=current_yield,
        title=f'Average Yield for Plastic {selected_plastic}',
        gauge=dict(
            axis=dict(range=[None, 100]),
            bar=dict(color="green"),
            steps=steps_selected_grade,
            threshold=dict(line=dict(color="red", width=2), thickness=0.75)
        )
    ))

    steps_overall_avg = [
        dict(range=[0, overall_avg_yield], color="green"),
        dict(range=[overall_avg_yield, 100], color="red")
    ]

    yield_gauge_chart_overall_avg = go.Figure(go.Indicator(
        mode='gauge+number',
        value=overall_avg_yield,
        title='Overall Average Yield',
        gauge=dict(
            axis=dict(range=[None, 100]),
            bar=dict(color="green"),
            steps=steps_overall_avg,
            threshold=dict(line=dict(color="red", width=2), thickness=0.75)
        )
    ))

    return yield_gauge_chart_selected_grade, yield_gauge_chart_overall_avg


# Callback to update data table based on selected plastic grade, Make, Block, and Measurement
@app.callback(
    [Output('selected-data-table', 'data'),
     Output('download-link', 'href')],
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value'),
     Input('measurement-dropdown', 'value')]
)
def update_data_table(selected_plastic, selected_make, selected_block, selected_measurement):
    # Check if all selected values are not None or empty string
    if all(value is not None and value != '' for value in
           [selected_plastic, selected_make, selected_block, selected_measurement]):
        selected_df = df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                         (df['Block'] == float(selected_block)) & (df['Measurement'] == float(selected_measurement))]

        # Convert DataFrame to CSV and encode as base64 for download link
        csv_string = selected_df.to_csv(index=False, encoding='utf-8')
        csv_base64 = 'data:text/csv;base64,' + b64encode(csv_string.encode()).decode()

        return selected_df.to_dict('records'), csv_base64
    else:
        return [], ''


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

@AnnMarieW I have added the code here as you suggested.

@AnnMarieW
Copy link
Collaborator

@pat1510

In the last callback I see you are returning the csv data to the href prop. This is being flagged as an invalid (dangerous) link.

I recommend using the dcc.Download component instead. See more info in the Dash docs

If you have any more questions, please feel free to ask them on the Dash Community Forum

@pat1510
Copy link

pat1510 commented Feb 13, 2024

Hello All, thanks for the input. But I am still getting the same error. I may be doing something incorrect, could anyone look into my code and see what I am doing wrong?

import dash
import numpy as np
from dash import dcc, html
from dash.dash_table import DataTable
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
from dash.dependencies import Input, Output
from base64 import b64encode

# Demo DataFrame
data = {
    'Plastic': ['A', 'A', 'B', 'B', 'A', 'B', 'A', 'A', 'B', 'B', 'A', 'B'],
    'Make': [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],
    'Block': [10, 20, 30, 10, 20, 30, 10, 20, 30, 10, 20, 30],
    'Measurement': [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],
    'Actual_Yield': [85, 90, 88, 92, 87, 91, 85, 90, 88, 92, 87, 91],
    'Min': [80, 85, 85, 88, 82, 90, 80, 85, 85, 88, 82, 90],
    'Max': [88, 92, 90, 94, 89, 94, 88, 92, 90, 94, 89, 94]
}

df = pd.DataFrame(data)

# Calculate average yield for each grade and additional filters
average_yield_per_grade = df.groupby(['Plastic', 'Make', 'Block', 'Measurement'])['Actual_Yield'].mean().reset_index()

# Calculate overall average yield
overall_avg_yield = df['Actual_Yield'].mean()

app = dash.Dash(__name__)

app.layout = html.Div(className='row', children=[
    # Left column for Production Line Chart
    html.Div(className='six columns', children=[
        html.H1(children='KPI Dashboard', style={'textAlign': 'center'}),

        # Dropdown for selecting plastic grades
        dcc.Dropdown(
            id='plastic-dropdown',
            options=[
                {'label': plastic, 'value': plastic} for plastic in df['Plastic'].unique()
            ],
            value=df['Plastic'].iloc[0],  # Set default value to the first grade in the dataframe
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Plastic Grade'
        ),

        # Dropdown for selecting Make
        dcc.Dropdown(
            id='make-dropdown',
            options=[],  # Will be populated dynamically
            value='',  # Set default value to empty
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Make'
        ),

        # Dropdown for selecting Block
        dcc.Dropdown(
            id='block-dropdown',
            options=[],  # Will be populated dynamically
            value='',  # Set default value to empty
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Block'
        ),

        # Dropdown for selecting Measurement
        dcc.Dropdown(
            id='measurement-dropdown',
            options=[],  # Will be populated dynamically
            value='',  # Set default value to empty
            style={'width': '50%', 'margin': '20px auto'},
            placeholder='Select Measurement'
        ),

        # Line Chart
        dcc.Graph(
            id='production-line-chart',
            figure=px.line(title='Default Actual_Yield Trend')  # Set default figure
        ),
        # Download Button
        # html.Button('Download Data as Excel', id='download-link'),
        # dcc.Download(id="download-dataframe-csv"),

        dcc.Link('Download Data as Excel', id='download-link', href='/'),
        dcc.Download(id="download-dataframe-csv"),
    ]),

    # Center column for DataTable
    html.Div(className='four columns', children=[
        html.H3(children='Selected Combinations Data Table', style={'textAlign': 'center'}),

        DataTable(
            id='selected-data-table',
            columns=[
                {'name': 'Plastic', 'id': 'Plastic'},
                {'name': 'Make', 'id': 'Make'},
                {'name': 'Block', 'id': 'Block'},
                {'name': 'Measurement', 'id': 'Measurement'},
                {'name': 'Actual_Yield', 'id': 'Actual_Yield'},
                {'name': 'Min', 'id': 'Min'},
                {'name': 'Max', 'id': 'Max'},
                # Add more columns as needed
            ],
            style_table={'height': '300px', 'overflowY': 'auto'},
        ),
    ]),

    # Right column for Yield Gauge Charts
    html.Div(className='six columns', children=[
        # Yield Gauge Chart for Selected Grade
        html.Div(className='graph-container', children=[
            dcc.Graph(id='yield-gauge-chart-selected-grade',
                      style={'width': '700px', 'height': '500px', 'marginLeft': '280px'}
                      ),
        ]),

        # Yield Gauge Chart for Overall Average
        html.Div(className='graph-container', children=[
            dcc.Graph(id='yield-gauge-chart-overall-average',
                      style={'width': '700px', 'height': '500px', 'marginLeft': '-10px'}
                      ),
        ]),
    ], style={'display': 'flex'}),

    # Interval component for updating gauge value
    dcc.Interval(
        id='interval-component',
        interval=1 * 1000,  # in milliseconds (1 second)
        n_intervals=0
    ),
])


# Callback to populate Make dropdown based on selected plastic grade
@app.callback(
    Output('make-dropdown', 'options'),
    [Input('plastic-dropdown', 'value')]
)
def update_make_dropdown(selected_plastic):
    make_options = [{'label': str(make), 'value': make} for make in
                    df[df['Plastic'] == selected_plastic]['Make'].unique()]
    return make_options


# Callback to populate Block dropdown based on selected plastic grade and Make
@app.callback(
    Output('block-dropdown', 'options'),
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value')]
)
def update_block_dropdown(selected_plastic, selected_make):
    # Check if selected_make is not empty
    if not selected_make:
        return []

    block_options = [{'label': str(block), 'value': block} for block in
                     df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make))]['Block'].unique()]
    return block_options


# Callback to populate Measurement dropdown based on selected plastic grade, Make, and Block
@app.callback(
    Output('measurement-dropdown', 'options'),
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value')]
)
def update_measurement_dropdown(selected_plastic, selected_make, selected_block):
    # Check if selected_block is not empty
    if not selected_block:
        return []

    measurement_options = [{'label': str(measurement), 'value': measurement} for measurement in
                           df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                              (df['Block'] == float(selected_block))]['Measurement'].unique()]
    return measurement_options


# Callback to update line chart based on selected plastic grade and filters
@app.callback(
    Output('production-line-chart', 'figure'),
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value'),
     Input('measurement-dropdown', 'value'),
     Input('interval-component', 'n_intervals')]
)
def update_line_chart(selected_plastic, selected_make, selected_block, selected_measurement, n_intervals):
    # Check if any selected value is empty
    if any(value is None or value == '' for value in
           [selected_plastic, selected_make, selected_block, selected_measurement]):
        return px.line(title='No data available')  # Return a default line chart with a title

    selected_df = df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                     (df['Block'] == float(selected_block)) & (df['Measurement'] == float(selected_measurement))].copy()

    if selected_df.empty:
        return px.line(title='No data available')  # Return a default line chart with a title

    selected_df['Count'] = range(1, len(selected_df) + 1)

    # Check if 'Min' and 'Max' columns are present in the DataFrame
    if 'Min' not in selected_df.columns or 'Max' not in selected_df.columns:
        return px.line(title='Missing data columns')  # Return a default line chart with a title

    line_chart = px.line(selected_df, x='Count', y=['Min', 'Max'],
                         title=f'Plastic-{selected_plastic}, Make-{selected_make}, Block-{selected_block}, Measurement-{selected_measurement} Min'
                               f' and Max Trend')
    # Update marker properties for Min and Max
    line_chart.update_traces(
        line=dict(width=2),  # Adjust the width of the line
        mode='markers+lines',  # Show markers and lines
        marker=dict(size=10)  # Adjust the size of the marker
    )
    line_chart.update_layout(xaxis_title='Count')

    return line_chart


@app.callback(
    [Output('yield-gauge-chart-selected-grade', 'figure'),
     Output('yield-gauge-chart-overall-average', 'figure')],
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value'),
     Input('measurement-dropdown', 'value'),
     Input('interval-component', 'n_intervals')]
)
def update_gauge_charts(selected_plastic, selected_make, selected_block, selected_measurement, n_intervals):
    # Check if any selected value is None or an empty string
    if any(value is None or value == '' for value in
           [selected_plastic, selected_make, selected_block, selected_measurement]):
        return go.Figure(), go.Figure()  # Return default figures

    # Check if selected_block and selected_measurement are not None or empty string before converting to float
    selected_block_float = float(selected_block) if selected_block and selected_block != '' else None
    selected_measurement_float = float(
        selected_measurement) if selected_measurement and selected_measurement != '' else None

    selected_df = df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                     (df['Block'] == selected_block_float) & (df['Measurement'] == selected_measurement_float)].copy()

    if selected_df.empty:
        return go.Figure(), go.Figure()

    selected_df['Count'] = range(1, len(selected_df) + 1)

    current_yield = average_yield_per_grade.loc[
        (average_yield_per_grade['Plastic'] == selected_plastic) &
        (average_yield_per_grade['Make'] == int(selected_make)) &
        (average_yield_per_grade['Block'] == selected_block_float) &
        (average_yield_per_grade['Measurement'] == selected_measurement_float), 'Actual_Yield'].values

    if len(current_yield) > 0:
        current_yield = current_yield[0]
    else:
        current_yield = 0

    steps_selected_grade = [
        dict(range=[0, current_yield], color="green"),
        dict(range=[current_yield, 100], color="red")
    ]

    yield_gauge_chart_selected_grade = go.Figure(go.Indicator(
        mode='gauge+number',
        value=current_yield,
        title=f'Average Yield for Plastic {selected_plastic}',
        gauge=dict(
            axis=dict(range=[None, 100]),
            bar=dict(color="green"),
            steps=steps_selected_grade,
            threshold=dict(line=dict(color="red", width=2), thickness=0.75)
        )
    ))

    steps_overall_avg = [
        dict(range=[0, overall_avg_yield], color="green"),
        dict(range=[overall_avg_yield, 100], color="red")
    ]

    yield_gauge_chart_overall_avg = go.Figure(go.Indicator(
        mode='gauge+number',
        value=overall_avg_yield,
        title='Overall Average Yield',
        gauge=dict(
            axis=dict(range=[None, 100]),
            bar=dict(color="green"),
            steps=steps_overall_avg,
            threshold=dict(line=dict(color="red", width=2), thickness=0.75)
        )
    ))

    return yield_gauge_chart_selected_grade, yield_gauge_chart_overall_avg


@app.callback(
    [Output('selected-data-table', 'data'),
     Output('download-link', 'href')],
    [Input('plastic-dropdown', 'value'),
     Input('make-dropdown', 'value'),
     Input('block-dropdown', 'value'),
     Input('measurement-dropdown', 'value')]
)
def update_data_table(selected_plastic, selected_make, selected_block, selected_measurement):
    # Check if all selected values are not None or empty string
    if all(value is not None and value != '' for value in
           [selected_plastic, selected_make, selected_block, selected_measurement]):
        selected_df = df[(df['Plastic'] == selected_plastic) & (df['Make'] == int(selected_make)) &
                         (df['Block'] == float(selected_block)) & (df['Measurement'] == float(selected_measurement))]

        # Convert DataFrame to CSV and encode as base64 for download link
        csv_string = selected_df.to_csv(index=False, encoding='utf-8')
        csv_base64 = 'data:text/csv;base64,' + b64encode(csv_string.encode()).decode()

        return selected_df.to_dict('records'), csv_base64
    else:
        return [], ''


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

@AnnMarieW
Copy link
Collaborator

@pat1510 Please ask your question on the Dash Community Forum. I'll be happy to help you over there.

This issue is now closed.

@pat1510
Copy link

pat1510 commented Feb 13, 2024 via email

@pat1510
Copy link

pat1510 commented Feb 13, 2024 via email

@AnnMarieW
Copy link
Collaborator

@pat1510 That's a great topic for the forum - please ask over there 🙂

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants