Dynamic calculation based on interactive inputs

Hi,

I am new to dash and hit another roadblock today.

In my app, I want to give the user the possivility to add upon every click on “+” (see graphic below) additional dropdowns and inputs. However, I would like to calculate, based on the selection made under GK and the Amount, a running total of Amount * k(GK), where k is a constant which depends on GK-selection and is retrieved from a pd-Dataframe.

Any suggestions how I can implement this would be appreciated.

Thank you!

import dash
from dash import Dash, dcc, html, State, MATCH, ALL
from dash.dependencies import Input, Output
from dash import html
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd
import numpy as np

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])

df = pd.read_csv('Zemente_Stand_08_22.csv', delimiter=';', encoding='utf8')
df1 = pd.read_csv('Ausgangstoffe_08_22.csv', delimiter=';', encoding='utf8')

app.layout = html.Div([
        
    dbc.Row(
        dbc.Col(
            html.H1("Transportbeton", 
            style={'font-size': '36px', 'textAlign': 'center', 'justyfy-content': 'center', 'text-decoration': 'underline', 'background':'#636363', 'color':'white'}),
            ),
        style={'height':'60px', 'background':'#636363'}
    ),

    dbc.Row([
        dbc.Col([

            html.Div([html.H5('Zement', style={"height": "30px", 'margin-top': '12px', 'margin-bottom':'12px', 'justyfy-content': 'center'}),
            dcc.Dropdown(
                df["Werk"].unique(),
                placeholder='Werk',
                id='Werksauswahl'
            ), 
                html.Div([
                    dcc.Dropdown(
                    df["Zement"],
                    placeholder='Zementtyp',
                    id='Zementauswahl'
            ),]),
            
            ], style={'text-align':'left', "width": "15%", 'margin-right':'25px'}),

            html.Div([html.H5('Menge [kg]', style={"height": "30px", 'margin-top': '12px', 'margin-bottom':'12px'}),
            dcc.Input(
                id='Zementmenge_input',
                type='number',
                value=0,
                placeholder='Menge [kg]',
                style={'height':'35px'}
            ),]),

        ], style={'display':'flex', "width": "30%"}),

 
        html.Div(id='Zement_gesamt_gwp', style={'color':'red'}),

    ]),

       
    dbc.Row([
            dbc.Col([
                html.Div([html.H5('GK', style={"height": "10px", 'margin-top': '12px', 'margin-bottom':'2px', 'justyfy-content': 'center'}),
                ], style={'text-align':'left', "width": "15%", 'margin-right':'25px'}),
                html.Div([html.H5('Amount [kg]', style={"height": "10px", 'margin-top': '12px', 'margin-bottom':'2px', 'justyfy-content': 'center'}),
            ]),

    ],   style={'display':'flex', "width": "30%"})],
    
    ),


    html.Div([
        html.H6('', style={"height": "2px", 'margin-top': '12px', 'margin-bottom':'12px', 'justyfy-content': 'center'}),
        html.Div(id='dropdown-container', children=[]),
        html.Button("+", id="add-filter", n_clicks=0),
        html.Div(id='GK_gesamt_gwp', style={'color':'red'}),
        html.Div(id='dropdown-container-output')
    ]),

    
])
    


@app.callback(
    dash.dependencies.Output('Zementauswahl', 'options'),
    [dash.dependencies.Input('Werksauswahl', 'value')])
def set_cement_plant (selected_werk):
    return df['Zement'][df['Werk'] == selected_werk]

@app.callback(
    dash.dependencies.Output('Zement_gesamt_gwp', 'children'),
    [dash.dependencies.Input('Werksauswahl', 'value'),
    dash.dependencies.Input('Zementauswahl', 'value'),
    dash.dependencies.Input('Zementmenge_input', 'value')])
def get_gwp_value (selected_werk, selected_cement, selected_amount):
    gwp = df['GWP_netto'][(df['Werk']==selected_werk) & (df['Zement']==selected_cement)]
    zement_gwp = gwp*selected_amount
    zement_gwp = round(zement_gwp, 2)   
    return zement_gwp.astype(str) + str(' kg CO2/e')


@app.callback(
    Output('dropdown-container', 'children'),
    Input('add-filter', 'n_clicks'),
    State('dropdown-container', 'children'))
def display_dropdowns(n_clicks, children):
    new_dropdown = dbc.Row([
                        dbc.Col([
                            dcc.Dropdown(
                            df1['Prozess'].loc[0:3],
                            placeholder='GK-Typ',
                            id={
                            'type': 'GK-dropdown',
                            'index': n_clicks}, style={'text-align':'left', "width": "230px", 'margin-right':'25px'}),
                            
                            dcc.Input(id='GK-Menge', type='number', value=0),
                            ], style={'display':'flex', "width": "30%"})],
                    )
                        
        
    children.append(new_dropdown)
    return children


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

grafik

Hi @slyfox

so you want to create something like a lookup? Let’s say you have Kies=100, Sand=200 and in the pandas DataFrame you will a value k for these two variables?

Yes, exactly!

The df is derived from a simple .csv. Here is an excerpt.

grafik

So something like this: gwp = df1['GWP'][(df1['Type']==selected_type)]
selected_type would be the output of 'type':'GK-dropdown' in function display_dropdowns.
Then finally, the gwp variable should be multiplied with the amount the user inserts into the input field.

Maybe you could map the Type and the GWP in the dropdown component like this? Obviously you would create the “options” list of dictionaries by iterating thru your DataFrame

from dash import Dash, Input, Output, html, dcc
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc


# initialize app
app = Dash(
    external_stylesheets=[dbc.themes.SPACELAB]
)

# app layout
app.layout = dbc.Container(
    [
        dbc.Row(
            dcc.Dropdown(
                id='drop',
                options=[
                    {'label': 'Kies', 'value': 0.0122},
                    {'label': 'Sand', 'value': 0.0045},
                    {'label': 'Splitt', 'value': 0.011},
                ],
                style={'width': '30%'}
            ),
        ),
        dbc.Row(
            dcc.Input(
                id='amount',
                type='number',
                value=100,
                style={'width': '30%'}
            ),
        ),
        dbc.Row(
            html.Pre(
                id='pre'
            )
        )
    ]
)


@app.callback(
    Output('pre', 'children'),
    [
        Input('drop', 'value'),
        Input('amount', 'value')
    ],
    prevent_initial_callback=True
)
def calc(k_value, amount):
    if not k_value:
        raise PreventUpdate
    return f'The total amount is {k_value * amount}'


if __name__ == '__main__':
    app.run_server(debug=True, port=8058)
1 Like

@slyfox You could try using a DataTable for this. Here is an example:

order_entry

from dash import Dash, dash_table, html, Input, Output, ctx
import pandas as pd

product_data = {
    "product": ["apples", "bananas", "milk", "egg", "bread", "cookies"],
    "price": [3.75, 1.65, 1.55, 3.60, 3.00, 3.97],
    "unit": ["kg", "kg", "l", "dz", "ea", "pkg"],
}
new_order_line = {"product": "", "price": 0, "unit": "", "quantity": 0, "total": 0}
df_product = pd.DataFrame(product_data)
df_new_order_line = pd.DataFrame(new_order_line, index=[0])


app = Dash(__name__)

title = html.H4("Order Entry Table", style={"textAlign": "center", "margin": 30})
add_button = html.Button("+", n_clicks=0, id="order-entry-add-btn")
total = html.Div(id="order-entry-total", style={"textAlign": "right"})

table = dash_table.DataTable(
    id="order-entry-table",
    columns=[
        {
            "name": "product",
            "id": "product",
            "editable": True,
            "presentation": "dropdown",
        },
        {
            "name": "quantity",
            "id": "quantity",
            "type": "numeric",
            "format": {"specifier": ",.0f"},
            "editable": True,
        },
        {"name": "unit", "id": "unit"},
        {
            "name": "price",
            "id": "price",
            "type": "numeric",
            "format": {"specifier": "$,.2f"},
        },
        {
            "name": "total",
            "id": "total",
            "type": "numeric",
            "format": {"specifier": "$,.2f"},
        },
    ],
    data=df_new_order_line.to_dict("records"),
    row_deletable=True,
    dropdown={
        "product": {
            "options": [{"label": i, "value": i} for i in df_product["product"]]
        },
    },
)

app.layout = html.Div([title, add_button, table, total])


@app.callback(
    Output("order-entry-table", "data"),
    Output("order-entry-total", "children"),
    Input("order-entry-add-btn", "n_clicks"),
    Input("order-entry-table", "data"),
)
def add_row(n_clicks, rows):
    df_order = pd.DataFrame(rows)

    # add a new line
    if ctx.triggered_id == "order-entry-add-btn":
        if n_clicks > 0:
            df_order = df_order.append(df_new_order_line, ignore_index=True)

    # update the order
    if rows:
        df_merged = pd.merge(df_order, df_product, how="left", on="product")
        df_order["price"] = df_merged["price_y"]
        df_order["unit"] = df_merged["unit_y"]
        df_order["total"] = df_order.price * df_order.quantity

    order_total = df_order["total"].sum() if rows else 0
    return df_order.to_dict("records"), f"Total   ${order_total:,.2f}"


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

2 Likes