from BPTK_Py import Model
import BPTK_Py
= Model(starttime=0.0, stoptime=20.0, dt=1.0, name="Test Model") model
Simple Dashboard
Developing Dashboards Using the SimpleDashboard Utility Class
One of BPTK’s biggest strengths is the ability to easily create interactive dashboards. The simple dashboard class in BPTK allows the user to create complex, interactive dashboards with minimum coding requirements.
Creating the model
First, we need a BPTK model, let’s create one.
We need to do all necessary imports and instantiate the model:
Next, we have to define the model. The model calculates an account balance given a salary and a tax rate.
= model.stock("balance")
stock = model.stock("tax_acc")
taxes_accumulated
= model.flow("income")
income = model.flow("taxes")
taxes
= model.constant("salary")
constantSalary = model.constant("tax")
constantTax = 0.0
constantTax.equation = 0.0
constantSalary.equation
= income - taxes
stock.equation = constantSalary # income - tax
income.equation = constantSalary * constantTax
taxes.equation
= taxes taxes_accumulated.equation
Lastly we have to register a scenario manager and scenario in BPTK.
= {"sm": {"model": model}}
scenario_manager
= BPTK_Py.bptk()
bptk
bptk.register_scenario_manager(scenario_manager)
bptk.register_scenarios(="sm",
scenario_manager=
scenarios
{"testScenario":{}
} )
Next we want to display the output using an interactive dashboard.
Display output
The simple dashboard class automatically handles plot updates, connects widgets to the model and handles widget updating. You can create dashboards without the simple dashboard class, but that is not recommended.
First, we have to import and instantiate the SimpleDashboard class. It requires both the scenario manager and scenario.
from BPTK_Py.visualizations import SimpleDashboard
import ipywidgets as widgets
= SimpleDashboard(bptk, scenario_manager="sm", scenario="testScenario") dashboard
Now we have to create a few widgets. We need two sliders to update the tax rate and salary. When the sliders change, a graph plotting the balance is updated.
= widgets.FloatSlider(
wdg_tax_slider ="Tax rate",
descriptionmin=0.0,
max=1.0,
=0.2,
value=0.01,
step
)= widgets.FloatSlider(
wdg_salary_slider ="Salary",
descriptionmin=0.0,
max=1000.0,
=100.0,
value=1.0,
step
)
"tax")
dashboard.add_widget(wdg_tax_slider, "salary")
dashboard.add_widget(wdg_salary_slider,
= dashboard.add_plot(
plot =["balance"],
equations=["Balance"],
names="Account Balance",
title="Months",
x_label="Balance",
y_label
)
= widgets.VBox([wdg_tax_slider, wdg_salary_slider])
controls
display(plot)
display(controls) dashboard.start()
It would be useful to see how much of the salary gets paid as taxes (in absolute terms). Luckily, adding multiple graphs is trivial.
= dashboard.add_plot(
plot2 =["tax_acc"],
equations=["Taxes"],
names="Paid Taxes",
title="Months",
x_label="Taxes",
y_label
)
= widgets.Tab(children = [plot, plot2])
graph_tabs 0, 'Balance')
graph_tabs.set_title(1, 'Taxes')
graph_tabs.set_title(
display(graph_tabs)
display(controls) dashboard.start()
Advanced features
Complex models tend to have more requirements than just updating a graph when a slider moves.
Callbacks
This dashboard will show how to use callbacks to execute custom code when widget values change. In the example below, taxes can be activated and deactivated using a tick box.
# instantiate dashboard
= SimpleDashboard(bptk, scenario_manager="sm", scenario="testScenario")
dashboard
# Create all widgets
= widgets.FloatSlider(
wdg_salary_slider ="Salary",
descriptionmin=0.0,
max=1000.0,
=100.0,
value=1.0,
step
)= widgets.Checkbox(
wdg_taxes ="Taxes",
description=False
value
)
= widgets.FloatSlider(
wdg_tax_slider ="Tax rate",
descriptionmin=0.0,
max=1.0,
=0.0,
value=0.01,
step
)
"salary")
dashboard.add_widget(wdg_salary_slider, "tax")
dashboard.add_widget(wdg_tax_slider,
# Hide tax slider
= "none"
wdg_tax_slider.layout.display
# When tax checkbox is changed
def taxes_changed(active):
if(active): # If tax checkbox is set to true
= "flex" # Show tax slider
wdg_tax_slider.layout.display else:
= "none" # Hide tax slider
wdg_tax_slider.layout.display = 0.0 # Set value of tax slider to 0
wdg_tax_slider.value
# Add tax tick box to dashboard and link it to the tax_changed function.
dashboard.add_widget(wdg_taxes, taxes_changed)
= dashboard.add_plot(
plot =["balance"],
equations=["Balance"],
names="Account Balance",
title="Months",
x_label="Balance",
y_label
)= widgets.VBox([wdg_salary_slider, wdg_taxes, wdg_tax_slider])
main_controls
display(plot)
display(main_controls)
dashboard.start()
Dynamically update plots
Plots are managed by the simple dashboard class. Dynamically updating plots is often required for more advanced features. In this example we update the number of steps a graph displays.
The dashboard uses the SimpleDashboard.update_plot_data function to update plot data when the visualization period is selected in a dropdown.
# instantiate dashboard
= SimpleDashboard(bptk, scenario_manager="sm", scenario="testScenario")
dashboard
# Create all widgets
= widgets.Dropdown(
wdg_months_select ="Display",
description=["10", "20"],
options
)
def month_select(months):
"visualize_to_period", int(months) + 1, -1)
dashboard.update_plot_data(
dashboard.add_widget(wdg_months_select, month_select)
= dashboard.add_plot(
plot =["balance"],
equations=["Balance"],
names="Account Balance",
title=11,
visualize_to_period="Months",
x_label="Balance",
y_label
)= widgets.VBox([wdg_months_select])
main_controls
display(plot)
display(main_controls)
dashboard.start()
Custom plots
SimpleDashboard only supports simple plots. More complex plotting requires custom plots. Below is an example on how to create a custom table plot.
# instantiate dashboard
= SimpleDashboard(bptk, scenario_manager="sm", scenario="testScenario")
dashboard
def custom_plot():
= bptk.plot_scenarios(
df =["sm"],
scenario_managers=["testScenario"],
scenarios=["balance"],
equations="Table Example",
title={"sm_testScenario3_balance": "Balance"},
series_names=True
return_df
)
display(df)
= dashboard.add_custom_plot(custom_plot)
plot
display(plot) dashboard.start()
A complex example
This example incorporates all techniques into one model. It is a fairly complex example that can be used as a reference when creating interactive dashboard.
First, let’s update the model. This model has an income tax and social security payments. Social security payments are only paid once a threshold of 200 is reached. Health insurance is paid optionally, either based on income or fixed amount. Income now increases over time.
from BPTK_Py import sd_functions as sd
# Create the model
= Model(starttime=0.0, stoptime=20.0, dt=1.0, name="Test Model")
model
# The final balance of the account
= model.stock("balance")
stock
# All required flows
= model.flow("income_in")
income = model.flow("income_tax_in")
incomeTax = model.flow("social_security_in")
socialSecurity
= model.flow("health_insurance_in")
healthInsurance = model.flow("health_insurance_fixed_in") # Health insurance fixed amount
healthInsuranceFixed = model.flow("health_insurance_income_in") # Health insurance based on income
healthInsuranceIncome
# All constants (can be adjusted in the interactive dashboard)
= model.constant("salary")
constantSalary = model.constant("income_tax")
constantTax = model.constant("social_security")
constantSocialSecurity = model.constant("health_insurance_fixed")
constantHealthInsuranceFixed = model.constant("health_insurance_income")
constantHealthInsuranceIncome
= 300.0
constantSalary.equation = 0.2
constantTax.equation = 40.0
constantSocialSecurity.equation = 0.0
constantHealthInsuranceFixed.equation = 0.0
constantHealthInsuranceIncome.equation
# All flow equations
= constantHealthInsuranceIncome * constantSalary
healthInsuranceIncome.equation = constantHealthInsuranceFixed
healthInsuranceFixed.equation
= income - incomeTax - socialSecurity - healthInsurance
stock.equation = constantSalary * sd.lookup(sd.time(), "salary_curve")
income.equation = constantSalary * constantTax
incomeTax.equation = sd.min(sd.max(0.0, constantSalary - 200.0), 1.0) * constantSocialSecurity
socialSecurity.equation = healthInsuranceFixed + healthInsuranceIncome
healthInsurance.equation
# Create scenario manager and scenario
= {"sm": {"model": model,
scenario_manager "base_points": {
"salary_curve":[
1.0, 1.0],
[2.0, 1.0],
[3.0, 1.0],
[4.0, 1.0],
[5.0, 1.1],
[9.0, 1.20],
[13.0, 1.35],
[17.0, 1.6],
[
]
}}}
= BPTK_Py.bptk()
bptk
bptk.register_scenario_manager(scenario_manager)
bptk.register_scenarios(="sm",
scenario_manager=
scenarios
{"testScenario2":{
}
} )
Next, we create the dashboard:
from BPTK_Py.visualizations.simple_dashboard import ModelConnection
# instantiate dashboard
= SimpleDashboard(bptk, scenario_manager="sm", scenario="testScenario2")
dashboard
# Create all widgets
= widgets.FloatSlider(
wdg_salary_slider ="Salary",
descriptionmin=0.0,
max=1000.0,
=100.0,
value=1.0,
step
)"salary")
dashboard.add_widget(wdg_salary_slider,
= widgets.FloatSlider(
wdg_tax_slider ="Income Tax",
descriptionmin=0.0,
max=1.0,
=0.2,
value=0.01,
step
)"income_tax")
dashboard.add_widget(wdg_tax_slider,
= widgets.FloatSlider(
wdg_social_security_slider ="Social Security",
descriptionmin=0.0,
max=200.0,
=40.0,
value=1.0,
step
)"social_security")
dashboard.add_widget(wdg_social_security_slider,
= widgets.Checkbox(
wdg_health_insurance_tick ="Health insurance",
description
)= widgets.Dropdown(
wdg_health_insurance_drop ="Type",
description=["Fixed rate", "Income dependent"],
options
)
= widgets.FloatSlider(
wdg_health_insurance_fixed_rate ="Fixed Rate",
descriptionmin=0.0,
max=400.0,
=0.0,
value=1.0,
step
)"health_insurance_fixed")
dashboard.add_widget(wdg_health_insurance_fixed_rate, = widgets.FloatSlider(
wdg_health_insurance_income_based ="Percentage",
descriptionmin=0.0,
max=1.0,
=0.0,
value=0.01,
step
)"health_insurance_income")
dashboard.add_widget(wdg_health_insurance_income_based,
= widgets.Dropdown(
wdg_months_select ="Months",
description=["10", "20"],
options
)
= widgets.FloatSlider(
wdg_salary_increase_1 ="Salary 1-4",
descriptionmin=0.0,
max=3.0,
=1.0,
value=0.01,
step
)= widgets.FloatSlider(
wdg_salary_increase_2 ="Salary 5-9",
descriptionmin=0.0,
max=3.0,
=1.1,
value=0.01,
step
)= widgets.FloatSlider(
wdg_salary_increase_3 ="Salary 10-13",
descriptionmin=0.0,
max=3.0,
=1.2,
value=0.01,
step
)= widgets.FloatSlider(
wdg_salary_increase_4 ="Salary 13-17",
descriptionmin=0.0,
max=3.0,
=1.35,
value=0.01,
step
)= widgets.FloatSlider(
wdg_salary_increase_5 ="Salary 18-20",
descriptionmin=0.0,
max=3.0,
=1.6,
value=0.01,
step
)
# When health insurance is deactivated, the values of wdg_health_insurance_fixed_rate and wdg_health_insurance_income_based are set to 0. These variables save the slider values to restore it if health insurance is enabled again.
= 0.0
fixed_rate = 0.0
income_based
# Called when health insurance is deactivated or activated
def health_insurance_event(active):
global fixed_rate
global income_based
if(active): # Show widgets
= 'flex'
wdg_health_insurance_drop.layout.display
if(wdg_health_insurance_drop.value == "Fixed rate"): # If the health insurance type is fixed rate
= 'flex'
wdg_health_insurance_fixed_rate.layout.display = 'none'
wdg_health_insurance_income_based.layout.display = fixed_rate # Restore last slider value
wdg_health_insurance_fixed_rate.value else: # If the health insurance type is income dependent
= 'none'
wdg_health_insurance_fixed_rate.layout.display = 'flex'
wdg_health_insurance_income_based.layout.display = income_based # Restore last slider value
wdg_health_insurance_income_based.value else: # Hide widgets
= 'none'
wdg_health_insurance_drop.layout.display = 'none'
wdg_health_insurance_fixed_rate.layout.display = 'none'
wdg_health_insurance_income_based.layout.display
# Save last slider value
if(wdg_health_insurance_drop.value == "Fixed rate"):
= wdg_health_insurance_fixed_rate.value
fixed_rate else:
= wdg_health_insurance_income_based.value
income_based
# Set slider values to 0, to remove the effect of health insurance from the model
= 0.0
wdg_health_insurance_income_based.value = 0.0
wdg_health_insurance_fixed_rate.value
# Called when the type of health insurance is changed
def health_insurance_type_event(type):
global fixed_rate
global income_based
if(type == "Fixed rate"): # If the new type is fixed rate
# Remove income based slider, save the value and set it to 0
= 'none'
wdg_health_insurance_income_based.layout.display = wdg_health_insurance_income_based.value
income_based = 0.0
wdg_health_insurance_income_based.value
# Show fixed rate slider, restore the value
= 'flex'
wdg_health_insurance_fixed_rate.layout.display = fixed_rate
wdg_health_insurance_fixed_rate.value
else:
# Remove fixed rate slider, save the value and set it to 0
= 'none'
wdg_health_insurance_fixed_rate.layout.display = wdg_health_insurance_fixed_rate.value
fixed_rate = 0.0
wdg_health_insurance_fixed_rate.value
# Show income based slider, restore the value
= 'flex'
wdg_health_insurance_income_based.layout.display = income_based
wdg_health_insurance_income_based.value
def month_select(months):
"visualize_to_period", int(months) + 1, -1)
dashboard.update_plot_data(
# Add widgets to the dashboard
dashboard.add_widget(wdg_health_insurance_tick, health_insurance_event)
dashboard.add_widget(wdg_health_insurance_drop, health_insurance_type_event)
dashboard.add_widget(wdg_months_select, month_select)=ModelConnection(element="salary_curve", points=[0,1,2,3]))
dashboard.add_widget(wdg_salary_increase_1, model_connection=ModelConnection(element="salary_curve", points=[4]))
dashboard.add_widget(wdg_salary_increase_2, model_connection=ModelConnection(element="salary_curve", points=[5]))
dashboard.add_widget(wdg_salary_increase_3, model_connection=ModelConnection(element="salary_curve", points=[6]))
dashboard.add_widget(wdg_salary_increase_4, model_connection=ModelConnection(element="salary_curve", points=[7]))
dashboard.add_widget(wdg_salary_increase_5, model_connection
dashboard.add_widget(wdg_months_select, month_select)
dashboard.add_widget(wdg_months_select, month_select)
dashboard.add_widget(wdg_months_select, month_select)
dashboard.add_widget(wdg_months_select, month_select)
# Hide widgets
= 'none'
wdg_health_insurance_drop.layout.display = 'none'
wdg_health_insurance_fixed_rate.layout.display = 'none'
wdg_health_insurance_income_based.layout.display
def table():
= bptk.plot_scenarios(
df =["sm"],
scenario_managers=["testScenario2"],
scenarios=["balance", "income_in", "income_tax_in", "social_security_in", "health_insurance_in"],
equations="Table Example",
title={"sm_testScenario2_balance": "Balance", "sm_testScenario2_income_in": "Income", "sm_testScenario2_income_tax_in": "Tax", "sm_testScenario2_social_security_in": "Social Security", "sm_testScenario2_health_insurance_in": "Health Insurance"},
series_names=True,
return_df=int(wdg_months_select.value)+1
visualize_to_period
)
display(df)
= dashboard.add_custom_plot(table)
plot_table
= dashboard.add_plot(
plot =["balance"],
equations=["Balance"],
names="Account Balance",
title=11,
visualize_to_period="Months",
x_label="Balance",
y_label
)
= widgets.Tab([plot, plot_table])
tabbed_graphs 0, "Balance")
tabbed_graphs.set_title(1, "Table")
tabbed_graphs.set_title(
= widgets.VBox([wdg_tax_slider, wdg_social_security_slider, wdg_months_select])
main_controls = widgets.VBox([wdg_salary_slider, wdg_salary_increase_1, wdg_salary_increase_2, wdg_salary_increase_3, wdg_salary_increase_4, wdg_salary_increase_5])
salary_controls = widgets.VBox([wdg_health_insurance_tick, wdg_health_insurance_drop, wdg_health_insurance_fixed_rate, wdg_health_insurance_income_based])
health_insurance_controls
= widgets.Tab([main_controls, salary_controls, health_insurance_controls])
controls_tab 0, "General")
controls_tab.set_title(1, "Salary")
controls_tab.set_title(2, "Health insurance")
controls_tab.set_title(
display(tabbed_graphs)
display(controls_tab)
dashboard.start()