Ludvig commited on
Commit
8dd9b7b
β€’
1 Parent(s): 7f486ad

Fixes resetting. Refactors and adds templates. Use tile borders in sum settings.

Browse files
README.md CHANGED
@@ -14,6 +14,7 @@ Streamlit application for plotting a confusion matrix.
14
 
15
 
16
  ## TODOs
 
17
  - ggsave only uses DPI for scaling? We would expect output files to have the given DPI?
18
  - Allow svg, pdf?
19
  - Add full reset button (empty cache on different files) - callback?
 
14
 
15
 
16
  ## TODOs
17
+ - Add option to preview plot in black and white (for printed papers)
18
  - ggsave only uses DPI for scaling? We would expect output files to have the given DPI?
19
  - Allow svg, pdf?
20
  - Add full reset button (empty cache on different files) - callback?
app.py CHANGED
@@ -60,6 +60,7 @@ def input_choice_callback():
60
  """
61
  st.session_state["step"] = 0
62
  st.session_state["input_type"] = None
 
63
 
64
  to_delete = ["classes", "count_data", "uploaded_design_settings"]
65
  for key in to_delete:
@@ -74,6 +75,9 @@ def input_choice_callback():
74
  if conf_mat_path.exists():
75
  conf_mat_path.unlink()
76
 
 
 
 
77
 
78
  # Text
79
  intro_text()
@@ -84,7 +88,9 @@ if st.session_state.get("step") is None:
84
  st.session_state["step"] = 0
85
 
86
  input_choice = st.radio(
87
- label="Input",
 
 
88
  options=["Upload predictions", "Upload counts", "Generate", "Enter counts"],
89
  index=0,
90
  horizontal=True,
@@ -358,7 +364,7 @@ if st.session_state["step"] >= 2:
358
 
359
  # Section for specifying design settings
360
 
361
- design_settings, design_ready, selected_classes = design_section(
362
  num_classes=num_classes,
363
  design_settings_store_path=design_settings_store_path,
364
  )
@@ -367,7 +373,7 @@ if st.session_state["step"] >= 2:
367
  # for user to fix issues
368
  if st.session_state["step"] >= 3 and design_ready:
369
  DownloadHeader.centered_json_download(
370
- data=design_settings,
371
  file_name="design_settings.json",
372
  label="Download design settings",
373
  help="Download the design settings to allow reusing settings in future plots.",
 
60
  """
61
  st.session_state["step"] = 0
62
  st.session_state["input_type"] = None
63
+ st.session_state["num_resets"] = 0
64
 
65
  to_delete = ["classes", "count_data", "uploaded_design_settings"]
66
  for key in to_delete:
 
75
  if conf_mat_path.exists():
76
  conf_mat_path.unlink()
77
 
78
+ # Allows design settings to show
79
+ st.session_state["design_reset_mode"] = False
80
+
81
 
82
  # Text
83
  intro_text()
 
88
  st.session_state["step"] = 0
89
 
90
  input_choice = st.radio(
91
+ label="Input Choice",
92
+ label_visibility="hidden",
93
+ key="InputChoice",
94
  options=["Upload predictions", "Upload counts", "Generate", "Enter counts"],
95
  index=0,
96
  horizontal=True,
 
364
 
365
  # Section for specifying design settings
366
 
367
+ design_ready, selected_classes = design_section(
368
  num_classes=num_classes,
369
  design_settings_store_path=design_settings_store_path,
370
  )
 
373
  # for user to fix issues
374
  if st.session_state["step"] >= 3 and design_ready:
375
  DownloadHeader.centered_json_download(
376
+ data=st.session_state["selected_design_settings"],
377
  file_name="design_settings.json",
378
  label="Download design settings",
379
  help="Download the design settings to allow reusing settings in future plots.",
design.py CHANGED
@@ -6,6 +6,7 @@ from PIL import Image
6
  from text_sections import (
7
  design_text,
8
  )
 
9
 
10
 
11
  def _add_select_box(
@@ -31,110 +32,99 @@ def _add_select_box(
31
  )
32
 
33
 
34
- templates = {
35
- "grey_1": {
36
- "Grey 2-class": {
37
- "settings": "design_settings.grey_nc2_1.1.json",
38
- "image": "grey_nc2_1.1.jpg",
39
- },
40
- "Grey 3-class": {
41
- "settings": "design_settings.grey_nc3_1.1.json",
42
- "image": "grey_nc3_1.1.jpg",
43
- },
44
- "Grey 2-class with sums": {
45
- "settings": "design_settings.grey_nc2_sums_1.1.json",
46
- "image": "grey_nc2_sums_1.1.jpg",
47
- },
48
- }
49
- }
50
-
51
- template_selections = {}
52
 
53
 
54
- def design_section(
55
- num_classes,
56
- design_settings_store_path,
57
- ):
58
- output = {}
59
-
60
  def reset_output_callback():
61
- output.clear()
62
  if "uploaded_design_settings" in st.session_state:
63
  del st.session_state["uploaded_design_settings"]
 
 
 
64
 
65
- with st.form(key="settings_upload_form"):
66
- design_text()
67
-
68
- with st.expander("Templates"):
69
- for temp_collection_name, temp_collection in templates.items():
70
- cols = st.columns(3)
71
- for i, (temp_name, template) in enumerate(temp_collection.items()):
72
- temp_image = Image.open(f"templates/{template['image']}")
73
- # temp_settings =
74
- with cols[i % 3]:
75
- st.image(
76
- temp_image,
77
- # caption="Confusion Matrix",
78
- clamp=False,
79
- channels="RGB",
80
- output_format="auto",
81
- )
82
- template_selections[temp_collection_name] = st.radio(
83
- "Select template",
84
- index=0,
85
- options=["--"] + list(temp_collection.keys()),
86
- horizontal=True,
87
- )
88
-
89
- st.markdown("---")
90
-
91
- with st.expander("Upload settings"):
92
- uploaded_settings_path = st.file_uploader(
93
- "Upload design settings", type=["json"]
94
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- if st.form_submit_button(
97
- label="Apply settings", on_click=reset_output_callback
98
- ):
99
- selections = [
100
- (temp_coll_name, selection)
101
- for temp_coll_name, selection in template_selections.items()
102
- if selection != "--"
103
- ]
104
- if len(selections) > 1:
105
- st.warning("More than one template was selected. The first is applied.")
106
- template_selected = "--"
107
- if selections:
108
- selection_temp_coll_name, selection = selections[0]
109
- template_selected = selection
110
-
111
  if uploaded_settings_path is not None:
112
- if template_selected != "--":
113
- st.warning(
114
- "When both selecting a template and uploading settings, "
115
- "the uploaded settings are used."
116
- )
117
  st.session_state["uploaded_design_settings"] = json.load(
118
  uploaded_settings_path
119
  )
120
- elif template_selected != "--":
121
- with open(
122
- "templates/"
123
- + templates[selection_temp_coll_name][template_selected][
124
- "settings"
125
- ],
126
- "r",
127
- ) as jfile:
128
- st.session_state["uploaded_design_settings"] = json.load(jfile)
129
- else:
130
- st.warning(
131
- "No settings were uploaded and no templates were selected. Both are *optional*."
132
- )
133
 
134
- col1, col2, col1 = st.columns([5, 2.5, 5])
135
  with col2:
136
  st.button("Reset design", on_click=reset_output_callback)
137
 
 
 
 
 
 
 
 
 
 
 
 
138
  def get_uploaded_setting(key, default, type_=None, options=None):
139
  if (
140
  "uploaded_design_settings" in st.session_state
@@ -156,475 +146,589 @@ def design_section(
156
  return out
157
  return default
158
 
159
- with st.form(key="settings_form"):
160
- col1, col2, col3 = st.columns([4, 2, 2])
161
- with col1:
162
- selected_classes = st.multiselect(
163
- "Select classes (min=2, order is respected)",
164
- options=st.session_state["classes"],
165
- default=st.session_state["classes"],
166
- help="Select the classes to create the confusion matrix for. "
167
- "Any observation with either a target or prediction "
168
- "of another class is excluded.",
169
- )
170
- # Reverse by default
171
- selected_classes.reverse()
172
- with col2:
173
- st.write(" ")
174
- st.write(" ")
175
- reverse_class_order = st.checkbox(
176
- "Reverse order", value=False, help="Reverse the order of the classes."
177
- )
178
-
179
- # Color palette
180
- col1, col2, col3, col4 = st.columns([4, 4, 2, 2])
181
- with col1:
182
- output["palette"] = _add_select_box(
183
- key="palette",
184
- label="Color Palette",
185
- default="Blues",
186
- options=["Blues", "Greens", "Oranges", "Greys", "Purples", "Reds"],
187
- get_setting_fn=get_uploaded_setting,
188
- type_=str,
189
- help="Color of the tiles. Select a preset color palette or create a custom gradient. ",
190
- )
191
- with col2:
192
- st.write("")
193
- st.write(" ")
194
- output["palette_use_custom"] = st.checkbox(
195
- "Custom gradient",
196
- value=False,
197
- help="Use a custom gradient for coloring the tiles.",
198
- )
199
- with col3:
200
- output["palette_custom_low"] = st.color_picker("Low color", value="#B1F9E8")
201
- with col4:
202
- output["palette_custom_high"] = st.color_picker(
203
- "High color", value="#239895"
204
- )
205
 
206
- # Ask for output parameters
207
- col1, col2, col3 = st.columns(3)
208
- with col1:
209
- output["width"] = st.number_input(
210
- "Width (px)",
211
- value=get_uploaded_setting(
212
- key="width", default=1200 + 100 * (num_classes - 2), type_=int
213
- ),
214
- step=50,
215
- )
216
- with col2:
217
- output["height"] = st.number_input(
218
- "Height (px)",
219
- value=get_uploaded_setting(
220
- key="width", default=1200 + 100 * (num_classes - 2), type_=int
221
- ),
222
- step=50,
223
- )
224
- with col3:
225
- output["dpi"] = st.number_input(
226
- "DPI (scaling)",
227
- value=get_uploaded_setting(key="dpi", default=320, type_=int),
228
- step=10,
229
- help="While the output file *currently* won't have this DPI, "
230
- "the DPI setting affects scaling of elements. ",
231
- )
232
-
233
- st.write(" ") # Slightly bigger gap between the two sections
234
- col1, col2, col3 = st.columns(3)
235
- with col1:
236
- output["show_counts"] = st.checkbox(
237
- "Show Counts",
238
- value=get_uploaded_setting(key="show_counts", default=True, type_=bool),
239
- )
240
- with col2:
241
- output["show_normalized"] = st.checkbox(
242
- "Show Normalized (%)",
243
- value=get_uploaded_setting(
244
- key="show_normalized", default=True, type_=bool
245
- ),
246
- )
247
- with col3:
248
- output["show_sums"] = st.checkbox(
249
- "Show Sum Tiles",
250
- value=get_uploaded_setting(key="show_sums", default=False, type_=bool),
251
- help="Show extra row and column with the "
252
- "totals for that row/column.",
253
- )
254
-
255
- st.markdown("""---""")
256
- st.markdown("**Advanced**:")
257
-
258
- with st.expander("Labels"):
259
- col1, col2 = st.columns(2)
260
  with col1:
261
- output["x_label"] = st.text_input(
262
- "x-axis",
263
- value=get_uploaded_setting(
264
- key="x_label", default="True Class", type_=str
265
- ),
 
 
266
  )
 
 
267
  with col2:
268
- output["y_label"] = st.text_input(
269
- "y-axis",
270
- value=get_uploaded_setting(
271
- key="y_label", default="Predicted Class", type_=str
272
- ),
 
273
  )
274
 
275
- st.markdown("---")
276
- col1, col2 = st.columns(2)
277
  with col1:
278
- output["title_label"] = st.text_input(
279
- "Title",
280
- value=get_uploaded_setting(
281
- key="title_label", default="", type_=str
282
- ),
 
 
 
 
 
 
 
 
 
 
 
 
283
  )
284
  with col2:
285
- output["caption_label"] = st.text_input(
286
- "Caption",
287
- value=get_uploaded_setting(
288
- key="caption_label", default="", type_=str
289
- ),
 
 
 
290
  )
291
- st.info(
292
- "Note: When adding a title or caption, "
293
- "you may need to adjust the height and "
294
- "width of the plot as well."
295
- )
296
-
297
- with st.expander("Elements"):
298
- col1, col2 = st.columns(2)
 
 
 
299
  with col1:
300
- output["rotate_y_text"] = st.checkbox(
301
- "Rotate y-axis text",
302
  value=get_uploaded_setting(
303
- key="rotate_y_text", default=True, type_=bool
304
  ),
 
305
  )
306
- output["place_x_axis_above"] = st.checkbox(
307
- "Place x-axis on top",
308
- value=get_uploaded_setting(
309
- key="place_x_axis_above", default=True, type_=bool
310
- ),
311
- )
312
- output["counts_on_top"] = st.checkbox(
313
- "Counts on top",
314
  value=get_uploaded_setting(
315
- key="counts_on_top", default=False, type_=bool
 
 
316
  ),
317
- help="Whether to switch the positions of the counts and normalized counts (%). "
318
- "The counts become the big centralized numbers and the "
319
- "normalized counts go below with a smaller font size.",
320
  )
321
- with col2:
322
- output["num_digits"] = st.number_input(
323
- "Digits",
324
- value=get_uploaded_setting(key="num_digits", default=2, type_=int),
325
- help="Number of digits to round percentages to.",
 
 
326
  )
327
 
328
- st.markdown("""---""")
329
-
330
- col1, col2 = st.columns(2)
331
  with col1:
332
- st.write("Row and column percentages:")
333
- output["show_row_percentages"] = st.checkbox(
334
- "Show row percentages",
 
335
  value=get_uploaded_setting(
336
- key="show_row_percentages", default=num_classes < 6, type_=bool
337
  ),
338
  )
339
- output["show_col_percentages"] = st.checkbox(
340
- "Show column percentages",
341
- value=get_uploaded_setting(
342
- key="show_col_percentages", default=num_classes < 6, type_=bool
343
- ),
344
- )
345
- output["show_arrows"] = st.checkbox(
346
- "Show arrows",
347
  value=get_uploaded_setting(
348
- key="show_arrows", default=True, type_=bool
349
  ),
350
  )
351
- output["diag_percentages_only"] = st.checkbox(
352
- "Diagonal row/column percentages only",
 
353
  value=get_uploaded_setting(
354
- key="diag_percentages_only", default=False, type_=bool
355
  ),
 
 
356
  )
357
- with col2:
358
- output["arrow_size"] = (
359
- st.slider(
360
- "Arrow size",
 
 
 
 
 
 
 
361
  value=get_uploaded_setting(
362
- key="arrow_size", default=0.048, type_=float
363
- ) * 10,
364
- min_value=0.03 * 10,
365
- max_value=0.06 * 10,
366
- step=0.001 * 10,
367
  )
368
- / 10
369
- )
370
- output["arrow_nudge_from_text"] = (
371
- st.slider(
372
- "Arrow nudge from text",
373
  value=get_uploaded_setting(
374
- key="arrow_nudge_from_text", default=0.065, type_=float
375
- ) * 10,
376
- min_value=0.00,
377
- max_value=0.1 * 10,
378
- step=0.001 * 10,
379
  )
380
- / 10
381
- )
382
 
383
- with st.expander("Tiles"):
384
- col1, col2 = st.columns(2)
385
- with col1:
386
- output["intensity_by"] = _add_select_box(
387
- key="intensity_by",
388
- label="Intensity based on",
389
- default="Counts",
390
- options=[
391
- "Counts",
392
- "Normalized (%)",
393
- "Log Counts",
394
- "Log2 Counts",
395
- "Log10 Counts",
396
- "Arcsinh Counts",
397
- ],
398
- get_setting_fn=get_uploaded_setting,
399
- type_=str,
400
- )
401
- with col2:
402
- output["darkness"] = st.slider(
403
- "Darkness",
404
- min_value=0.0,
405
- max_value=1.0,
406
- value=get_uploaded_setting(
407
- key="darkness", default=0.8, type_=float
408
- ),
409
- step=0.01,
410
- help="How dark the darkest colors should be, between 0 and 1, where 1 is darkest.",
411
  )
412
 
413
- st.markdown("""---""")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
- output["show_tile_border"] = st.checkbox(
416
- "Add tile borders",
417
- value=get_uploaded_setting(
418
- key="show_tile_border", default=False, type_=bool
419
- ),
420
- )
421
 
422
- col1, col2, col3 = st.columns(3)
423
- with col1:
424
- output["tile_border_color"] = st.color_picker(
425
- "Border color",
426
- value=get_uploaded_setting(
427
- key="tile_border_color", default="#000000", type_=str
428
- ),
429
- )
430
- with col2:
431
- output["tile_border_size"] = st.slider(
432
- "Border size",
433
- min_value=0.0,
434
- max_value=3.0,
435
- value=get_uploaded_setting(
436
- key="tile_border_size", default=0.1, type_=float
437
- ),
438
- step=0.01,
439
- )
440
- with col3:
441
- output["tile_border_linetype"] = _add_select_box(
442
- key="tile_border_linetype",
443
- label="Border linetype",
444
- default="solid",
445
- options=[
446
- "solid",
447
- "dashed",
448
- "dotted",
449
- "dotdash",
450
- "longdash",
451
- "twodash",
452
- ],
453
- get_setting_fn=get_uploaded_setting,
454
- type_=str,
455
- )
456
-
457
- st.markdown("""---""")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
 
459
- st.write("Sum tile settings:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
- col1, col2 = st.columns(2)
462
- with col1:
463
- output["sum_tile_palette"] = _add_select_box(
464
- key="sum_tile_palette",
465
- label="Color Palette",
466
- default="Greens",
467
- options=["Greens", "Oranges", "Greys", "Purples", "Reds", "Blues"],
468
- get_setting_fn=get_uploaded_setting,
469
- type_=str,
470
- )
471
 
472
- with col2:
473
- output["sum_tile_label"] = st.text_input(
474
- "Label",
 
475
  value=get_uploaded_setting(
476
- key="sum_tile_label", default="Ξ£", type_=str
477
  ),
478
- key="sum_tiles_label",
479
  )
480
 
481
- # tile_fill = NULL,
482
- # font_color = NULL,
483
- # tile_border_color = NULL,
484
- # tile_border_size = NULL,
485
- # tile_border_linetype = NULL,
486
- # tc_tile_fill = NULL,
487
- # tc_font_color = NULL,
488
- # tc_tile_border_color = NULL,
489
- # tc_tile_border_size = NULL,
490
- # tc_tile_border_linetype = NULL
491
-
492
- with st.expander("Zero Counts"):
493
- st.write("Special settings for tiles where the count is 0:")
494
- col1, col2, col3 = st.columns(3)
495
- with col1:
496
- output["show_zero_shading"] = st.checkbox(
497
- "Add shading",
498
- value=get_uploaded_setting(
499
- key="show_zero_shading", default=True, type_=bool
500
- ),
501
- )
502
- with col2:
503
- output["show_zero_text"] = st.checkbox(
504
- "Show text",
505
- value=get_uploaded_setting(
506
- key="show_zero_text", default=False, type_=bool
507
- ),
508
- help="Whether to show counts, normalized (%), etc.",
509
- )
510
- with col3:
511
- output["show_zero_percentages"] = st.checkbox(
512
- "Show row/column percentages",
513
- value=get_uploaded_setting(
514
- key="show_zero_percentages", default=False, type_=bool
515
- ),
516
- help="Only relevant when row/column percentages are enabled.",
517
- )
 
 
 
 
 
518
 
519
- if True:
520
- with st.expander("Fonts"):
521
- # Specify available settings and defaults per font
522
- font_types = {
523
- "Top Font": {
524
- "key_prefix": "font_top",
525
- "description": "The big text in the middle (normalized (%) by default).",
526
- "settings": {
527
- "size": 4.3, # 2.8
528
- "color": "#000000",
529
- "alpha": 1.0,
530
- "bold": False,
531
- "italic": False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  },
533
- },
534
- "Bottom Font": {
535
- "key_prefix": "font_bottom",
536
- "description": "The text just below the top font (counts by default).",
537
- "settings": {
538
- "size": 2.8,
539
- "color": "#000000",
540
- "alpha": 1.0,
541
- "bold": False,
542
- "italic": False,
543
  },
544
- },
545
- "Percentages Font": {
546
- "key_prefix": "font_percentage",
547
- "description": "The row and column percentages.",
548
- "settings": {
549
- "size": 2.35,
550
- "color": "#000000",
551
- "alpha": 0.85,
552
- "bold": False,
553
- "italic": True,
554
- "suffix": "%",
555
- "prefix": "",
556
  },
557
- },
558
- "Normalized (%)": {
559
- "key_prefix": "font_normalized",
560
- "description": "Special settings for the normalized (%) text.",
561
- "settings": {"suffix": "%", "prefix": ""},
562
- },
563
- "Counts": {
564
- "key_prefix": "font_counts",
565
- "description": "Special settings for the counts text.",
566
- "settings": {"suffix": "", "prefix": ""},
567
- },
568
- }
569
-
570
- for font_type_title, font_type_spec in font_types.items():
571
- st.markdown(f"**{font_type_title}**")
572
- st.markdown(font_type_spec["description"])
573
- num_cols = 3
574
- font_settings = create_font_settings(
575
- key_prefix=font_type_spec["key_prefix"],
576
- get_setting_fn=get_uploaded_setting,
577
- settings_to_get=list(font_type_spec["settings"].keys()),
578
- )
579
 
580
- for i, (setting_name, setting_widget) in enumerate(
581
- font_settings.items()
582
- ):
583
- if i % num_cols == 0:
584
- cols = st.columns(num_cols)
585
- with cols[i % num_cols]:
586
- default = font_type_spec["settings"][
587
- setting_name[len(font_type_spec["key_prefix"]) + 1 :]
588
- ]
589
- output[setting_name] = setting_widget(
590
- k=setting_name, d=default
591
- )
 
 
 
 
 
592
 
593
- if font_type_title != list(font_types.keys())[-1]:
594
- st.markdown("""---""")
595
-
596
- st.markdown("""---""")
597
-
598
- if st.form_submit_button(label="Generate plot"):
599
- st.session_state["step"] = 3
600
- if (
601
- not output["show_sums"]
602
- or output["sum_tile_palette"] != output["palette"]
603
- ):
604
- # Save settings as json
605
- with open(design_settings_store_path, "w") as f:
606
- json.dump(output, f)
607
- if not output["place_x_axis_above"]:
608
- selected_classes.reverse()
609
- if reverse_class_order:
610
- selected_classes.reverse()
611
 
612
- design_ready = False
613
- if st.session_state["step"] >= 3:
614
- design_ready = True
615
- if output["show_sums"] and output["sum_tile_palette"] == output["palette"]:
616
- st.error(
617
- "The color palettes (background colors) "
618
- "for the tiles and sum tiles are identical. "
619
- "Please select a different color palette for "
620
- "the sum tiles under **Tiles** >> *Sum tile settings*."
621
- )
622
- design_ready = False
623
- if len(selected_classes) < 2:
624
- st.error("At least 2 classes must be selected.")
625
- design_ready = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
 
627
- return output, design_ready, selected_classes
628
 
629
 
630
  # defaults: dict,
 
6
  from text_sections import (
7
  design_text,
8
  )
9
+ from templates import get_templates
10
 
11
 
12
  def _add_select_box(
 
32
  )
33
 
34
 
35
+ templates = get_templates()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
 
38
+ def select_settings():
 
 
 
 
 
39
  def reset_output_callback():
40
+ st.session_state["selected_design_settings"].clear()
41
  if "uploaded_design_settings" in st.session_state:
42
  del st.session_state["uploaded_design_settings"]
43
+ st.session_state["step"] = 2
44
+ st.session_state["design_reset_mode"] = True
45
+ st.session_state["num_resets"] += 1
46
 
47
+ with st.expander("Templates"):
48
+ col1, col2, _ = st.columns([1, 1, 3])
49
+ with col1:
50
+ # Find template with num classes closest to
51
+ # the number of classes in the data
52
+ templates_available_num_classes = sorted(
53
+ set([d["num_classes"] for d in templates.values()])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  )
55
+ closest_num_classes_distance = 99999
56
+ closest_num_classes_idx = -1
57
+ for idx, n in enumerate(templates_available_num_classes):
58
+ distance = abs(n - len(st.session_state["classes"]))
59
+ if distance < closest_num_classes_distance:
60
+ closest_num_classes_distance = distance
61
+ closest_num_classes_idx = idx
62
+
63
+ n_classes = st.selectbox(
64
+ "Number of classes",
65
+ index=closest_num_classes_idx + 1,
66
+ options=[-1] + templates_available_num_classes,
67
+ )
68
+ with col2:
69
+ st.write(" ")
70
+ st.write(" ")
71
+ has_sums = st.checkbox("Sum tiles", value=False)
72
+
73
+ filtered_templates = {
74
+ temp_name: temp
75
+ for temp_name, temp in templates.items()
76
+ if (n_classes == -1 or temp["num_classes"] == n_classes)
77
+ and temp["sums"] == has_sums
78
+ }
79
+
80
+ num_cols = 3
81
+ for i, (temp_name, template) in enumerate(filtered_templates.items()):
82
+ if i % num_cols == 0:
83
+ cols = st.columns(num_cols)
84
+ temp_image = Image.open(f"template_resources/{template['image']}")
85
+ with cols[i % 3]:
86
+ st.image(
87
+ temp_image,
88
+ clamp=False,
89
+ channels="RGB",
90
+ output_format="auto",
91
+ )
92
+ _, col2, _ = st.columns([5, 6, 5])
93
+ with col2:
94
+ if st.button("Select", key=temp_name.replace(" ", "_")):
95
+ with open(
96
+ "template_resources/" + template["settings"],
97
+ "r",
98
+ ) as jfile:
99
+ st.session_state["uploaded_design_settings"] = json.load(
100
+ jfile
101
+ )
102
 
103
+ with st.expander("Upload settings"):
104
+ uploaded_settings_path = st.file_uploader(
105
+ "Upload design settings", type=["json"]
106
+ )
107
+ if st.button(label="Apply settings", on_click=reset_output_callback):
 
 
 
 
 
 
 
 
 
 
108
  if uploaded_settings_path is not None:
 
 
 
 
 
109
  st.session_state["uploaded_design_settings"] = json.load(
110
  uploaded_settings_path
111
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ _, col2, _ = st.columns([5, 2.5, 5])
114
  with col2:
115
  st.button("Reset design", on_click=reset_output_callback)
116
 
117
+
118
+ def design_section(
119
+ num_classes,
120
+ design_settings_store_path,
121
+ ):
122
+ st.session_state["selected_design_settings"] = {}
123
+
124
+ st.markdown("---")
125
+ design_text()
126
+ select_settings()
127
+
128
  def get_uploaded_setting(key, default, type_=None, options=None):
129
  if (
130
  "uploaded_design_settings" in st.session_state
 
146
  return out
147
  return default
148
 
149
+ if st.session_state["design_reset_mode"]:
150
+ if "form_placeholder" in st.session_state:
151
+ st.session_state["form_placeholder"].empty()
152
+ st.session_state["design_reset_mode"] = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
+ st.session_state["form_placeholder"] = st.empty()
155
+ with st.session_state["form_placeholder"].container():
156
+ with st.form(key=f"settings_form_{st.session_state['num_resets']}"):
157
+ col1, col2, col3 = st.columns([4, 2, 2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  with col1:
159
+ selected_classes = st.multiselect(
160
+ "Select classes (min=2, order is respected)",
161
+ options=st.session_state["classes"],
162
+ default=st.session_state["classes"],
163
+ help="Select the classes to create the confusion matrix for. "
164
+ "Any observation with either a target or prediction "
165
+ "of another class is excluded.",
166
  )
167
+ # Reverse by default
168
+ selected_classes.reverse()
169
  with col2:
170
+ st.write(" ")
171
+ st.write(" ")
172
+ reverse_class_order = st.checkbox(
173
+ "Reverse order",
174
+ value=False,
175
+ help="Reverse the order of the classes.",
176
  )
177
 
178
+ # Color palette
179
+ col1, col2, col3, col4 = st.columns([4, 4, 2, 2])
180
  with col1:
181
+ st.session_state["selected_design_settings"][
182
+ "palette"
183
+ ] = _add_select_box(
184
+ key="palette",
185
+ label="Color Palette",
186
+ default="Blues",
187
+ options=[
188
+ "Blues",
189
+ "Greens",
190
+ "Oranges",
191
+ "Greys",
192
+ "Purples",
193
+ "Reds",
194
+ ],
195
+ get_setting_fn=get_uploaded_setting,
196
+ type_=str,
197
+ help="Color of the tiles. Select a preset color palette or create a custom gradient. ",
198
  )
199
  with col2:
200
+ st.write("")
201
+ st.write(" ")
202
+ st.session_state["selected_design_settings"][
203
+ "palette_use_custom"
204
+ ] = st.checkbox(
205
+ "Custom gradient",
206
+ value=False,
207
+ help="Use a custom gradient for coloring the tiles.",
208
  )
209
+ with col3:
210
+ st.session_state["selected_design_settings"][
211
+ "palette_custom_low"
212
+ ] = st.color_picker("Low color", value="#B1F9E8")
213
+ with col4:
214
+ st.session_state["selected_design_settings"][
215
+ "palette_custom_high"
216
+ ] = st.color_picker("High color", value="#239895")
217
+
218
+ # Ask for output parameters
219
+ col1, col2, col3 = st.columns(3)
220
  with col1:
221
+ st.session_state["selected_design_settings"]["width"] = st.number_input(
222
+ "Width (px)",
223
  value=get_uploaded_setting(
224
+ key="width", default=1200 + 100 * (num_classes - 2), type_=int
225
  ),
226
+ step=50,
227
  )
228
+ with col2:
229
+ st.session_state["selected_design_settings"][
230
+ "height"
231
+ ] = st.number_input(
232
+ "Height (px)",
 
 
 
233
  value=get_uploaded_setting(
234
+ key="width",
235
+ default=1200 + 100 * (num_classes - 2),
236
+ type_=int,
237
  ),
238
+ step=50,
 
 
239
  )
240
+ with col3:
241
+ st.session_state["selected_design_settings"]["dpi"] = st.number_input(
242
+ "DPI (scaling)",
243
+ value=get_uploaded_setting(key="dpi", default=320, type_=int),
244
+ step=10,
245
+ help="While the output file *currently* won't have this DPI, "
246
+ "the DPI setting affects scaling of elements. ",
247
  )
248
 
249
+ st.write(" ") # Slightly bigger gap between the two sections
250
+ col1, col2, col3 = st.columns(3)
 
251
  with col1:
252
+ st.session_state["selected_design_settings"][
253
+ "show_counts"
254
+ ] = st.checkbox(
255
+ "Show Counts",
256
  value=get_uploaded_setting(
257
+ key="show_counts", default=True, type_=bool
258
  ),
259
  )
260
+ with col2:
261
+ st.session_state["selected_design_settings"][
262
+ "show_normalized"
263
+ ] = st.checkbox(
264
+ "Show Normalized (%)",
 
 
 
265
  value=get_uploaded_setting(
266
+ key="show_normalized", default=True, type_=bool
267
  ),
268
  )
269
+ with col3:
270
+ st.session_state["selected_design_settings"]["show_sums"] = st.checkbox(
271
+ "Show Sum Tiles",
272
  value=get_uploaded_setting(
273
+ key="show_sums", default=False, type_=bool
274
  ),
275
+ help="Show extra row and column with the "
276
+ "totals for that row/column.",
277
  )
278
+
279
+ st.markdown("""---""")
280
+ st.markdown("**Advanced**:")
281
+
282
+ with st.expander("Labels"):
283
+ col1, col2 = st.columns(2)
284
+ with col1:
285
+ st.session_state["selected_design_settings"][
286
+ "x_label"
287
+ ] = st.text_input(
288
+ "x-axis",
289
  value=get_uploaded_setting(
290
+ key="x_label", default="True Class", type_=str
291
+ ),
 
 
 
292
  )
293
+ with col2:
294
+ st.session_state["selected_design_settings"][
295
+ "y_label"
296
+ ] = st.text_input(
297
+ "y-axis",
298
  value=get_uploaded_setting(
299
+ key="y_label", default="Predicted Class", type_=str
300
+ ),
 
 
 
301
  )
 
 
302
 
303
+ st.markdown("---")
304
+ col1, col2 = st.columns(2)
305
+ with col1:
306
+ st.session_state["selected_design_settings"][
307
+ "title_label"
308
+ ] = st.text_input(
309
+ "Title",
310
+ value=get_uploaded_setting(
311
+ key="title_label", default="", type_=str
312
+ ),
313
+ )
314
+ with col2:
315
+ st.session_state["selected_design_settings"][
316
+ "caption_label"
317
+ ] = st.text_input(
318
+ "Caption",
319
+ value=get_uploaded_setting(
320
+ key="caption_label", default="", type_=str
321
+ ),
322
+ )
323
+ st.info(
324
+ "Note: When adding a title or caption, "
325
+ "you may need to adjust the height and "
326
+ "width of the plot as well."
 
 
 
 
327
  )
328
 
329
+ with st.expander("Elements"):
330
+ col1, col2 = st.columns(2)
331
+ with col1:
332
+ st.session_state["selected_design_settings"][
333
+ "rotate_y_text"
334
+ ] = st.checkbox(
335
+ "Rotate y-axis text",
336
+ value=get_uploaded_setting(
337
+ key="rotate_y_text", default=True, type_=bool
338
+ ),
339
+ )
340
+ st.session_state["selected_design_settings"][
341
+ "place_x_axis_above"
342
+ ] = st.checkbox(
343
+ "Place x-axis on top",
344
+ value=get_uploaded_setting(
345
+ key="place_x_axis_above", default=True, type_=bool
346
+ ),
347
+ )
348
+ st.session_state["selected_design_settings"][
349
+ "counts_on_top"
350
+ ] = st.checkbox(
351
+ "Counts on top",
352
+ value=get_uploaded_setting(
353
+ key="counts_on_top", default=False, type_=bool
354
+ ),
355
+ help="Whether to switch the positions of the counts and normalized counts (%). "
356
+ "The counts become the big centralized numbers and the "
357
+ "normalized counts go below with a smaller font size.",
358
+ )
359
+ with col2:
360
+ st.session_state["selected_design_settings"][
361
+ "num_digits"
362
+ ] = st.number_input(
363
+ "Digits",
364
+ value=get_uploaded_setting(
365
+ key="num_digits", default=2, type_=int
366
+ ),
367
+ help="Number of digits to round percentages to.",
368
+ )
369
 
370
+ st.markdown("""---""")
 
 
 
 
 
371
 
372
+ col1, col2 = st.columns(2)
373
+ with col1:
374
+ st.write("Row and column percentages:")
375
+ st.session_state["selected_design_settings"][
376
+ "show_row_percentages"
377
+ ] = st.checkbox(
378
+ "Show row percentages",
379
+ value=get_uploaded_setting(
380
+ key="show_row_percentages",
381
+ default=num_classes < 6,
382
+ type_=bool,
383
+ ),
384
+ )
385
+ st.session_state["selected_design_settings"][
386
+ "show_col_percentages"
387
+ ] = st.checkbox(
388
+ "Show column percentages",
389
+ value=get_uploaded_setting(
390
+ key="show_col_percentages",
391
+ default=num_classes < 6,
392
+ type_=bool,
393
+ ),
394
+ )
395
+ st.session_state["selected_design_settings"][
396
+ "show_arrows"
397
+ ] = st.checkbox(
398
+ "Show arrows",
399
+ value=get_uploaded_setting(
400
+ key="show_arrows", default=True, type_=bool
401
+ ),
402
+ )
403
+ st.session_state["selected_design_settings"][
404
+ "diag_percentages_only"
405
+ ] = st.checkbox(
406
+ "Diagonal row/column percentages only",
407
+ value=get_uploaded_setting(
408
+ key="diag_percentages_only",
409
+ default=False,
410
+ type_=bool,
411
+ ),
412
+ )
413
+ with col2:
414
+ st.session_state["selected_design_settings"]["arrow_size"] = (
415
+ st.slider(
416
+ "Arrow size",
417
+ value=get_uploaded_setting(
418
+ key="arrow_size", default=0.048, type_=float
419
+ )
420
+ * 10,
421
+ min_value=0.03 * 10,
422
+ max_value=0.06 * 10,
423
+ step=0.001 * 10,
424
+ )
425
+ / 10
426
+ )
427
+ st.session_state["selected_design_settings"][
428
+ "arrow_nudge_from_text"
429
+ ] = (
430
+ st.slider(
431
+ "Arrow nudge from text",
432
+ value=get_uploaded_setting(
433
+ key="arrow_nudge_from_text",
434
+ default=0.065,
435
+ type_=float,
436
+ )
437
+ * 10,
438
+ min_value=0.00,
439
+ max_value=0.1 * 10,
440
+ step=0.001 * 10,
441
+ )
442
+ / 10
443
+ )
444
 
445
+ with st.expander("Tiles"):
446
+ col1, col2 = st.columns(2)
447
+ with col1:
448
+ st.session_state["selected_design_settings"][
449
+ "intensity_by"
450
+ ] = _add_select_box(
451
+ key="intensity_by",
452
+ label="Intensity based on",
453
+ default="Counts",
454
+ options=[
455
+ "Counts",
456
+ "Normalized (%)",
457
+ "Log Counts",
458
+ "Log2 Counts",
459
+ "Log10 Counts",
460
+ "Arcsinh Counts",
461
+ ],
462
+ get_setting_fn=get_uploaded_setting,
463
+ type_=str,
464
+ )
465
+ with col2:
466
+ st.session_state["selected_design_settings"][
467
+ "darkness"
468
+ ] = st.slider(
469
+ "Darkness",
470
+ min_value=0.0,
471
+ max_value=1.0,
472
+ value=get_uploaded_setting(
473
+ key="darkness", default=0.8, type_=float
474
+ ),
475
+ step=0.01,
476
+ help="How dark the darkest colors should be, between 0 and 1, where 1 is darkest.",
477
+ )
478
 
479
+ st.markdown("""---""")
 
 
 
 
 
 
 
 
 
480
 
481
+ st.session_state["selected_design_settings"][
482
+ "show_tile_border"
483
+ ] = st.checkbox(
484
+ "Add tile borders",
485
  value=get_uploaded_setting(
486
+ key="show_tile_border", default=False, type_=bool
487
  ),
 
488
  )
489
 
490
+ col1, col2, col3 = st.columns(3)
491
+ with col1:
492
+ st.session_state["selected_design_settings"][
493
+ "tile_border_color"
494
+ ] = st.color_picker(
495
+ "Border color",
496
+ value=get_uploaded_setting(
497
+ key="tile_border_color",
498
+ default="#000000",
499
+ type_=str,
500
+ ),
501
+ )
502
+ with col2:
503
+ st.session_state["selected_design_settings"][
504
+ "tile_border_size"
505
+ ] = st.slider(
506
+ "Border size",
507
+ min_value=0.0,
508
+ max_value=3.0,
509
+ value=get_uploaded_setting(
510
+ key="tile_border_size", default=0.1, type_=float
511
+ ),
512
+ step=0.01,
513
+ )
514
+ with col3:
515
+ st.session_state["selected_design_settings"][
516
+ "tile_border_linetype"
517
+ ] = _add_select_box(
518
+ key="tile_border_linetype",
519
+ label="Border linetype",
520
+ default="solid",
521
+ options=[
522
+ "solid",
523
+ "dashed",
524
+ "dotted",
525
+ "dotdash",
526
+ "longdash",
527
+ "twodash",
528
+ ],
529
+ get_setting_fn=get_uploaded_setting,
530
+ type_=str,
531
+ )
532
 
533
+ st.markdown("""---""")
534
+
535
+ st.write("Sum tile settings:")
536
+
537
+ col1, col2 = st.columns(2)
538
+ with col1:
539
+ st.session_state["selected_design_settings"][
540
+ "sum_tile_palette"
541
+ ] = _add_select_box(
542
+ key="sum_tile_palette",
543
+ label="Color Palette",
544
+ default="Greens",
545
+ options=[
546
+ "Greens",
547
+ "Oranges",
548
+ "Greys",
549
+ "Purples",
550
+ "Reds",
551
+ "Blues",
552
+ ],
553
+ get_setting_fn=get_uploaded_setting,
554
+ type_=str,
555
+ )
556
+
557
+ with col2:
558
+ st.session_state["selected_design_settings"][
559
+ "sum_tile_label"
560
+ ] = st.text_input(
561
+ "Label",
562
+ value=get_uploaded_setting(
563
+ key="sum_tile_label", default="Ξ£", type_=str
564
+ ),
565
+ key="sum_tiles_label",
566
+ )
567
+
568
+ # tile_fill = NULL,
569
+ # font_color = NULL,
570
+ # tile_border_color = NULL,
571
+ # tile_border_size = NULL,
572
+ # tile_border_linetype = NULL,
573
+ # tc_tile_fill = NULL,
574
+ # tc_font_color = NULL,
575
+ # tc_tile_border_color = NULL,
576
+ # tc_tile_border_size = NULL,
577
+ # tc_tile_border_linetype = NULL
578
+
579
+ with st.expander("Zero Counts"):
580
+ st.write("Special settings for tiles where the count is 0:")
581
+ col1, col2, col3 = st.columns(3)
582
+ with col1:
583
+ st.session_state["selected_design_settings"][
584
+ "show_zero_shading"
585
+ ] = st.checkbox(
586
+ "Add shading",
587
+ value=get_uploaded_setting(
588
+ key="show_zero_shading", default=True, type_=bool
589
+ ),
590
+ )
591
+ with col2:
592
+ st.session_state["selected_design_settings"][
593
+ "show_zero_text"
594
+ ] = st.checkbox(
595
+ "Show text",
596
+ value=get_uploaded_setting(
597
+ key="show_zero_text", default=False, type_=bool
598
+ ),
599
+ help="Whether to show counts, normalized (%), etc.",
600
+ )
601
+ with col3:
602
+ st.session_state["selected_design_settings"][
603
+ "show_zero_percentages"
604
+ ] = st.checkbox(
605
+ "Show row/column percentages",
606
+ value=get_uploaded_setting(
607
+ key="show_zero_percentages",
608
+ default=False,
609
+ type_=bool,
610
+ ),
611
+ help="Only relevant when row/column percentages are enabled.",
612
+ )
613
+
614
+ if True:
615
+ with st.expander("Fonts"):
616
+ # Specify available settings and defaults per font
617
+ font_types = {
618
+ "Top Font": {
619
+ "key_prefix": "font_top",
620
+ "description": "The big text in the middle (normalized (%) by default).",
621
+ "settings": {
622
+ "size": 4.3, # 2.8
623
+ "color": "#000000",
624
+ "alpha": 1.0,
625
+ "bold": False,
626
+ "italic": False,
627
+ },
628
  },
629
+ "Bottom Font": {
630
+ "key_prefix": "font_bottom",
631
+ "description": "The text just below the top font (counts by default).",
632
+ "settings": {
633
+ "size": 2.8,
634
+ "color": "#000000",
635
+ "alpha": 1.0,
636
+ "bold": False,
637
+ "italic": False,
638
+ },
639
  },
640
+ "Percentages Font": {
641
+ "key_prefix": "font_percentage",
642
+ "description": "The row and column percentages.",
643
+ "settings": {
644
+ "size": 2.35,
645
+ "color": "#000000",
646
+ "alpha": 0.85,
647
+ "bold": False,
648
+ "italic": True,
649
+ "suffix": "%",
650
+ "prefix": "",
651
+ },
652
  },
653
+ "Normalized (%)": {
654
+ "key_prefix": "font_normalized",
655
+ "description": "Special settings for the normalized (%) text.",
656
+ "settings": {"suffix": "%", "prefix": ""},
657
+ },
658
+ "Counts": {
659
+ "key_prefix": "font_counts",
660
+ "description": "Special settings for the counts text.",
661
+ "settings": {"suffix": "", "prefix": ""},
662
+ },
663
+ }
664
+
665
+ for font_type_title, font_type_spec in font_types.items():
666
+ st.markdown(f"**{font_type_title}**")
667
+ st.markdown(font_type_spec["description"])
668
+ num_cols = 3
669
+ font_settings = create_font_settings(
670
+ key_prefix=font_type_spec["key_prefix"],
671
+ get_setting_fn=get_uploaded_setting,
672
+ settings_to_get=list(font_type_spec["settings"].keys()),
673
+ )
 
674
 
675
+ for i, (setting_name, setting_widget) in enumerate(
676
+ font_settings.items()
677
+ ):
678
+ if i % num_cols == 0:
679
+ cols = st.columns(num_cols)
680
+ with cols[i % num_cols]:
681
+ default = font_type_spec["settings"][
682
+ setting_name[
683
+ len(font_type_spec["key_prefix"]) + 1 :
684
+ ]
685
+ ]
686
+ st.session_state["selected_design_settings"][
687
+ setting_name
688
+ ] = setting_widget(k=setting_name, d=default)
689
+
690
+ if font_type_title != list(font_types.keys())[-1]:
691
+ st.markdown("""---""")
692
 
693
+ st.markdown("""---""")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
 
695
+ if st.form_submit_button(label="Generate plot"):
696
+ st.session_state["step"] = 3
697
+ if (
698
+ not st.session_state["selected_design_settings"]["show_sums"]
699
+ or st.session_state["selected_design_settings"]["sum_tile_palette"]
700
+ != st.session_state["selected_design_settings"]["palette"]
701
+ ):
702
+ # Save settings as json
703
+ with open(design_settings_store_path, "w") as f:
704
+ json.dump(st.session_state["selected_design_settings"], f)
705
+ if not st.session_state["selected_design_settings"][
706
+ "place_x_axis_above"
707
+ ]:
708
+ selected_classes.reverse()
709
+ if reverse_class_order:
710
+ selected_classes.reverse()
711
+
712
+ design_ready = False
713
+ if st.session_state["step"] >= 3:
714
+ design_ready = True
715
+ if (
716
+ st.session_state["selected_design_settings"]["show_sums"]
717
+ and st.session_state["selected_design_settings"]["sum_tile_palette"]
718
+ == st.session_state["selected_design_settings"]["palette"]
719
+ ):
720
+ st.error(
721
+ "The color palettes (background colors) "
722
+ "for the tiles and sum tiles are identical. "
723
+ "Please select a different color palette for "
724
+ "the sum tiles under **Tiles** >> *Sum tile settings*."
725
+ )
726
+ design_ready = False
727
+ if len(selected_classes) < 2:
728
+ st.error("At least 2 classes must be selected.")
729
+ design_ready = False
730
 
731
+ return design_ready, selected_classes
732
 
733
 
734
  # defaults: dict,
plot.R CHANGED
@@ -201,15 +201,6 @@ confusion_matrix <- dplyr::filter(
201
 
202
  # Plotting settings
203
 
204
- # Sum tiles
205
- sums_settings <- sum_tile_settings()
206
- if (isTRUE(design_settings$show_sums)) {
207
- sums_settings <- sum_tile_settings(
208
- palette = design_settings$sum_tile_palette,
209
- label = design_settings$sum_tile_label
210
- )
211
- }
212
-
213
  build_fontface <- function(bold, italic) {
214
  dplyr::case_when(
215
  isTRUE(bold) && isTRUE(italic) ~ "bold.italic",
@@ -296,6 +287,18 @@ if (isTRUE(design_settings$palette_use_custom)) {
296
  )
297
  }
298
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  confusion_matrix_plot <- tryCatch(
300
  {
301
  cvms::plot_confusion_matrix(
 
201
 
202
  # Plotting settings
203
 
 
 
 
 
 
 
 
 
 
204
  build_fontface <- function(bold, italic) {
205
  dplyr::case_when(
206
  isTRUE(bold) && isTRUE(italic) ~ "bold.italic",
 
287
  )
288
  }
289
 
290
+ # Sum tiles
291
+ sums_settings <- sum_tile_settings()
292
+ if (isTRUE(design_settings$show_sums)) {
293
+ sums_settings <- sum_tile_settings(
294
+ palette = design_settings$sum_tile_palette,
295
+ label = design_settings$sum_tile_label,
296
+ tile_border_color = tile_border_color,
297
+ tile_border_size = design_settings$tile_border_size,
298
+ tile_border_linetype = design_settings$tile_border_linetype
299
+ )
300
+ }
301
+
302
  confusion_matrix_plot <- tryCatch(
303
  {
304
  cvms::plot_confusion_matrix(
template_resources/blues_nc2_1.1.png ADDED
template_resources/blues_nc2_sums_1.1.png ADDED
template_resources/blues_nc3_1.1.png ADDED
template_resources/blues_nc3_sums_1.1.png ADDED
template_resources/design_settings.blues_nc2_1.1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"palette": "Blues", "palette_use_custom": false, "palette_custom_low": "#B1F9E8", "palette_custom_high": "#239895", "width": 1100, "height": 1100, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": false, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": false, "tile_border_color": "#000000", "tile_border_size": 0.1, "tile_border_linetype": "solid", "sum_tile_palette": "Greens", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
template_resources/design_settings.blues_nc2_sums_1.1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"palette": "Blues", "palette_use_custom": false, "palette_custom_low": "#B1F9E8", "palette_custom_high": "#239895", "width": 1300, "height": 1300, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": true, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": false, "tile_border_color": "#000000", "tile_border_size": 0.1, "tile_border_linetype": "solid", "sum_tile_palette": "Greens", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
template_resources/design_settings.blues_nc3_1.1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"palette": "Blues", "palette_use_custom": false, "palette_custom_low": "#B1F9E8", "palette_custom_high": "#239895", "width": 1300, "height": 1300, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": false, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": false, "tile_border_color": "#000000", "tile_border_size": 0.1, "tile_border_linetype": "solid", "sum_tile_palette": "Greens", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
template_resources/design_settings.blues_nc3_sums_1.1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"palette": "Blues", "palette_use_custom": false, "palette_custom_low": "#B1F9E8", "palette_custom_high": "#239895", "width": 1425, "height": 1425, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": true, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": false, "tile_border_color": "#000000", "tile_border_size": 0.1, "tile_border_linetype": "solid", "sum_tile_palette": "Greens", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
templates/design_settings.grey_nc2_1.1.json β†’ template_resources/design_settings.greys_nc2_1.1.json RENAMED
@@ -1 +1 @@
1
- {"palette": "Greys", "width": 1150, "height": 1150, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": false, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
 
1
+ {"palette": "Greys", "width": 1150, "height": 1150, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": false, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.75, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
templates/design_settings.grey_nc3_1.1.json β†’ template_resources/design_settings.greys_nc2_sums_1.1.json RENAMED
@@ -1 +1 @@
1
- {"palette": "Greys", "width": 1300, "height": 1300, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": false, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
 
1
+ {"palette": "Greys", "width": 1300, "height": 1300, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": true, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.75, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
template_resources/design_settings.greys_nc3_1.1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"palette": "Greys", "width": 1300, "height": 1300, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": false, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.75, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
template_resources/design_settings.greys_nc3_sums_1.1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"palette": "Greys", "palette_use_custom": false, "palette_custom_low": "#B1F9E8", "palette_custom_high": "#239895", "width": 1425, "height": 1425, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": true, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.75, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
template_resources/greys_nc2_1.1.png ADDED
template_resources/greys_nc2_sums_1.1.png ADDED
template_resources/greys_nc3_1.1.png ADDED
template_resources/greys_nc3_sums_1.1.png ADDED
templates.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def get_templates():
2
+ temps = Templates()
3
+
4
+ # Blues
5
+ temps.add(
6
+ name="Blues 2-Class",
7
+ num_classes=2,
8
+ sums=False,
9
+ settings_path="design_settings.blues_nc2_1.1.json",
10
+ image_path="blues_nc2_1.1.png",
11
+ collection="Blues 1",
12
+ )
13
+ temps.add(
14
+ name="Blues 3-Class",
15
+ num_classes=3,
16
+ sums=False,
17
+ settings_path="design_settings.blues_nc3_1.1.json",
18
+ image_path="blues_nc3_1.1.png",
19
+ collection="Blues 1",
20
+ )
21
+ temps.add(
22
+ name="Blues 2-Class w/ Sums",
23
+ num_classes=2,
24
+ sums=True,
25
+ settings_path="design_settings.blues_nc2_sums_1.1.json",
26
+ image_path="blues_nc2_sums_1.1.png",
27
+ collection="Blues 1",
28
+ )
29
+ temps.add(
30
+ name="Blues 3-Class w/ Sums",
31
+ num_classes=3,
32
+ sums=True,
33
+ settings_path="design_settings.blues_nc3_sums_1.1.json",
34
+ image_path="blues_nc3_sums_1.1.png",
35
+ collection="Blues 1",
36
+ )
37
+
38
+ # Greys
39
+ temps.add(
40
+ name="Greys 2-Class",
41
+ num_classes=2,
42
+ sums=False,
43
+ settings_path="design_settings.greys_nc2_1.1.json",
44
+ image_path="greys_nc2_1.1.png",
45
+ collection="Greys 1",
46
+ )
47
+ temps.add(
48
+ name="Greys 3-Class",
49
+ num_classes=3,
50
+ sums=False,
51
+ settings_path="design_settings.greys_nc3_1.1.json",
52
+ image_path="greys_nc3_1.1.png",
53
+ collection="Greys 1",
54
+ )
55
+ temps.add(
56
+ name="Greys 2-Class w/ Sums",
57
+ num_classes=2,
58
+ sums=True,
59
+ settings_path="design_settings.greys_nc2_sums_1.1.json",
60
+ image_path="greys_nc2_sums_1.1.png",
61
+ collection="Greys 1",
62
+ )
63
+ temps.add(
64
+ name="Greys 3-Class w/ Sums",
65
+ num_classes=3,
66
+ sums=True,
67
+ settings_path="design_settings.greys_nc3_sums_1.1.json",
68
+ image_path="greys_nc3_sums_1.1.png",
69
+ collection="Greys 1",
70
+ )
71
+
72
+ return temps.get_templates()
73
+
74
+
75
+ class Templates:
76
+ def __init__(self) -> None:
77
+ self.templates = {}
78
+
79
+ def get_templates(self) -> dict:
80
+ return self.templates
81
+
82
+ def add(
83
+ self,
84
+ name: str,
85
+ num_classes: int,
86
+ sums: bool,
87
+ settings_path: str,
88
+ image_path: str,
89
+ collection: str,
90
+ ) -> None:
91
+ self.templates[name] = {
92
+ "collection": collection,
93
+ "num_classes": num_classes,
94
+ "sums": sums,
95
+ "settings": settings_path,
96
+ "image": image_path,
97
+ }
templates/design_settings.grey_nc2_sums_1.1.json DELETED
@@ -1 +0,0 @@
1
- {"palette": "Greys", "width": 1300, "height": 1300, "dpi": 320, "show_counts": true, "show_normalized": true, "show_sums": true, "x_label": "True Class", "y_label": "Predicted Class", "title_label": "", "caption_label": "", "rotate_y_text": true, "place_x_axis_above": true, "counts_on_top": false, "num_digits": 2, "show_row_percentages": true, "show_col_percentages": true, "show_arrows": true, "diag_percentages_only": false, "arrow_size": 0.048, "arrow_nudge_from_text": 0.065, "intensity_by": "Counts", "darkness": 0.8, "show_tile_border": true, "tile_border_color": "#000000", "tile_border_size": 0.05, "tile_border_linetype": "longdash", "sum_tile_palette": "Purples", "sum_tile_label": "Total", "show_zero_shading": true, "show_zero_text": true, "show_zero_percentages": false, "font_top_color": "#000000", "font_top_bold": false, "font_top_italic": false, "font_top_size": 4.3, "font_top_alpha": 1.0, "font_bottom_color": "#000000", "font_bottom_bold": false, "font_bottom_italic": false, "font_bottom_size": 2.8, "font_bottom_alpha": 1.0, "font_percentage_color": "#000000", "font_percentage_bold": false, "font_percentage_italic": true, "font_percentage_size": 2.35, "font_percentage_alpha": 0.85, "font_percentage_prefix": "", "font_percentage_suffix": "%", "font_normalized_prefix": "", "font_normalized_suffix": "%", "font_counts_prefix": "", "font_counts_suffix": ""}
 
 
templates/grey_nc2_1.1.jpg DELETED
Binary file (109 kB)
 
templates/grey_nc2_sums_1.1.jpg DELETED
Binary file (155 kB)
 
templates/grey_nc3_1.1.jpg DELETED
Binary file (182 kB)
 
text_sections.py CHANGED
@@ -193,7 +193,8 @@ def design_text():
193
  st.subheader("Design your plot")
194
  st.write("This is where you customize the design of your confusion matrix plot.")
195
  st.markdown(
196
- "We suggest you go directly to `Generate plot` to see the starting point. Then go back and tweak to your liking!"
 
197
  )
198
  st.markdown(
199
  "The *width* and *height* settings are usually necessary to adjust as they "
@@ -201,7 +202,6 @@ def design_text():
201
  "time for a start."
202
  )
203
  st.write(
204
- "If you have previously saved your preferred design settings, "
205
- "you can start by uploading the json file. "
206
- "Otherwise, get designing!"
207
  )
 
 
193
  st.subheader("Design your plot")
194
  st.write("This is where you customize the design of your confusion matrix plot.")
195
  st.markdown(
196
+ "We suggest you go directly to `Generate plot` to see the starting point. Then go back and tweak to your liking! "
197
+ "You can also select one of the templates or upload previously saved design settings."
198
  )
199
  st.markdown(
200
  "The *width* and *height* settings are usually necessary to adjust as they "
 
202
  "time for a start."
203
  )
204
  st.write(
205
+ "Get designing!"
 
 
206
  )
207
+ st.write("")