come modificare una cella dash_table.DataTable e aggiornare altri callback

Aug 28 2020

Ho un dashboard Plotly-Dash qui che viene aggiornato utilizzando gli input delle variabili sul lato sinistro, nonché alcune variabili "fisse".

Queste variabili fisse sono mostrate in dash_table.DataTable nella parte inferiore dell'immagine. Se l'utente sceglie di modificare il valore in DataTable, vorrei aggiornare i callback che utilizzavano questi valori fissi.

A partire da ora, i callback utilizzano il menu a discesa e gli input numerici come [Input(' ', ' ')] e le variabili fisse sono memorizzate come variabili e utilizzate nelle equazioni pertinenti.

C'è un modo per:

  • utilizzare un callback per definire questi valori variabili fissi. E quando la tabella viene modificata, lo sono anche queste variabili "fisse", ..
  • oppure, utilizzare un callback per cercare nella tabella e, se vengono apportate modifiche, aggiornare i valori delle variabili fisse.

Una domanda un po 'vaga che so, l'ho cercato su Google praticamente in ogni modo e la maggior parte delle informazioni ha a che fare con il filtraggio di tabelle dati e la visualizzazione di righe, meno selezionando e memorizzando i valori delle celle come variabili.

Tutto ciò di cui ho veramente bisogno è un esempio di prendere un valore numerico di cella, usando come callback [Input()], e quel callback usando l'input in una formula algebrica di base.

Ho allegato il codice, vedrai che ho armeggiato con la prima richiamata, il resto del codice funziona bene.

import dash
import dash_design_kit as ddk
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import pandas as pd
import dash_daq as daq
import dash_table
from dash.dependencies import Input, Output
import math

import pandas as pd
import pathlib

from crunch_numbers import *
num_datacards = 5

# theme.js supplies some additional styling, generated using editor

# REQUIRED FOR DEPLOYMENT
app = dash.Dash(__name__, suppress_callback_exceptions=True) # keep suppress_ in production code
server = app.server # expose server variable for Procfile


app.layout = ddk.App(show_editor=True, children=[
    
    ddk.Header([
            ddk.Logo("assets/komatsuLogo.png",
                style={
                    "height": "30px",
                    "margin-right":"0px",
                    "width": "auto",
                },
            ),
            
            ddk.Title('Drivetrain Selection'),
        ]), # end of ddk.Header



  
    ddk.Block(width=20,children=[ # left-side (inputs)
        ddk.Card([ # machine Configuration
            ddk.CardHeader("Machine Configuration"),
            html.Br(),
            dcc.Dropdown(
                id='Platform',
                options=[
                        {'label': 'Badger', 'value': 'Badger'},
                        {'label': 'Weasel', 'value': 'Weasel'},
                    ],
                value='Badger',
                clearable=False,
                placeholder="Select Machine",
            ),
            html.Br(),
            dcc.Dropdown(
                id='battery_size',
                options=[
                        {'label': '5S1P ( 66kWh)', 'value': 66},
                        {'label': '5S2P (132kWh)', 'value': 132},
                    ],
                value=66,
                clearable=False,
                #placeholder="Battery Size (kWh)",
            ),
            html.Br(),
            dcc.Dropdown(
                id='Motor Selection',
                options=[
                        {'label': 'MD 2200', 'value': 'sumo_md_2200'},
                    ],
                value='sumo_md_2200',
                clearable=False,
                placeholder="Motor Selection",
            ),
        ]), # end of Machine Configuration

        ddk.Card([ # "Inputs"
            ddk.CardHeader("Inputs"),

            daq.NumericInput(
                id='ramp_angle',
                label='% Grade',
                labelPosition='top',
                value=0,
                min=0,
                max=18,
                size='auto',
            ),
            html.Br(),
            daq.NumericInput(
                id='ground_speed',
                label='Speed (kph)',
                labelPosition='top',
                value=0,
                min=0,
                max=15,
                size='auto',
            ),
            html.Br(),
            daq.NumericInput(
                id='parasitics',
                label='Parasitic Loads (kw)',
                labelPosition='top',
                value=0,
                min=0,
                max=30,
                size='auto',
            ),
            #html.Br(),

        ]), # end of "Inputs"
    
    ]), # end of left-side

    ddk.Block(width=80, children=[ # right side block
        ddk.Card([ # datacards and plot
            ddk.DataCard(
                width=100/num_datacards, # num_datacards is defined at top of file
                id='motor_speed',
                value=0,
                label="(RPM)",
            ),
            ddk.DataCard(
                width=100/num_datacards,
                id='motor_torque',
                value=0,
                label="(NM)",
            ),
            ddk.DataCard(
                width=100/num_datacards,
                id='traction_efficiency',
                value=0,
                label="(Tot. %)",
            ),
            ddk.DataCard(
                width=100/num_datacards,
                id='total_power',
                value=0,
                label="(kW)",
            ),
            ddk.DataCard(
                width=100/num_datacards,
                id='autonomy',
                value=0,
                label="(km)",
            ),
            dcc.Graph(id='plot'),
        ]), # end datacards and plot

        ddk.Card(width=100,children=[ # table card

            ddk.CardHeader("Machine Characteristics"),

            dcc.Markdown(
                    """
                    Update values in the table to modify machine performance.
                    """,
                    style={'textAlign': 'justify'}
            ),

            dash_table.DataTable(
                    id='machine_spec_table',
                    data=Badger.to_dict("rows"),
                    columns=[ # only 'Values' and 'Mechanical Efficiency' are editable!
                        {"name": i, "id": i,"editable":False,"selectable":True} 
                        if i == "Description" or i == "Units"
                        else  {"name": i, "id": i,"selectable":True} 
                        for i in Badger.columns
                        

                    ],
                    style_as_list_view=True,
                    style_header={"fontWeight": "bold", "textTransform": "capitalize"},
                    style_data_conditional=[
                        {
                            "if": {"row_index": "even"},
                            "backgroundColor": "var(--report_background_page)",
                        }
                    ],
                    editable=True,
            ),

        ]) # end of table card
    ]) # end of right side block
]) # end of ddk.App


""" 
Example of how to manage column width, should the need arise
    style_cell_conditional=[   
        {
            'if': {'column_id': 'Units'},
            'width': 25
        } for c in ['Units']
    ],
"""

############################# TABLE CALLBACKS ##################################################
################################################################################################

def find_fixed_variables(dict_list,var):
    return dict_list[]
    


############################# DRIVETRAIN SELECTION CALLBACKS ###################################
################################################################################################

@app.callback(
    Output('motor_speed', 'value'),
    [Input('ground_speed', 'value'),
     Input('machine_spec_table','data')] # , Input('tire_rr', 'value'), Input('diff_ratio', 'value'), Input('transfer_ratio', 'value')
)
def update_output(ground_speed,dict_list): #tire_rr, diff_ratio, transfer_ratio

    return math.floor((ground_speed*1000)/60/(2*math.pi*tire_rr)*diff_ratio*transfer_ratio)

@app.callback(
        Output('total_power', 'value'),
        [Input('ground_speed', 'value'), 
         Input('ramp_angle', 'value')] #, Input('parasitics', 'value')] # Input('GVW', 'value'), Input('RR', 'value'),, Input('traction_efficiency', 'value')
)
def update_output(ground_speed, ramp_angle): #, traction_efficiency

    power = math.floor(((RR/100)*(ground_speed*0.278) * GVW * gravity_cnst * math.cos(math.atan(ramp_angle/100))
                        / 0.9 / 1000)
                       + ((ground_speed * 0.278) * GVW * gravity_cnst * math.sin(math.atan(ramp_angle / 100))
                          / 0.9 / 1000)
                       )

    if ground_speed == 0:
        return 0
    else:
        return power

@app.callback(
    Output('motor_torque', 'value'),
    [Input('ground_speed', 'value'), 
     Input('motor_speed', 'value'), 
     Input('total_power', 'value'),]
)
def update_output(ground_speed, motor_speed, total_power):
    if ground_speed == 0:
        return 0
    elif math.floor(9.5488*total_power*1000/motor_speed) < 50:
        return 50
    else:
        return math.floor(9.5488*total_power*1000/motor_speed)

@app.callback(
    Output('plot', 'figure'),
    [Input('motor_speed', 'value'), 
     Input('motor_torque', 'value')] #Input('Motor Selection', 'value')
)
def update_output(motor_speed, motor_torque): # , Motor_Selection

    fig = go.Figure(
        layout=go.Layout(
            # title="Motor Efficiency Plot",
            # autosize=False,
            # width=500,
            paper_bgcolor="rgba(0,0,0,0)",
            plot_bgcolor="rgba(0,0,0,0)",
            yaxis=dict(title="Motor Torque (Nm)"),
            xaxis=dict(title="Motor Speed (RPM)"),
        )
    )
    fig.update_layout(legend=dict(
        orientation="h",
        yanchor="bottom",
        y=1,
        xanchor="left",
        x=0
    )
    )

    fig.add_trace(go.Scatter(
        x=TM4_BoundaryCurve['Speed (rpm)'],
        y=TM4_BoundaryCurve['Peak Torque (Nm)'],
        name="Peak Torque",
    )
    )
    fig.add_trace(go.Scatter(
        x=TM4_BoundaryCurve['Speed (rpm)'],
        y=TM4_BoundaryCurve['Continuous Torque (Nm)'],
        name="Cont. Torque",
    )
    )

    fig.add_trace(go.Contour(
        z=[TM4_EfficiencyMap['0'], TM4_EfficiencyMap['280'], TM4_EfficiencyMap['420'], TM4_EfficiencyMap['560'],
           TM4_EfficiencyMap['700'],
           TM4_EfficiencyMap['840'], TM4_EfficiencyMap['980'], TM4_EfficiencyMap['1120'], TM4_EfficiencyMap['1260'],
           TM4_EfficiencyMap['1400'],
           TM4_EfficiencyMap['1540'], TM4_EfficiencyMap['1680'], TM4_EfficiencyMap['1820'], TM4_EfficiencyMap['1960'],
           TM4_EfficiencyMap['2100'],
           TM4_EfficiencyMap['2240'], TM4_EfficiencyMap['2380'], TM4_EfficiencyMap['2520'], TM4_EfficiencyMap['2660'],
           TM4_EfficiencyMap['2800'],
           TM4_EfficiencyMap['2940'], TM4_EfficiencyMap['3080'], TM4_EfficiencyMap['3220'], TM4_EfficiencyMap['3360'],
           TM4_EfficiencyMap['3500'], ],
        x=TM4_EfficiencyMap['Speed'],
        y=TM4_EfficiencyMap['Torque'],
        transpose=True,
        colorscale='Blues',
        ncontours=20,
        opacity=0.5,
        showscale=False,
        contours=dict(
            showlabels=True,  # show labels on contours
            labelfont=dict(  # label font properties
                size=12,
                color='white',
            )
        )
    )
    )

    fig.add_trace(go.Scatter(
        x=[motor_speed],
        y=[motor_torque],
        name="Actual",
        mode="markers",
        marker=dict(size=20, color='black', symbol="x"),
    )
    )
    return fig

@app.callback(
    Output('autonomy', 'value'),
    [Input('ground_speed', 'value'), 
     Input('total_power', 'value'), 
     Input('battery_size', 'value')]
)
def update_output(ground_speed, total_power, battery_size):
    if ground_speed == 0 or total_power == 0:
        return 0
    else:
        return round((battery_size * DOD / total_power) * ground_speed, 2)
    
@app.callback(
    Output('traction_efficiency', 'value'),
    [Input('motor_speed', 'value'), 
     Input('motor_torque', 'value')]
)
def update_output(motor_speed, motor_torque):
    df = pd.DataFrame(TM4_EfficiencyMap)
    if motor_speed <= 280:
        speed = str(0)
        torque = 50
    else:
        speed = str(int((round(motor_speed / 140, 0) / 2) * 280))
        torque = round(motor_torque / 50, 0) * 50

    z = sum(round(df.loc[df['Torque'] == torque, speed] / 100 * diff_eff * transfer_eff * driveshaft_mt * driveshaft_td, 2))
    return z





################################# MANDATORY SERVER CODE ##################################
if __name__ == '__main__':
    app.run_server(debug=True)

Risposte

RuBa Aug 28 2020 at 21:45

Va bene, soluzione così semplice, niente che alcune istruzioni di test print() non possano risolvere lol. Essenzialmente a seconda del tipo di input di callback utilizzato per Input('machine_spec_table','data')

Qui ho usato "dati", ce ne sono molti altri disponibili e spiegati nella documentazione,

l'input per la funzione è il seguente:

your_var = [
    {'Description': 'Gross Vehicle Weight', 'Values': 29500, 'Units': 'kg', 'Mechanical Efficiency': '-'}, 
    {'Description': 'Weight Distribution', 'Values': '60/40', 'Units': '', 'Mechanical Efficiency': '-'}, 
    {'Description': 'Tire Rolling Radius', 'Values': 0.589, 'Units': 'm', 'Mechanical Efficiency': '-'}, 
    {'Description': 'Differential Ratio', 'Values': 20.65, 'Units': '', 'Mechanical Efficiency': 0.93}, 
    {'Description': 'Transfer Case Ratio', 'Values': 2.48, 'Units': '', 'Mechanical Efficiency': 0.98}, 
    {'Description': 'Rolling Resistance', 'Values': 0.02, 'Units': '', 'Mechanical Efficiency': '-'}, 
    {'Description': 'Drive Shaft', 'Values': '-', 'Units': '', 'Mechanical Efficiency': 0.98}
]

Un elenco di dizionari! Facile da accedere, qualcosa sulla falsariga di table[0]['Values]va bene :) (0 è l'indice dell'elenco, 'Valori' la chiave del dizionario).