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,428 @@
|
|
|
1
|
+
"""Simian-GUI plots examples application.
|
|
2
|
+
|
|
3
|
+
The plots shown in this application are created with the Python Plotly library. For more information
|
|
4
|
+
about Plotly refer to: https://plotly.com/python/.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
import os
|
|
9
|
+
import textwrap
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import plotly.express as px
|
|
13
|
+
import plotly.graph_objects as go
|
|
14
|
+
from simian.gui import Form, component, utils
|
|
15
|
+
|
|
16
|
+
# Prepare some pandas DataFrames with demo data.
|
|
17
|
+
GAP_MINDER = px.data.gapminder()
|
|
18
|
+
DATA_TIPS = px.data.tips()
|
|
19
|
+
STOCKS = px.data.stocks()
|
|
20
|
+
|
|
21
|
+
# Initialize defaults.
|
|
22
|
+
PLOTS_DICT = {}
|
|
23
|
+
BASE_LABEL = "Select a plot to show."
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
# Show the application in a Simian-Local window when the module is 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.plottypes", window_title="Plot types examples", debug=False)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def register_type(name):
|
|
37
|
+
"""Decorator initialization function with registration name input."""
|
|
38
|
+
|
|
39
|
+
def dec(func):
|
|
40
|
+
"""Decorator function with the to decorate function as input."""
|
|
41
|
+
|
|
42
|
+
def wrapped_func(payload) -> dict:
|
|
43
|
+
"""Wrapper around plot function."""
|
|
44
|
+
# Create a new Plotly object to get a clean starting point.
|
|
45
|
+
plot_obj = utils.Plotly()
|
|
46
|
+
func(plot_obj)
|
|
47
|
+
|
|
48
|
+
# Ensure the margins remain small.
|
|
49
|
+
plot_obj.figure.layout.margin.t = 40
|
|
50
|
+
plot_obj.figure.layout.margin.b = 30
|
|
51
|
+
plot_obj.figure.layout.margin.l = 50
|
|
52
|
+
|
|
53
|
+
# Put the new plot contents in the payload.
|
|
54
|
+
utils.setSubmissionData(payload, "plot", plot_obj)
|
|
55
|
+
|
|
56
|
+
# Use the decorated function's docstring as description.
|
|
57
|
+
doc_string = textwrap.dedent(" " + func.__doc__)
|
|
58
|
+
utils.setSubmissionData(payload, key="description", data=doc_string.strip())
|
|
59
|
+
|
|
60
|
+
# Show the code of the decorated function in the app.
|
|
61
|
+
utils.setSubmissionData(
|
|
62
|
+
payload,
|
|
63
|
+
key="code_block",
|
|
64
|
+
data=f"<pre><code>{inspect.getsource(func)}</code></pre>",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return payload
|
|
68
|
+
|
|
69
|
+
# Register the plot type in the dictionary.
|
|
70
|
+
PLOTS_DICT.update({name: wrapped_func})
|
|
71
|
+
|
|
72
|
+
return wrapped_func
|
|
73
|
+
|
|
74
|
+
return dec
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def gui_event(_meta_data: dict, payload: dict) -> dict:
|
|
78
|
+
"""Event handling of the application.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
meta_data: Form meta data.
|
|
82
|
+
payload: Current status of the Form's contents.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
payload: Updated Form contents.
|
|
86
|
+
"""
|
|
87
|
+
# No need to check where the event is coming from; only the plot_selection component can trigger
|
|
88
|
+
# events. Get the selected plot.
|
|
89
|
+
plot_selection, _ = utils.getSubmissionData(payload, key="plot_selection")
|
|
90
|
+
|
|
91
|
+
if plot_selection in PLOTS_DICT:
|
|
92
|
+
# Switch to the function that was registered for the plot type.
|
|
93
|
+
plot_func = PLOTS_DICT[plot_selection]
|
|
94
|
+
plot_func(payload=payload)
|
|
95
|
+
|
|
96
|
+
else:
|
|
97
|
+
# No selection. Clear the figure.
|
|
98
|
+
plot_obj, _ = utils.getSubmissionData(payload, "plot")
|
|
99
|
+
plot_obj.figure.data = []
|
|
100
|
+
plot_obj.figure.layout.title = BASE_LABEL
|
|
101
|
+
plot_obj.figure.layout.xaxis.title = {}
|
|
102
|
+
plot_obj.figure.layout.yaxis.title = {}
|
|
103
|
+
utils.setSubmissionData(payload, key="plot", data=plot_obj)
|
|
104
|
+
|
|
105
|
+
# Replace the description and code block with the default text.
|
|
106
|
+
utils.setSubmissionData(payload, key="description", data=BASE_LABEL)
|
|
107
|
+
utils.setSubmissionData(payload, key="code_block", data=BASE_LABEL)
|
|
108
|
+
|
|
109
|
+
return payload
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def gui_init(*_args) -> dict:
|
|
113
|
+
"""Initializes the form.
|
|
114
|
+
|
|
115
|
+
Initialization of the form and the components therein.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
payload: Form definition.
|
|
119
|
+
"""
|
|
120
|
+
# Create a form object as the base of the application.
|
|
121
|
+
form_obj = Form()
|
|
122
|
+
|
|
123
|
+
plot_defaults = {
|
|
124
|
+
"data": [],
|
|
125
|
+
"layout": {
|
|
126
|
+
"title": {"text": BASE_LABEL},
|
|
127
|
+
"margin": {"t": 40, "b": 30, "l": 50},
|
|
128
|
+
},
|
|
129
|
+
"config": {"displaylogo": False},
|
|
130
|
+
}
|
|
131
|
+
disable = {"disabled": True}
|
|
132
|
+
col_opts = {"width": 6}
|
|
133
|
+
|
|
134
|
+
# Turn off black formatter for readability of build tables.
|
|
135
|
+
base_controls = utils.addComponentsFromTable(
|
|
136
|
+
parent=form_obj,
|
|
137
|
+
build_table=[
|
|
138
|
+
# key class lvl options default
|
|
139
|
+
["columns", "Columns", 1, None, None],
|
|
140
|
+
["left_column", "column", 2, col_opts, None],
|
|
141
|
+
["right_column", "column", 2, col_opts, None],
|
|
142
|
+
["plot", "Plotly", 3, None, plot_defaults], # fmt: skip
|
|
143
|
+
],
|
|
144
|
+
column_names=["key", "class", "level", "options", "defaultValue"],
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
controls_dict = utils.addComponentsFromTable(
|
|
148
|
+
parent=base_controls["left_column"],
|
|
149
|
+
build_table=[
|
|
150
|
+
# key class lvl options default label
|
|
151
|
+
["plot_selection", "Select", 1, None, None, "Select a plot type"],
|
|
152
|
+
["description", "TextArea", 1, disable, BASE_LABEL, None],
|
|
153
|
+
["panel", "Panel", 1, None, None, "Source code"],
|
|
154
|
+
["code_block", "Html", 2, disable, BASE_LABEL, None], # fmt: skip
|
|
155
|
+
],
|
|
156
|
+
column_names=["key", "class", "level", "options", "defaultValue", "label"],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Put the plot types in the select component.
|
|
160
|
+
controls_dict["plot_selection"].setValues(list(PLOTS_DICT.keys()), list(PLOTS_DICT.keys()))
|
|
161
|
+
controls_dict["plot_selection"].properties = {"triggerHappy": True}
|
|
162
|
+
|
|
163
|
+
# Define the logo.
|
|
164
|
+
image_file = os.path.join(os.path.dirname(__file__), "logo.png")
|
|
165
|
+
logo = utils.encodeImage(image_file)
|
|
166
|
+
|
|
167
|
+
# Put the form in the outputs.
|
|
168
|
+
payload = {
|
|
169
|
+
"form": form_obj,
|
|
170
|
+
"navbar": {
|
|
171
|
+
"logo": logo,
|
|
172
|
+
"title": "Plot examples",
|
|
173
|
+
"subtitle": "<small>Simian Demo</small>",
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return payload
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@register_type("Bar")
|
|
181
|
+
def show_bar(plot_obj: component.Component) -> None:
|
|
182
|
+
"""Bar plot showing GAP minder data with Plotly visualization."""
|
|
183
|
+
plot_obj.figure = px.bar(
|
|
184
|
+
GAP_MINDER.query("year==2007"),
|
|
185
|
+
x="continent",
|
|
186
|
+
y="pop",
|
|
187
|
+
color="country",
|
|
188
|
+
title="Populations in 2007",
|
|
189
|
+
text="country",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@register_type("Box plot")
|
|
194
|
+
def show_box_plot(plot_obj: component.Component) -> None:
|
|
195
|
+
"""Box plot showing restaurant bills with Plotly visualization."""
|
|
196
|
+
plot_obj.figure = px.box(
|
|
197
|
+
DATA_TIPS,
|
|
198
|
+
x="day",
|
|
199
|
+
y="total_bill",
|
|
200
|
+
color="time",
|
|
201
|
+
notched=True,
|
|
202
|
+
title="Box plot of total bill",
|
|
203
|
+
hover_data=["day"],
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@register_type("CDF")
|
|
208
|
+
def show_ecdf(plot_obj: component.Component) -> None:
|
|
209
|
+
"""Empirical Cumulative Distribution Function of restaurant bills Plotly visualization."""
|
|
210
|
+
plot_obj.figure = px.ecdf(
|
|
211
|
+
DATA_TIPS,
|
|
212
|
+
x="total_bill",
|
|
213
|
+
title="Restaurant bills",
|
|
214
|
+
marginal="histogram",
|
|
215
|
+
color="time",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@register_type("Contour")
|
|
220
|
+
def show_contour(plot_obj: component.Component) -> None:
|
|
221
|
+
"""Contour plot with Plotly visualization."""
|
|
222
|
+
plot_obj.figure = go.Figure(
|
|
223
|
+
data=go.Contour(
|
|
224
|
+
z=[
|
|
225
|
+
[2, 4, 7, 12, 13, 14, 15, 16],
|
|
226
|
+
[3, 1, 6, 11, 12, 13, 16, 17],
|
|
227
|
+
[4, 2, 7, 7, 11, 14, 17, 18],
|
|
228
|
+
[5, 3, 8, 8, 13, 15, 18, 19],
|
|
229
|
+
[7, 4, 10, 9, 16, 18, 20, 19],
|
|
230
|
+
[9, 10, 5, 27, 23, 21, 21, 21],
|
|
231
|
+
[11, 14, 17, 26, 25, 24, 23, 22],
|
|
232
|
+
],
|
|
233
|
+
contours={
|
|
234
|
+
"coloring": "heatmap",
|
|
235
|
+
"showlabels": True, # show labels on contours
|
|
236
|
+
"labelfont": { # label font properties
|
|
237
|
+
"size": 12,
|
|
238
|
+
"color": "white",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
),
|
|
242
|
+
layout={"title": {"text": "Contour example"}},
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@register_type("Draw shapes")
|
|
247
|
+
def show_drawing(plot_obj: component.Component) -> None:
|
|
248
|
+
"""Line plot showing stock market data with Plotly visualization.
|
|
249
|
+
|
|
250
|
+
Shapes can be drawn on and erased from the plot with the options in the toolbar.
|
|
251
|
+
"""
|
|
252
|
+
plot_obj.figure = px.line(
|
|
253
|
+
STOCKS,
|
|
254
|
+
x="date",
|
|
255
|
+
y=STOCKS.columns,
|
|
256
|
+
hover_data={"date": "|%B %d, %Y"},
|
|
257
|
+
title="Stock market data",
|
|
258
|
+
)
|
|
259
|
+
plot_obj.figure.update_layout(
|
|
260
|
+
title_text="Draw shapes example",
|
|
261
|
+
dragmode="drawopenpath",
|
|
262
|
+
newshape_line_color="cyan",
|
|
263
|
+
)
|
|
264
|
+
plot_obj.config.update(
|
|
265
|
+
{
|
|
266
|
+
"modeBarButtonsToAdd": [
|
|
267
|
+
"drawline",
|
|
268
|
+
"drawopenpath",
|
|
269
|
+
"drawclosedpath",
|
|
270
|
+
"drawcircle",
|
|
271
|
+
"drawrect",
|
|
272
|
+
"eraseshape",
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@register_type("Heat map")
|
|
279
|
+
def show_heatmap(plot_obj: component.Component) -> None:
|
|
280
|
+
"""Heat map overlain with data points showing restaurant bills with Plotly visualization.
|
|
281
|
+
|
|
282
|
+
The histograms on the axes are easy to add options.
|
|
283
|
+
"""
|
|
284
|
+
plot_obj.figure = px.density_heatmap(
|
|
285
|
+
DATA_TIPS,
|
|
286
|
+
x="total_bill",
|
|
287
|
+
y="tip",
|
|
288
|
+
marginal_x="histogram",
|
|
289
|
+
marginal_y="histogram",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Add markers to measurement locations.
|
|
293
|
+
plot_obj.figure.add_trace(
|
|
294
|
+
go.Scatter(
|
|
295
|
+
x=DATA_TIPS["total_bill"],
|
|
296
|
+
y=DATA_TIPS["tip"],
|
|
297
|
+
mode="markers",
|
|
298
|
+
showlegend=False,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@register_type("Line")
|
|
304
|
+
def show_line(plot_obj: component.Component) -> None:
|
|
305
|
+
"""Line plot showing stock market data with Plotly visualization.
|
|
306
|
+
|
|
307
|
+
Note that in the bottom subplot the date range of the main plot can be changed.
|
|
308
|
+
"""
|
|
309
|
+
plot_obj.figure = px.line(
|
|
310
|
+
STOCKS,
|
|
311
|
+
x="date",
|
|
312
|
+
y=STOCKS.columns,
|
|
313
|
+
hover_data={"date": "|%B %d, %Y"},
|
|
314
|
+
title="Stock market data",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
plot_obj.figure.update_xaxes(
|
|
318
|
+
dtick="M1",
|
|
319
|
+
tickformat="%b\n%Y",
|
|
320
|
+
ticklabelmode="period",
|
|
321
|
+
rangeslider_visible=True,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@register_type("Map")
|
|
326
|
+
def show_map(plot_obj: component.Component) -> None:
|
|
327
|
+
"""Map showing GAP minder data with Plotly visualization."""
|
|
328
|
+
plot_obj.figure = px.scatter_geo(
|
|
329
|
+
GAP_MINDER.query("year==2007"),
|
|
330
|
+
locations="iso_alpha",
|
|
331
|
+
color="gdpPercap",
|
|
332
|
+
hover_name="country",
|
|
333
|
+
size="pop",
|
|
334
|
+
size_max=60,
|
|
335
|
+
projection="natural earth",
|
|
336
|
+
title="GDP and population in 2007",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@register_type("Mesh")
|
|
341
|
+
def show_mesh(plot_obj: component.Component) -> None:
|
|
342
|
+
"""Mesh grid showing random data with Plotly visualization."""
|
|
343
|
+
nr_points = 25
|
|
344
|
+
plot_obj.figure = go.Figure(
|
|
345
|
+
data=[
|
|
346
|
+
go.Mesh3d(
|
|
347
|
+
x=(30 * np.random.randn(nr_points)),
|
|
348
|
+
y=(25 * np.random.randn(nr_points)),
|
|
349
|
+
z=(30 * np.random.randn(nr_points)),
|
|
350
|
+
opacity=0.5,
|
|
351
|
+
)
|
|
352
|
+
]
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
grid_defaults = {
|
|
356
|
+
"gridcolor": "white",
|
|
357
|
+
"showbackground": True,
|
|
358
|
+
"zerolinecolor": "white",
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# xaxis.backgroundcolor is used to set background color
|
|
362
|
+
plot_obj.figure.update_layout(
|
|
363
|
+
scene={
|
|
364
|
+
"xaxis": {"backgroundcolor": "rgb(200, 200, 230)", **grid_defaults},
|
|
365
|
+
"yaxis": {"backgroundcolor": "rgb(230, 200, 230)", **grid_defaults},
|
|
366
|
+
"zaxis": {"backgroundcolor": "rgb(230, 230, 200)", **grid_defaults},
|
|
367
|
+
},
|
|
368
|
+
margin={"r": 10, "l": 10, "b": 10, "t": 10},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@register_type("Pie")
|
|
373
|
+
def show_pie(plot_obj: component.Component) -> None:
|
|
374
|
+
"""Pie chart showing GAP minder data over the years with Plotly visualization.
|
|
375
|
+
|
|
376
|
+
The Plotly express `facet` options allow for easier subplot generation.
|
|
377
|
+
"""
|
|
378
|
+
plot_obj.figure = px.pie(
|
|
379
|
+
GAP_MINDER,
|
|
380
|
+
values="pop",
|
|
381
|
+
names="country",
|
|
382
|
+
facet_col="year",
|
|
383
|
+
facet_col_wrap=4,
|
|
384
|
+
)
|
|
385
|
+
plot_obj.figure.update_traces(textposition="inside")
|
|
386
|
+
plot_obj.figure.update_layout(
|
|
387
|
+
uniformtext_minsize=12,
|
|
388
|
+
uniformtext_mode="hide",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@register_type("Scatter")
|
|
393
|
+
def show_scatter(plot_obj: component.Component) -> None:
|
|
394
|
+
"""Scatter plot showing GAP minder data with Plotly visualization."""
|
|
395
|
+
plot_obj.figure = px.scatter(
|
|
396
|
+
GAP_MINDER.query("year==2007"),
|
|
397
|
+
x="gdpPercap",
|
|
398
|
+
log_x=True,
|
|
399
|
+
y="lifeExp",
|
|
400
|
+
symbol="year",
|
|
401
|
+
size="pop",
|
|
402
|
+
size_max=60,
|
|
403
|
+
color="continent",
|
|
404
|
+
hover_name="country",
|
|
405
|
+
title="GDP and life expectancy in 2007",
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
@register_type("Scatter 3D")
|
|
410
|
+
def show_scatter3d(plot_obj: component.Component) -> None:
|
|
411
|
+
"""3D Scatter plot showing GAP minder data with Plotly visualization.
|
|
412
|
+
|
|
413
|
+
Pan, zoom and rotate the plot to inspect its contents.
|
|
414
|
+
"""
|
|
415
|
+
# https://plotly.com/python/line-and-scatter/
|
|
416
|
+
plot_obj.figure = px.scatter_3d(
|
|
417
|
+
GAP_MINDER,
|
|
418
|
+
x="year",
|
|
419
|
+
y="gdpPercap",
|
|
420
|
+
log_y=True,
|
|
421
|
+
z="lifeExp",
|
|
422
|
+
size="pop",
|
|
423
|
+
size_max=60,
|
|
424
|
+
color="continent",
|
|
425
|
+
hover_name="country",
|
|
426
|
+
title="GDP and life expectancy",
|
|
427
|
+
)
|
|
428
|
+
plot_obj.figure.update_layout(scene_aspectmode="auto")
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": [
|
|
3
|
+
{
|
|
4
|
+
"title": "",
|
|
5
|
+
"collapsible": true,
|
|
6
|
+
"key": "descriptionPanel",
|
|
7
|
+
"type": "panel",
|
|
8
|
+
"label": "Description",
|
|
9
|
+
"input": false,
|
|
10
|
+
"tableView": false,
|
|
11
|
+
"components": [
|
|
12
|
+
{
|
|
13
|
+
"label": "Description",
|
|
14
|
+
"className": "lead",
|
|
15
|
+
"attrs": [
|
|
16
|
+
{
|
|
17
|
+
"attr": "",
|
|
18
|
+
"value": ""
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"content": "This example shows two Plotly components rendering life expectancy and population from the GapMinder dataset that comes with Plotly Express.\nFor both treemaps, the year that is shown can be selected with a slider. There is a small difference though.\n<br><br>\nThe slider on the left has its <code>triggerHappy</code> property set and when its value has changed,\nit makes a call to the backend to fetch the plot data for the selected year.\nA <code>debounceTime</code> of 1 second is set to ensure that the callback is not triggered at each value the slider passes.\n<br><br>\nThe treemap on the right uses a hidden field that contains the data for all years as its <code>defaultValue</code>.\nThe Plotly component uses <code>calculateValue</code> to call a JavaScript function that gets the data for the selected year from the hidden field.",
|
|
22
|
+
"refreshOnChange": false,
|
|
23
|
+
"key": "description",
|
|
24
|
+
"type": "htmlelement",
|
|
25
|
+
"input": false,
|
|
26
|
+
"tableView": false
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"collapsed": false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"label": "Columns",
|
|
33
|
+
"columns": [
|
|
34
|
+
{
|
|
35
|
+
"components": [
|
|
36
|
+
{
|
|
37
|
+
"label": "Treemap",
|
|
38
|
+
"aspectRatio": 1.777,
|
|
39
|
+
"hideLabel": true,
|
|
40
|
+
"tableView": false,
|
|
41
|
+
"key": "treemap",
|
|
42
|
+
"type": "customplotly",
|
|
43
|
+
"input": true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"label": "Year",
|
|
47
|
+
"tableView": false,
|
|
48
|
+
"defaultValue": 2007,
|
|
49
|
+
"min": 1952,
|
|
50
|
+
"max": 2007,
|
|
51
|
+
"step": 5,
|
|
52
|
+
"key": "year",
|
|
53
|
+
"type": "customslider",
|
|
54
|
+
"input": true
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"width": 6,
|
|
58
|
+
"offset": 0,
|
|
59
|
+
"push": 0,
|
|
60
|
+
"pull": 0,
|
|
61
|
+
"size": "md",
|
|
62
|
+
"currentWidth": 6
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"components": [
|
|
66
|
+
{
|
|
67
|
+
"label": "Treemap",
|
|
68
|
+
"aspectRatio": 1.777,
|
|
69
|
+
"hideLabel": true,
|
|
70
|
+
"tableView": false,
|
|
71
|
+
"key": "treemapPreloaded",
|
|
72
|
+
"type": "customplotly",
|
|
73
|
+
"input": true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"label": "Year",
|
|
77
|
+
"tableView": false,
|
|
78
|
+
"defaultValue": 2007,
|
|
79
|
+
"min": 1952,
|
|
80
|
+
"max": 2007,
|
|
81
|
+
"step": 5,
|
|
82
|
+
"key": "yearPreloaded",
|
|
83
|
+
"type": "customslider",
|
|
84
|
+
"input": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"label": "Preloaded Data",
|
|
88
|
+
"key": "preloadedData",
|
|
89
|
+
"type": "hidden",
|
|
90
|
+
"input": true,
|
|
91
|
+
"tableView": false
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"width": 6,
|
|
95
|
+
"offset": 0,
|
|
96
|
+
"push": 0,
|
|
97
|
+
"pull": 0,
|
|
98
|
+
"size": "md",
|
|
99
|
+
"currentWidth": 6
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"key": "columns",
|
|
103
|
+
"type": "columns",
|
|
104
|
+
"input": false,
|
|
105
|
+
"tableView": false
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Treemap example.
|
|
2
|
+
|
|
3
|
+
Uses the Plotly component to create a treemap https://plotly.com/javascript/reference/treemap/.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import plotly.express as px
|
|
9
|
+
from simian.gui import Form, component, utils
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
from simian.local import run
|
|
13
|
+
|
|
14
|
+
run("simian.examples.treemap", window_title="Treemap Example")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Load the gapminder dataset.
|
|
18
|
+
GAPMINDER_DATA = px.data.gapminder()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def gui_init(_meta_data: dict) -> dict:
|
|
22
|
+
"""Initializes the form.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
_meta_data (dict): Meta data.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
dict: Payload with form definition.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# Register component initializers.
|
|
32
|
+
Form.componentInitializer(
|
|
33
|
+
treemap=_init_treemap,
|
|
34
|
+
year=_init_year,
|
|
35
|
+
treemapPreloaded=_init_treemap_preloaded,
|
|
36
|
+
preloadedData=_init_preloaded_data,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Create the form and load the json builder into it.
|
|
40
|
+
form = Form(from_file=__file__)
|
|
41
|
+
|
|
42
|
+
# Add the form to the payload and set the title on the navbar.
|
|
43
|
+
return {
|
|
44
|
+
"form": form,
|
|
45
|
+
"navbar": {"title": "World population and life expectancy"},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _init_treemap(comp: component.Plotly):
|
|
50
|
+
# Initialize the treemap with data from the year 2007.
|
|
51
|
+
comp.figure = _select_year(2007)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _init_year(comp: component.Slider):
|
|
55
|
+
# Make the slider emit events one second after each change. Only the last value is used.
|
|
56
|
+
comp.addCustomProperty("triggerHappy", "YearSelected")
|
|
57
|
+
comp.addCustomProperty("debounceTime", 1000)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _init_treemap_preloaded(comp: component.Plotly):
|
|
61
|
+
# Set the calculateValue to call the JavaScript function that selects the data for the year.
|
|
62
|
+
comp.setCalculateValue("value = fillTreemap(data.preloadedData, data.yearPreloaded);")
|
|
63
|
+
comp.figure = _select_year(2007)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _init_preloaded_data(comp: component.Hidden):
|
|
67
|
+
# Aggregate data for continent and world.
|
|
68
|
+
cont_data = _aggregate_gapminder(["continent", "year"])
|
|
69
|
+
world_data = _aggregate_gapminder(["year"])
|
|
70
|
+
full_data = pd.concat([GAPMINDER_DATA, cont_data, world_data])
|
|
71
|
+
full_data["world"] = "world"
|
|
72
|
+
|
|
73
|
+
# Get the full identifier of the row: "world/<continent>/<country>". Parts that do not exist are
|
|
74
|
+
# not added.
|
|
75
|
+
row_ids = full_data[["world", "continent", "country"]].apply(
|
|
76
|
+
lambda r: r.str.cat(sep="/"), axis=1
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Split the row identifiers in their own name and the parent.
|
|
80
|
+
parents, _, own_name = zip(*(id.rpartition("/") for id in row_ids))
|
|
81
|
+
|
|
82
|
+
# Set the default value of the hidden component.
|
|
83
|
+
comp.defaultValue = {
|
|
84
|
+
"countries": own_name,
|
|
85
|
+
"parents": parents,
|
|
86
|
+
"ids": list(row_ids),
|
|
87
|
+
"lifeExp": list(full_data["lifeExp"]),
|
|
88
|
+
"pop": list(full_data["pop"]),
|
|
89
|
+
"years": list(full_data["year"]),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def gui_event(meta_data: dict, payload: dict) -> dict:
|
|
94
|
+
"""Handle events of the application.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
meta_data (dict): Meta data.
|
|
98
|
+
payload (dict): Payload including submission data from the user interface.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
dict: Updated payload data.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# Register event handlers.
|
|
105
|
+
Form.eventHandler(YearSelected=_update_year)
|
|
106
|
+
|
|
107
|
+
# Handle the event using the event data in the payload.
|
|
108
|
+
callback = utils.getEventFunction(meta_data, payload)
|
|
109
|
+
return callback(meta_data, payload)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _update_year(_meta_data: dict, payload: dict) -> dict:
|
|
113
|
+
# Get the year and treemap.
|
|
114
|
+
year, _ = utils.getSubmissionData(payload, "year")
|
|
115
|
+
treemap, _ = utils.getSubmissionData(payload, "treemap")
|
|
116
|
+
|
|
117
|
+
# Update the layout and plot data with the data of the selected year.
|
|
118
|
+
treemap.figure = _select_year(year)
|
|
119
|
+
|
|
120
|
+
# Set the treemap value.
|
|
121
|
+
utils.setSubmissionData(payload, "treemap", treemap)
|
|
122
|
+
|
|
123
|
+
return payload
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _select_year(year: int):
|
|
127
|
+
# Get the data for the year.
|
|
128
|
+
yearData = GAPMINDER_DATA.query(f"year == {year}")
|
|
129
|
+
|
|
130
|
+
# Update the treemap data and layout.
|
|
131
|
+
fig = px.treemap(
|
|
132
|
+
yearData,
|
|
133
|
+
path=[px.Constant("world"), "continent", "country"],
|
|
134
|
+
values="pop",
|
|
135
|
+
color="lifeExp",
|
|
136
|
+
color_continuous_scale="RdBu",
|
|
137
|
+
color_continuous_midpoint=np.average(yearData["lifeExp"], weights=yearData["pop"]),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
fig.update_layout(margin=dict(t=50, l=25, r=25, b=25))
|
|
141
|
+
|
|
142
|
+
return fig
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _aggregate_gapminder(grouping: str) -> pd.DataFrame:
|
|
146
|
+
"""Aggregate population and life expectancy data."""
|
|
147
|
+
grouped_gaps = GAPMINDER_DATA.groupby(grouping)
|
|
148
|
+
|
|
149
|
+
# Total population of a group is the sum of its parts.
|
|
150
|
+
pop = grouped_gaps["pop"].sum()
|
|
151
|
+
|
|
152
|
+
# Total life expectancy of a group is the mean weighted with the population of its parts.
|
|
153
|
+
life_exp = grouped_gaps.apply(lambda x: np.average(x["lifeExp"], weights=x["pop"]))
|
|
154
|
+
life_exp.name = "lifeExp"
|
|
155
|
+
|
|
156
|
+
# Join the tables.
|
|
157
|
+
return pd.concat([pop, life_exp], axis=1).reset_index()
|