simian-examples 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simian/examples/all_components.py +408 -0
- simian/examples/ballthrower.py +429 -0
- simian/examples/ballthrower_engine.py +102 -0
- simian/examples/js/treemap.js +76 -0
- simian/examples/logo.png +0 -0
- simian/examples/plottypes.py +428 -0
- simian/examples/treemap.json +108 -0
- simian/examples/treemap.py +157 -0
- simian/examples/workshop_example.json +211 -0
- simian/examples/workshop_example.py +219 -0
- simian/examples/wrapped_thrower.py +233 -0
- simian_examples-3.3.0.dist-info/METADATA +24 -0
- simian_examples-3.3.0.dist-info/RECORD +15 -0
- simian_examples-3.3.0.dist-info/WHEEL +5 -0
- simian_examples-3.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": [
|
|
3
|
+
{
|
|
4
|
+
"label": "Load GAP Minder data",
|
|
5
|
+
"showValidations": false,
|
|
6
|
+
"disableOnInvalid": true,
|
|
7
|
+
"tableView": false,
|
|
8
|
+
"key": "loadData",
|
|
9
|
+
"type": "button",
|
|
10
|
+
"action": "event",
|
|
11
|
+
"input": true,
|
|
12
|
+
"event": "LoadData"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"label": "No data",
|
|
16
|
+
"content": "<b>No data loaded.</b>",
|
|
17
|
+
"refreshOnChange": false,
|
|
18
|
+
"key": "noData",
|
|
19
|
+
"customConditional": "show = !('availableYears' in data) || data.availableYears.length == 0;",
|
|
20
|
+
"type": "htmlelement",
|
|
21
|
+
"input": false,
|
|
22
|
+
"tableView": false
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"label": "Available continents",
|
|
26
|
+
"key": "availableContinents",
|
|
27
|
+
"type": "hidden",
|
|
28
|
+
"input": true,
|
|
29
|
+
"tableView": false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"label": "Select continent(s)",
|
|
33
|
+
"labelPosition": "left-left",
|
|
34
|
+
"widget": "choicesjs",
|
|
35
|
+
"tableView": true,
|
|
36
|
+
"multiple": true,
|
|
37
|
+
"dataSrc": "custom",
|
|
38
|
+
"data": {
|
|
39
|
+
"custom": "values=data.availableContinents"
|
|
40
|
+
},
|
|
41
|
+
"key": "selectContinents",
|
|
42
|
+
"customConditional": "show = 'availableContinents' in data && data.availableContinents.length != 0",
|
|
43
|
+
"type": "select",
|
|
44
|
+
"input": true
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"label": "Tabs",
|
|
48
|
+
"components": [
|
|
49
|
+
{
|
|
50
|
+
"label": "Data",
|
|
51
|
+
"key": "dataTab",
|
|
52
|
+
"components": [
|
|
53
|
+
{
|
|
54
|
+
"label": "Data Columns",
|
|
55
|
+
"columns": [
|
|
56
|
+
{
|
|
57
|
+
"components": [
|
|
58
|
+
{
|
|
59
|
+
"label": "Available years",
|
|
60
|
+
"key": "availableYears",
|
|
61
|
+
"type": "hidden",
|
|
62
|
+
"input": true,
|
|
63
|
+
"tableView": false
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"label": "Select year(s)",
|
|
67
|
+
"labelPosition": "left-left",
|
|
68
|
+
"widget": "choicesjs",
|
|
69
|
+
"tableView": true,
|
|
70
|
+
"multiple": true,
|
|
71
|
+
"dataSrc": "custom",
|
|
72
|
+
"data": {
|
|
73
|
+
"custom": "values=data.availableYears;"
|
|
74
|
+
},
|
|
75
|
+
"key": "selectYears",
|
|
76
|
+
"customConditional": "show = 'availableYears' in data && data.availableYears.length != 0;",
|
|
77
|
+
"type": "select",
|
|
78
|
+
"input": true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"label": "GAP table",
|
|
82
|
+
"customClass": "table-sm table-striped",
|
|
83
|
+
"hideLabel": true,
|
|
84
|
+
"tableView": false,
|
|
85
|
+
"key": "gapTable",
|
|
86
|
+
"type": "customdatatables",
|
|
87
|
+
"input": true
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"width": 6,
|
|
91
|
+
"offset": 0,
|
|
92
|
+
"push": 0,
|
|
93
|
+
"pull": 0,
|
|
94
|
+
"size": "md",
|
|
95
|
+
"currentWidth": 6
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"components": [
|
|
99
|
+
{
|
|
100
|
+
"label": "Number of selected years",
|
|
101
|
+
"labelPosition": "left-left",
|
|
102
|
+
"mask": false,
|
|
103
|
+
"disabled": true,
|
|
104
|
+
"tableView": false,
|
|
105
|
+
"delimiter": false,
|
|
106
|
+
"requireDecimal": false,
|
|
107
|
+
"inputFormat": "plain",
|
|
108
|
+
"truncateMultipleSpaces": false,
|
|
109
|
+
"clearOnHide": false,
|
|
110
|
+
"calculateValue": "value = data.selectYears.length;",
|
|
111
|
+
"key": "numberSelectedYears",
|
|
112
|
+
"type": "number",
|
|
113
|
+
"input": true
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"label": "Plot",
|
|
117
|
+
"aspectRatio": 1.3333333333333333,
|
|
118
|
+
"hideLabel": true,
|
|
119
|
+
"tableView": false,
|
|
120
|
+
"key": "plot",
|
|
121
|
+
"type": "customplotly",
|
|
122
|
+
"input": true
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
"width": 6,
|
|
126
|
+
"offset": 0,
|
|
127
|
+
"push": 0,
|
|
128
|
+
"pull": 0,
|
|
129
|
+
"size": "md",
|
|
130
|
+
"currentWidth": 6
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"key": "dataColumns",
|
|
134
|
+
"type": "columns",
|
|
135
|
+
"input": false,
|
|
136
|
+
"tableView": false
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"label": "Scenarios",
|
|
142
|
+
"key": "scenariosTab",
|
|
143
|
+
"components": [
|
|
144
|
+
{
|
|
145
|
+
"label": "'Calculate' Scenarios",
|
|
146
|
+
"showValidations": false,
|
|
147
|
+
"tableView": false,
|
|
148
|
+
"key": "updateScenarios",
|
|
149
|
+
"type": "button",
|
|
150
|
+
"action": "event",
|
|
151
|
+
"input": true,
|
|
152
|
+
"event": "RunScenarios"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"label": "Scenarios",
|
|
156
|
+
"tableView": false,
|
|
157
|
+
"rowDrafts": false,
|
|
158
|
+
"key": "scenarios",
|
|
159
|
+
"type": "editgrid",
|
|
160
|
+
"displayAsTable": false,
|
|
161
|
+
"input": true,
|
|
162
|
+
"components": [
|
|
163
|
+
{
|
|
164
|
+
"label": "Selected year",
|
|
165
|
+
"labelPosition": "left-left",
|
|
166
|
+
"placeholder": "yyyy",
|
|
167
|
+
"applyMaskOn": "change",
|
|
168
|
+
"mask": false,
|
|
169
|
+
"tableView": true,
|
|
170
|
+
"delimiter": false,
|
|
171
|
+
"requireDecimal": false,
|
|
172
|
+
"inputFormat": "plain",
|
|
173
|
+
"truncateMultipleSpaces": false,
|
|
174
|
+
"validate": {
|
|
175
|
+
"custom": "valid = data.availableYears.includes(input);"
|
|
176
|
+
},
|
|
177
|
+
"errors": {
|
|
178
|
+
"custom": "Year must be one of the years in the dataset."
|
|
179
|
+
},
|
|
180
|
+
"key": "inputYear",
|
|
181
|
+
"type": "number",
|
|
182
|
+
"input": true
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"label": "Total population",
|
|
186
|
+
"labelPosition": "left-left",
|
|
187
|
+
"applyMaskOn": "change",
|
|
188
|
+
"mask": false,
|
|
189
|
+
"disabled": true,
|
|
190
|
+
"tableView": true,
|
|
191
|
+
"delimiter": false,
|
|
192
|
+
"requireDecimal": false,
|
|
193
|
+
"inputFormat": "plain",
|
|
194
|
+
"truncateMultipleSpaces": false,
|
|
195
|
+
"key": "totalPop",
|
|
196
|
+
"type": "number",
|
|
197
|
+
"input": true
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"hideLabel": true,
|
|
205
|
+
"key": "tabs",
|
|
206
|
+
"type": "tabs",
|
|
207
|
+
"input": false,
|
|
208
|
+
"tableView": false
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Workshop Example application.
|
|
2
|
+
|
|
3
|
+
Built for simian.gui 3.0.0.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import numbers
|
|
8
|
+
|
|
9
|
+
import plotly.express
|
|
10
|
+
from pandas import DataFrame as pandas_DataFrame
|
|
11
|
+
from simian.gui import Form, component, utils
|
|
12
|
+
|
|
13
|
+
# Import the GAP Minder data from the Plotly Express module.
|
|
14
|
+
# The resulting GAP_DATA is a pandas.DataFrame. The columns that will be used are:
|
|
15
|
+
# "year" and "continents"
|
|
16
|
+
GAP_DATA = plotly.express.data.gapminder()
|
|
17
|
+
GAP_DATA.insert(0, "id", GAP_DATA.index) # 'id' is needed in DataTables, so we are adding it here.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
# Run as a script
|
|
22
|
+
from simian.local import run
|
|
23
|
+
|
|
24
|
+
run(
|
|
25
|
+
"simian.examples.workshop_example",
|
|
26
|
+
window_title="Workshop application",
|
|
27
|
+
debug=True,
|
|
28
|
+
show_refresh=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def gui_init(_meta_data: dict) -> dict:
|
|
33
|
+
"""Initializes the form.
|
|
34
|
+
|
|
35
|
+
Initialization of the form and the components therein.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
payload: Form definition.
|
|
39
|
+
"""
|
|
40
|
+
# Setup component initializers and create the Form from the definition in the .json file.
|
|
41
|
+
Form.componentInitializer(
|
|
42
|
+
gapTable=init_gap_table,
|
|
43
|
+
selectContinents=make_trigger_happy,
|
|
44
|
+
selectYears=make_trigger_happy,
|
|
45
|
+
)
|
|
46
|
+
form = Form(from_file=__file__)
|
|
47
|
+
|
|
48
|
+
# Put the form in the outputs.
|
|
49
|
+
payload = {
|
|
50
|
+
"form": form,
|
|
51
|
+
"navbar": {
|
|
52
|
+
"title": "Workshop Application",
|
|
53
|
+
"subtitle": "<small>Simian Demo</small>",
|
|
54
|
+
},
|
|
55
|
+
"showChanged": True, # Flag changes to the settings in the UI.
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return payload
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def init_gap_table(gap_table: component.DataTables):
|
|
62
|
+
"""Initializer function for the gapTable."""
|
|
63
|
+
table_column_names = list(GAP_DATA.columns)
|
|
64
|
+
|
|
65
|
+
gap_table.setColumns(
|
|
66
|
+
titles=[i_name.capitalize() for i_name in table_column_names],
|
|
67
|
+
keys=table_column_names,
|
|
68
|
+
className=["noneditable"] * len(table_column_names),
|
|
69
|
+
visible=[False] + [True] * (len(table_column_names) - 1),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def make_trigger_happy(comp):
|
|
74
|
+
"""Initializer to make input components triggerHappy."""
|
|
75
|
+
comp.properties["triggerHappy"] = "FilterChange"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def gui_event(meta_data: dict, payload: dict) -> dict:
|
|
79
|
+
"""Event handling of the application.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
meta_data: Form meta data.
|
|
83
|
+
payload: Current status of the Form's contents.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
payload: Updated Form contents.
|
|
87
|
+
"""
|
|
88
|
+
# Register the events with their functions.
|
|
89
|
+
Form.eventHandler(
|
|
90
|
+
LoadData=load_data,
|
|
91
|
+
RunScenarios=update_scenarios,
|
|
92
|
+
FilterChange=filter_changed,
|
|
93
|
+
)
|
|
94
|
+
try:
|
|
95
|
+
caller = utils.getEventFunction(meta_data, payload)
|
|
96
|
+
payload = caller(meta_data, payload)
|
|
97
|
+
|
|
98
|
+
except ImportError as exc:
|
|
99
|
+
# No method with matching name found in the current namespace. May be used to have
|
|
100
|
+
# custom event routing. Here we only log the occurrence.
|
|
101
|
+
logging.exception(exc)
|
|
102
|
+
|
|
103
|
+
# Lower the "Form has changes" flag.
|
|
104
|
+
payload["pristine"] = True
|
|
105
|
+
|
|
106
|
+
return payload
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def filter_changed(meta_data: dict, payload: dict) -> dict:
|
|
110
|
+
"""Continent or Year selection change callback."""
|
|
111
|
+
# Continent or year selection changed. Update the table and plot.
|
|
112
|
+
update_table_and_plot(payload)
|
|
113
|
+
|
|
114
|
+
# Trigger a recalculate of the scenarios.
|
|
115
|
+
update_scenarios(meta_data, payload)
|
|
116
|
+
|
|
117
|
+
return payload
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def filter_data(selected_continents: list, selected_years: list) -> pandas_DataFrame:
|
|
121
|
+
"""Filter GAP data based on selected continents and years."""
|
|
122
|
+
if isinstance(selected_continents, str):
|
|
123
|
+
# Selected continent is a string. Put in a list for use in the filtering.
|
|
124
|
+
selected_continents = [selected_continents]
|
|
125
|
+
|
|
126
|
+
if isinstance(selected_years, numbers.Number):
|
|
127
|
+
# Selected year is a number. Put in a list for use in the filtering.
|
|
128
|
+
selected_years = [selected_years]
|
|
129
|
+
|
|
130
|
+
# Filter the GAP data.
|
|
131
|
+
new_gap = GAP_DATA[
|
|
132
|
+
GAP_DATA["continent"].isin(selected_continents) & GAP_DATA["year"].isin(selected_years)
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
return new_gap
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def load_data(_meta_data: dict, payload: dict) -> dict:
|
|
139
|
+
"""Put the Gap Minder data in the table."""
|
|
140
|
+
utils.setSubmissionData(payload, "gapTable", GAP_DATA)
|
|
141
|
+
|
|
142
|
+
# Put the available continents in the Select component.
|
|
143
|
+
gap_continents = sorted(set(GAP_DATA["continent"]))
|
|
144
|
+
utils.setSubmissionData(payload, "selectContinents", gap_continents)
|
|
145
|
+
utils.setSubmissionData(payload, "availableContinents", gap_continents)
|
|
146
|
+
|
|
147
|
+
# Put the available years in the Select component and select the last year.
|
|
148
|
+
gap_years = sorted(set(GAP_DATA["year"]))
|
|
149
|
+
utils.setSubmissionData(payload, "selectYears", [gap_years[-1]])
|
|
150
|
+
utils.setSubmissionData(payload, "availableYears", gap_years)
|
|
151
|
+
|
|
152
|
+
# Plot the loaded data.
|
|
153
|
+
update_table_and_plot(payload)
|
|
154
|
+
|
|
155
|
+
return payload
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def update_scenarios(_meta_data: dict, payload: dict) -> dict:
|
|
159
|
+
"""Update the scenario results in the scenario list."""
|
|
160
|
+
scenario_list, _ = utils.getSubmissionData(payload, key="scenarios")
|
|
161
|
+
|
|
162
|
+
for scenario_row in scenario_list:
|
|
163
|
+
# Loop over the scenario rows.
|
|
164
|
+
selected_year = scenario_row["inputYear"]
|
|
165
|
+
|
|
166
|
+
# Get the selected continents and filter the GAP data for the scenario year.
|
|
167
|
+
selected_continents, _ = utils.getSubmissionData(payload, "selectContinents")
|
|
168
|
+
filtered_gap_data = filter_data(selected_continents, selected_year)
|
|
169
|
+
|
|
170
|
+
# Calculate the Total population of all nations in the selection.
|
|
171
|
+
scenario_row["totalPop"] = float(filtered_gap_data["pop"].sum())
|
|
172
|
+
|
|
173
|
+
utils.setSubmissionData(payload, "scenarios", scenario_list)
|
|
174
|
+
|
|
175
|
+
return payload
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def update_table_and_plot(payload: dict) -> None:
|
|
179
|
+
"""Put the GAP data in the table and plot."""
|
|
180
|
+
selected_year, _ = utils.getSubmissionData(payload, "selectYears")
|
|
181
|
+
|
|
182
|
+
# Get the selected continents and filter the GAP data for the selected years.
|
|
183
|
+
selected_continents, _ = utils.getSubmissionData(payload, "selectContinents")
|
|
184
|
+
filtered_gap_data = filter_data(selected_continents, selected_year)
|
|
185
|
+
|
|
186
|
+
# Fill table with GAP data.
|
|
187
|
+
utils.setSubmissionData(payload, "gapTable", filtered_gap_data)
|
|
188
|
+
|
|
189
|
+
update_plot(payload, filtered_gap_data=filtered_gap_data)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def update_plot(payload: dict, filtered_gap_data: pandas_DataFrame = GAP_DATA) -> None:
|
|
193
|
+
"""Update the plot with a 'filtered' set of data."""
|
|
194
|
+
# Recreate the Plotly object.
|
|
195
|
+
plot_obj = utils.getSubmissionData(payload, key="plot")[0]
|
|
196
|
+
|
|
197
|
+
# Plot the new newly calculated x and y values, append the legend and put the plotly
|
|
198
|
+
# object in the submission data.
|
|
199
|
+
if len(filtered_gap_data) == 0:
|
|
200
|
+
# No data. Empty the plot.
|
|
201
|
+
plot_obj.figure = None
|
|
202
|
+
|
|
203
|
+
else:
|
|
204
|
+
# Fill the plot with data.
|
|
205
|
+
# https://plotly.com/python/line-and-scatter/
|
|
206
|
+
plot_obj.figure = plotly.express.scatter(
|
|
207
|
+
filtered_gap_data,
|
|
208
|
+
x="gdpPercap",
|
|
209
|
+
log_x=True,
|
|
210
|
+
y="lifeExp",
|
|
211
|
+
symbol="year",
|
|
212
|
+
size="pop",
|
|
213
|
+
size_max=60,
|
|
214
|
+
color="continent",
|
|
215
|
+
hover_name="country",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Put the new plot contents in the payload.
|
|
219
|
+
utils.setSubmissionData(payload, "plot", plot_obj)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Simian Wrapped Ball Thrower app.
|
|
2
|
+
|
|
3
|
+
Built for simian.gui 3.1.0.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import simian.examples.ballthrower as throw_gui
|
|
10
|
+
import simian.gui
|
|
11
|
+
from simian.examples.ballthrower import (
|
|
12
|
+
COLLAPSE_PANEL,
|
|
13
|
+
LABEL_LEFT,
|
|
14
|
+
causeError,
|
|
15
|
+
clearHistory,
|
|
16
|
+
gui_event,
|
|
17
|
+
)
|
|
18
|
+
from simian.gui import component, utils
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from monkeyproof.modelwrapper.mwapp.application import Application
|
|
22
|
+
except ImportError as exc:
|
|
23
|
+
raise RuntimeError("Simian wrapper could not be found. Is it installed?") from exc
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
# Run as a script
|
|
28
|
+
from simian.entrypoint import assert_simian_gui_imports
|
|
29
|
+
|
|
30
|
+
assert_simian_gui_imports()
|
|
31
|
+
from simian.local import run
|
|
32
|
+
|
|
33
|
+
run("simian.examples.wrapped_thrower", window_title="Wrapped Ball Thrower", debug=False)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def gui_init(meta_data) -> dict:
|
|
37
|
+
"""Initializes the form.
|
|
38
|
+
|
|
39
|
+
Initialization of the form and the components therein.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
payload: Form definition.
|
|
43
|
+
"""
|
|
44
|
+
app = Application("examples.thrower.ballthrower_model.ThrowerModel")
|
|
45
|
+
app.populate(
|
|
46
|
+
userData={
|
|
47
|
+
"Resources_XLS": {
|
|
48
|
+
"SelectedBall": {"value": "None"},
|
|
49
|
+
"SpeedHorizontal": {"value": 10 / math.sqrt(2)},
|
|
50
|
+
"SpeedVertical": {"value": 10 / math.sqrt(2)},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
utils.setCache(meta_data, "app", app)
|
|
55
|
+
|
|
56
|
+
# Get the list of balls from the wrapped model.
|
|
57
|
+
balls_list = app.model.dataStore.resources.getValue("DefinedBalls")
|
|
58
|
+
|
|
59
|
+
# Create a form and add a label.
|
|
60
|
+
form_obj = simian.gui.Form("form")
|
|
61
|
+
|
|
62
|
+
# Create tabs in the form.
|
|
63
|
+
tabs = component.Tabs(key="tabs", parent=form_obj)
|
|
64
|
+
tab_inputs, tab_settings = tabs.setContent(labels=["Inputs", "Used settings"])
|
|
65
|
+
|
|
66
|
+
# Create a Columns object and fill it with two empty columns.
|
|
67
|
+
col = component.Columns(key="inputColumns", parent=tab_inputs)
|
|
68
|
+
left_column, right_column = col.setContent(components=[], widths=[6, 6])
|
|
69
|
+
|
|
70
|
+
# Add Throw speed and angle controls.
|
|
71
|
+
created_components = throw_gui.add_throw_speed_controls(left_column)
|
|
72
|
+
|
|
73
|
+
# Put a Ball selector component in the throw options panel
|
|
74
|
+
ball_selector = component.Select("ballSelector", created_components["throwPanel"])
|
|
75
|
+
ball_selector.label = "Selected Ball"
|
|
76
|
+
ball_selector.tooltip = "Ball to throw."
|
|
77
|
+
ball_selector.setRequired()
|
|
78
|
+
ball_selector.labelPosition = "left-left"
|
|
79
|
+
|
|
80
|
+
# Fill the Balls selector with the list of balls from the wrapped model.
|
|
81
|
+
ball_selector.setValues(balls_list["Name"], balls_list.to_dict("records"))
|
|
82
|
+
|
|
83
|
+
# Add the options panels and controls.
|
|
84
|
+
left_dis_opts = {"disabled": True, **LABEL_LEFT}
|
|
85
|
+
|
|
86
|
+
def ball_prop(name: str) -> dict:
|
|
87
|
+
return {
|
|
88
|
+
"calculateValue": f"""
|
|
89
|
+
if (data.ballSelector) {{
|
|
90
|
+
value=data.ballSelector['{name}']
|
|
91
|
+
}} else {{
|
|
92
|
+
value = 0
|
|
93
|
+
}}""",
|
|
94
|
+
**left_dis_opts,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ball_mass_opts = ball_prop("Weight [kg]")
|
|
98
|
+
ball_radius_opts = ball_prop("Diameter [m]")
|
|
99
|
+
ball_drag_opts = ball_prop("Drag Coefficient [-]")
|
|
100
|
+
|
|
101
|
+
utils.addComponentsFromTable(
|
|
102
|
+
parent=left_column,
|
|
103
|
+
build_table=[
|
|
104
|
+
# key class level options default label tooltip
|
|
105
|
+
["ballPanel", "Panel", 1, COLLAPSE_PANEL, None, "Ball settings", None],
|
|
106
|
+
["ballMass", "Number", 2, ball_mass_opts, 0, "Mass [kg]:", "Mass of the ball."],
|
|
107
|
+
["ballRadius", "Number", 2, ball_radius_opts, 0, "Radius [m]:", "Radius of the ball."],
|
|
108
|
+
["dragCoefficient", "Number", 2, ball_drag_opts, 0, "Drag coefficient [-]:", "Drag coefficient of the ball."],
|
|
109
|
+
["constantsPanel", "Panel", 1, COLLAPSE_PANEL, None, "Constants", None],
|
|
110
|
+
["gravity", "Number", 2, left_dis_opts, 9.81, "Gravity [m/s<sup>2</sup>]:", "Gravitational accelleration acting on the ball."],
|
|
111
|
+
["airDensity", "Number", 2, left_dis_opts, 1.29, "Air density [kg/m<sup>3</sup>]:", "Density of the air through which the ball moves."] # fmt: skip
|
|
112
|
+
],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Show Scenario information.
|
|
116
|
+
scenario_grid = component.HtmlTable(
|
|
117
|
+
"scenario_info", left_column, column_names=["Scenario Name", "Wind speed"]
|
|
118
|
+
)
|
|
119
|
+
scenario_grid.setDefaultValue(
|
|
120
|
+
[[i_scen.name, i_scen.getValue("Wind")[0][0]] for i_scen in app.model.dataStore.scenarios]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Fill the right column.
|
|
124
|
+
throw_gui.fill_right_column(right_column)
|
|
125
|
+
|
|
126
|
+
# Fill the Settings tab.
|
|
127
|
+
_fill_settings_tab(tab_settings)
|
|
128
|
+
|
|
129
|
+
# Put the form in the outputs.
|
|
130
|
+
payload = {
|
|
131
|
+
"form": form_obj,
|
|
132
|
+
"navbar": {
|
|
133
|
+
"title": "Wrapped Ball Thrower",
|
|
134
|
+
"subtitle": "<small>Simian demo</small>",
|
|
135
|
+
},
|
|
136
|
+
"showChanged": True, # Flag changes to the settings in the UI.
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return payload
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def throwing(meta_data: dict, payload: dict) -> dict:
|
|
143
|
+
"""Simulate throwing a ball.
|
|
144
|
+
|
|
145
|
+
Get the settings from the payload.
|
|
146
|
+
"""
|
|
147
|
+
throw_speed, _ = utils.getSubmissionData(payload, key="throwSpeed")
|
|
148
|
+
throw_angle, _ = utils.getSubmissionData(payload, key="throwAngle")
|
|
149
|
+
selected_ball, _ = utils.getSubmissionData(payload, key="ballSelector")
|
|
150
|
+
|
|
151
|
+
# Convert the speed and angle to the speed components.
|
|
152
|
+
ver_speed = throw_speed * math.sin(throw_angle / 180 * math.pi)
|
|
153
|
+
hor_speed = throw_speed * math.cos(throw_angle / 180 * math.pi)
|
|
154
|
+
|
|
155
|
+
app, _ = utils.getCache(meta_data, "app")
|
|
156
|
+
|
|
157
|
+
resources = app.model.dataStore.resources
|
|
158
|
+
resources.setValue("SelectedBall", selected_ball["Name"], overload=True)
|
|
159
|
+
resources.setValue("SpeedHorizontal", hor_speed, overload=True)
|
|
160
|
+
resources.setValue("SpeedVertical", ver_speed, overload=True)
|
|
161
|
+
app.model.run()
|
|
162
|
+
|
|
163
|
+
trajectories = app.model.dataStore.custom["trajectories"]
|
|
164
|
+
|
|
165
|
+
# Create a Plotly object from the information in the payload.
|
|
166
|
+
plot_obj, _ = utils.getSubmissionData(payload, key="plot")
|
|
167
|
+
|
|
168
|
+
# Plot the new newly calculated x and y values, append the legend and put the plotly
|
|
169
|
+
# object in the submission data.
|
|
170
|
+
nr = int(len(plot_obj.figure.data) / 4 + 1)
|
|
171
|
+
|
|
172
|
+
for i_scen, i_res in trajectories.items():
|
|
173
|
+
plot_obj.figure.add_scatter(
|
|
174
|
+
x=i_res["x"], y=i_res["y"], name=f"Attempt {nr} - {i_scen}", mode="lines"
|
|
175
|
+
)
|
|
176
|
+
utils.setSubmissionData(payload, key="plot", data=plot_obj)
|
|
177
|
+
|
|
178
|
+
# Get the maximum thrown distances.
|
|
179
|
+
distances = [d[-1] for d in app.model.dataStore.results.getValue("Distance")[1:]]
|
|
180
|
+
|
|
181
|
+
# Update the table with the settings used for the throws.
|
|
182
|
+
new_row = [
|
|
183
|
+
nr,
|
|
184
|
+
nr,
|
|
185
|
+
round(hor_speed, 3),
|
|
186
|
+
round(ver_speed, 3),
|
|
187
|
+
selected_ball["Weight [kg]"],
|
|
188
|
+
selected_ball["Diameter [m]"] / 2,
|
|
189
|
+
selected_ball["Drag Coefficient [-]"],
|
|
190
|
+
round(min(distances), 3),
|
|
191
|
+
round(max(distances), 3),
|
|
192
|
+
round(np.mean(distances), 3),
|
|
193
|
+
round(max(distances) - min(distances), 3),
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
if nr == 1:
|
|
197
|
+
# First throw. Only put the new row in the table.
|
|
198
|
+
new_table_values = [new_row]
|
|
199
|
+
else:
|
|
200
|
+
# Extra row. Append the row to the DataFrame from the submission data.
|
|
201
|
+
new_table_values, _ = utils.getSubmissionData(payload, key="summary")
|
|
202
|
+
new_table_values.append(dict(zip(new_table_values[0].keys(), new_row)))
|
|
203
|
+
|
|
204
|
+
utils.setSubmissionData(payload, key="summary", data=new_table_values)
|
|
205
|
+
|
|
206
|
+
return payload
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _fill_settings_tab(tab_settings):
|
|
210
|
+
"""Fill Settings tab."""
|
|
211
|
+
# Create a table to store the throw settings in.
|
|
212
|
+
table_out = component.DataTables(key="summary", parent=tab_settings)
|
|
213
|
+
table_out.setColumns(
|
|
214
|
+
*[
|
|
215
|
+
list(x)
|
|
216
|
+
for x in zip(
|
|
217
|
+
["Id", "id"],
|
|
218
|
+
["Attempt", "attempt"],
|
|
219
|
+
["Horizontal speed", "hor_speed"],
|
|
220
|
+
["Vertical speed", "ver_speed"],
|
|
221
|
+
["Ball mass", "ball_mass"],
|
|
222
|
+
["Radius", "radius"],
|
|
223
|
+
["Drag Coeff.", "drag"],
|
|
224
|
+
["Min. distance", "min_dist"],
|
|
225
|
+
["Max. distance", "max_dist"],
|
|
226
|
+
["Mean distance", "mean_dist"],
|
|
227
|
+
["Spread distance", "spread_dist"],
|
|
228
|
+
)
|
|
229
|
+
],
|
|
230
|
+
visible=[False] + [True] * 10,
|
|
231
|
+
)
|
|
232
|
+
table_out.label = "Used settings per throw"
|
|
233
|
+
table_out.setFeatures(searching=False, ordering=False, paging=False)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simian-examples
|
|
3
|
+
Version: 3.3.0
|
|
4
|
+
Summary: Simian Examples for Python
|
|
5
|
+
Author-email: "MonkeyProof Solutions B.V." <info@simiansuite.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://simiansuite.com
|
|
8
|
+
Project-URL: documentation, https://doc.simiansuite.com/simian-gui/index.html
|
|
9
|
+
Project-URL: releasenotes, https://doc.simiansuite.com/simian-gui/release_notes.html
|
|
10
|
+
Project-URL: issues, https://github.com/Simian-Web-Apps/Issue-Tracker
|
|
11
|
+
Project-URL: github, https://github.com/Simian-Web-Apps
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: Unix
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: simian-gui==3.3.0
|
|
20
|
+
Requires-Dist: scipy
|
|
21
|
+
Provides-Extra: local
|
|
22
|
+
Requires-Dist: simian-local==3.3.0; extra == "local"
|
|
23
|
+
Dynamic: provides-extra
|
|
24
|
+
Dynamic: requires-dist
|