solara-ui 1.45.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.
Files changed (464) hide show
  1. prefix/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  2. prefix/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  3. solara/__init__.py +124 -0
  4. solara/__main__.py +765 -0
  5. solara/_stores.py +297 -0
  6. solara/alias.py +6 -0
  7. solara/autorouting.py +555 -0
  8. solara/cache.py +305 -0
  9. solara/checks.html +71 -0
  10. solara/checks.py +227 -0
  11. solara/comm.py +28 -0
  12. solara/components/__init__.py +77 -0
  13. solara/components/alert.py +155 -0
  14. solara/components/applayout.py +397 -0
  15. solara/components/button.py +85 -0
  16. solara/components/card.py +87 -0
  17. solara/components/checkbox.py +50 -0
  18. solara/components/code_highlight_css.py +11 -0
  19. solara/components/code_highlight_css.vue +63 -0
  20. solara/components/columns.py +159 -0
  21. solara/components/component_vue.py +134 -0
  22. solara/components/cross_filter.py +335 -0
  23. solara/components/dataframe.py +546 -0
  24. solara/components/datatable.py +214 -0
  25. solara/components/datatable.vue +175 -0
  26. solara/components/details.py +56 -0
  27. solara/components/download.vue +35 -0
  28. solara/components/echarts.py +86 -0
  29. solara/components/echarts.vue +139 -0
  30. solara/components/figure_altair.py +39 -0
  31. solara/components/file_browser.py +181 -0
  32. solara/components/file_download.py +199 -0
  33. solara/components/file_drop.py +159 -0
  34. solara/components/file_drop.vue +83 -0
  35. solara/components/file_list_widget.vue +78 -0
  36. solara/components/head.py +27 -0
  37. solara/components/head_tag.py +49 -0
  38. solara/components/head_tag.vue +60 -0
  39. solara/components/image.py +173 -0
  40. solara/components/input.py +456 -0
  41. solara/components/input_text_area.py +86 -0
  42. solara/components/link.py +55 -0
  43. solara/components/markdown.py +441 -0
  44. solara/components/markdown_editor.py +33 -0
  45. solara/components/markdown_editor.vue +359 -0
  46. solara/components/matplotlib.py +74 -0
  47. solara/components/meta.py +47 -0
  48. solara/components/misc.py +333 -0
  49. solara/components/pivot_table.py +258 -0
  50. solara/components/pivot_table.vue +158 -0
  51. solara/components/progress.py +47 -0
  52. solara/components/select.py +182 -0
  53. solara/components/select.vue +27 -0
  54. solara/components/slider.py +442 -0
  55. solara/components/slider_date.vue +56 -0
  56. solara/components/spinner-solara.vue +105 -0
  57. solara/components/spinner.py +45 -0
  58. solara/components/sql_code.py +41 -0
  59. solara/components/sql_code.vue +125 -0
  60. solara/components/style.py +105 -0
  61. solara/components/switch.py +71 -0
  62. solara/components/tab_navigation.py +37 -0
  63. solara/components/title.py +90 -0
  64. solara/components/title.vue +38 -0
  65. solara/components/togglebuttons.py +200 -0
  66. solara/components/tooltip.py +61 -0
  67. solara/core.py +42 -0
  68. solara/datatypes.py +143 -0
  69. solara/express.py +241 -0
  70. solara/hooks/__init__.py +4 -0
  71. solara/hooks/dataframe.py +99 -0
  72. solara/hooks/misc.py +263 -0
  73. solara/hooks/use_reactive.py +151 -0
  74. solara/hooks/use_thread.py +129 -0
  75. solara/kitchensink.py +8 -0
  76. solara/lab/__init__.py +34 -0
  77. solara/lab/components/__init__.py +7 -0
  78. solara/lab/components/chat.py +215 -0
  79. solara/lab/components/confirmation_dialog.py +163 -0
  80. solara/lab/components/cross_filter.py +7 -0
  81. solara/lab/components/input_date.py +339 -0
  82. solara/lab/components/input_time.py +133 -0
  83. solara/lab/components/menu.py +181 -0
  84. solara/lab/components/menu.vue +38 -0
  85. solara/lab/components/tabs.py +274 -0
  86. solara/lab/components/theming.py +98 -0
  87. solara/lab/components/theming.vue +72 -0
  88. solara/lab/hooks/__init__.py +0 -0
  89. solara/lab/hooks/dataframe.py +2 -0
  90. solara/lab/toestand.py +3 -0
  91. solara/lab/utils/__init__.py +2 -0
  92. solara/lab/utils/cookies.py +5 -0
  93. solara/lab/utils/dataframe.py +165 -0
  94. solara/lab/utils/headers.py +5 -0
  95. solara/layout.py +44 -0
  96. solara/lifecycle.py +46 -0
  97. solara/minisettings.py +141 -0
  98. solara/py.typed +0 -0
  99. solara/reactive.py +99 -0
  100. solara/routing.py +268 -0
  101. solara/scope/__init__.py +88 -0
  102. solara/scope/types.py +55 -0
  103. solara/server/__init__.py +0 -0
  104. solara/server/app.py +527 -0
  105. solara/server/assets/custom.css +1 -0
  106. solara/server/assets/custom.js +1 -0
  107. solara/server/assets/favicon.png +0 -0
  108. solara/server/assets/favicon.svg +5 -0
  109. solara/server/assets/style.css +1681 -0
  110. solara/server/assets/theme-dark.css +437 -0
  111. solara/server/assets/theme-light.css +420 -0
  112. solara/server/assets/theme.js +3 -0
  113. solara/server/cdn_helper.py +91 -0
  114. solara/server/esm.py +71 -0
  115. solara/server/fastapi.py +5 -0
  116. solara/server/flask.py +297 -0
  117. solara/server/jupyter/__init__.py +2 -0
  118. solara/server/jupyter/cdn_handler.py +28 -0
  119. solara/server/jupyter/server_extension.py +40 -0
  120. solara/server/jupyter/solara.py +91 -0
  121. solara/server/jupytertools.py +46 -0
  122. solara/server/kernel.py +388 -0
  123. solara/server/kernel_context.py +467 -0
  124. solara/server/patch.py +564 -0
  125. solara/server/pyinstaller/__init__.py +9 -0
  126. solara/server/pyinstaller/hook-ipyreact.py +5 -0
  127. solara/server/pyinstaller/hook-ipyvuetify.py +5 -0
  128. solara/server/pyinstaller/hook-solara.py +9 -0
  129. solara/server/qt.py +113 -0
  130. solara/server/reload.py +251 -0
  131. solara/server/server.py +484 -0
  132. solara/server/settings.py +249 -0
  133. solara/server/shell.py +269 -0
  134. solara/server/starlette.py +770 -0
  135. solara/server/static/ansi.js +270 -0
  136. solara/server/static/highlight-dark.css +82 -0
  137. solara/server/static/highlight.css +43 -0
  138. solara/server/static/main-vuetify.js +272 -0
  139. solara/server/static/main.js +163 -0
  140. solara/server/static/solara_bootstrap.py +129 -0
  141. solara/server/static/sun.svg +23 -0
  142. solara/server/static/webworker.js +42 -0
  143. solara/server/telemetry.py +212 -0
  144. solara/server/templates/index.html.j2 +1 -0
  145. solara/server/templates/loader-plain.css +11 -0
  146. solara/server/templates/loader-plain.html +20 -0
  147. solara/server/templates/loader-solara.css +111 -0
  148. solara/server/templates/loader-solara.html +40 -0
  149. solara/server/templates/plain.html +82 -0
  150. solara/server/templates/solara.html.j2 +486 -0
  151. solara/server/threaded.py +84 -0
  152. solara/server/utils.py +44 -0
  153. solara/server/websocket.py +45 -0
  154. solara/settings.py +86 -0
  155. solara/tasks.py +893 -0
  156. solara/template/button.py +16 -0
  157. solara/template/markdown.py +42 -0
  158. solara/template/portal/.flake8 +6 -0
  159. solara/template/portal/.pre-commit-config.yaml +28 -0
  160. solara/template/portal/LICENSE +21 -0
  161. solara/template/portal/Procfile +7 -0
  162. solara/template/portal/mypy.ini +3 -0
  163. solara/template/portal/pyproject.toml +26 -0
  164. solara/template/portal/solara_portal/__init__.py +4 -0
  165. solara/template/portal/solara_portal/components/__init__.py +2 -0
  166. solara/template/portal/solara_portal/components/article.py +28 -0
  167. solara/template/portal/solara_portal/components/data.py +28 -0
  168. solara/template/portal/solara_portal/components/header.py +6 -0
  169. solara/template/portal/solara_portal/components/layout.py +6 -0
  170. solara/template/portal/solara_portal/content/articles/equis-in-vidi.md +85 -0
  171. solara/template/portal/solara_portal/content/articles/substiterat-vati.md +70 -0
  172. solara/template/portal/solara_portal/data.py +60 -0
  173. solara/template/portal/solara_portal/pages/__init__.py +67 -0
  174. solara/template/portal/solara_portal/pages/article/__init__.py +26 -0
  175. solara/template/portal/solara_portal/pages/tabular.py +29 -0
  176. solara/template/portal/solara_portal/pages/viz/__init__.py +70 -0
  177. solara/template/portal/solara_portal/pages/viz/overview.py +14 -0
  178. solara/test/__init__.py +0 -0
  179. solara/test/pytest_plugin.py +783 -0
  180. solara/toestand.py +998 -0
  181. solara/util.py +348 -0
  182. solara/validate_hooks.py +258 -0
  183. solara/website/__init__.py +0 -0
  184. solara/website/assets/custom.css +444 -0
  185. solara/website/assets/images/logo-small.png +0 -0
  186. solara/website/assets/images/logo.svg +17 -0
  187. solara/website/assets/images/logo_white.svg +50 -0
  188. solara/website/assets/theme.js +8 -0
  189. solara/website/components/__init__.py +5 -0
  190. solara/website/components/algolia.py +6 -0
  191. solara/website/components/algolia.vue +24 -0
  192. solara/website/components/algolia_api.vue +202 -0
  193. solara/website/components/breadcrumbs.py +28 -0
  194. solara/website/components/contact.py +144 -0
  195. solara/website/components/docs.py +143 -0
  196. solara/website/components/header.py +75 -0
  197. solara/website/components/mailchimp.py +12 -0
  198. solara/website/components/mailchimp.vue +47 -0
  199. solara/website/components/markdown.py +99 -0
  200. solara/website/components/markdown_nav.vue +34 -0
  201. solara/website/components/notebook.py +171 -0
  202. solara/website/components/sidebar.py +105 -0
  203. solara/website/pages/__init__.py +370 -0
  204. solara/website/pages/about/__init__.py +9 -0
  205. solara/website/pages/about/about.md +3 -0
  206. solara/website/pages/apps/__init__.py +16 -0
  207. solara/website/pages/apps/authorization/__init__.py +119 -0
  208. solara/website/pages/apps/authorization/admin.py +12 -0
  209. solara/website/pages/apps/authorization/users.py +12 -0
  210. solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
  211. solara/website/pages/apps/layout-demo.py +40 -0
  212. solara/website/pages/apps/multipage/__init__.py +38 -0
  213. solara/website/pages/apps/multipage/page1.py +26 -0
  214. solara/website/pages/apps/multipage/page2.py +34 -0
  215. solara/website/pages/apps/scatter.py +136 -0
  216. solara/website/pages/apps/scrolling.py +63 -0
  217. solara/website/pages/apps/tutorial-streamlit.py +18 -0
  218. solara/website/pages/careers/__init__.py +27 -0
  219. solara/website/pages/changelog/__init__.py +10 -0
  220. solara/website/pages/changelog/changelog.md +372 -0
  221. solara/website/pages/contact/__init__.py +34 -0
  222. solara/website/pages/doc_use_download.py +85 -0
  223. solara/website/pages/documentation/__init__.py +90 -0
  224. solara/website/pages/documentation/advanced/__init__.py +9 -0
  225. solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
  226. solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +6 -0
  227. solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +196 -0
  228. solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +125 -0
  229. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +417 -0
  230. solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +69 -0
  231. solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +50 -0
  232. solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +124 -0
  233. solara/website/pages/documentation/advanced/content/15-reference/00-overview.md +3 -0
  234. solara/website/pages/documentation/advanced/content/15-reference/40-static_files.md +31 -0
  235. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +72 -0
  236. solara/website/pages/documentation/advanced/content/15-reference/60-static-site-generation.md +59 -0
  237. solara/website/pages/documentation/advanced/content/15-reference/70-search.md +34 -0
  238. solara/website/pages/documentation/advanced/content/15-reference/80-reloading.md +34 -0
  239. solara/website/pages/documentation/advanced/content/15-reference/90-notebook-support.md +7 -0
  240. solara/website/pages/documentation/advanced/content/15-reference/95-caching.md +148 -0
  241. solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
  242. solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +35 -0
  243. solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +42 -0
  244. solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +28 -0
  245. solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +108 -0
  246. solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +23 -0
  247. solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +192 -0
  248. solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +166 -0
  249. solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +18 -0
  250. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +256 -0
  251. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +108 -0
  252. solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +12 -0
  253. solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +7 -0
  254. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +187 -0
  255. solara/website/pages/documentation/advanced/content/40-development/00-overview.md +0 -0
  256. solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +45 -0
  257. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +76 -0
  258. solara/website/pages/documentation/api/__init__.py +19 -0
  259. solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
  260. solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +22 -0
  261. solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +20 -0
  262. solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +20 -0
  263. solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +20 -0
  264. solara/website/pages/documentation/api/hooks/__init__.py +9 -0
  265. solara/website/pages/documentation/api/hooks/use_cross_filter.py +23 -0
  266. solara/website/pages/documentation/api/hooks/use_dark_effective.py +12 -0
  267. solara/website/pages/documentation/api/hooks/use_effect.md +43 -0
  268. solara/website/pages/documentation/api/hooks/use_effect.py +9 -0
  269. solara/website/pages/documentation/api/hooks/use_exception.py +31 -0
  270. solara/website/pages/documentation/api/hooks/use_memo.md +16 -0
  271. solara/website/pages/documentation/api/hooks/use_memo.py +9 -0
  272. solara/website/pages/documentation/api/hooks/use_previous.py +30 -0
  273. solara/website/pages/documentation/api/hooks/use_reactive.py +16 -0
  274. solara/website/pages/documentation/api/hooks/use_state.py +10 -0
  275. solara/website/pages/documentation/api/hooks/use_state_or_update.py +66 -0
  276. solara/website/pages/documentation/api/hooks/use_thread.md +64 -0
  277. solara/website/pages/documentation/api/hooks/use_thread.py +42 -0
  278. solara/website/pages/documentation/api/hooks/use_trait_observe.py +12 -0
  279. solara/website/pages/documentation/api/routing/__init__.py +9 -0
  280. solara/website/pages/documentation/api/routing/generate_routes.py +10 -0
  281. solara/website/pages/documentation/api/routing/generate_routes_directory.py +10 -0
  282. solara/website/pages/documentation/api/routing/resolve_path.py +35 -0
  283. solara/website/pages/documentation/api/routing/route.py +29 -0
  284. solara/website/pages/documentation/api/routing/use_route.py +76 -0
  285. solara/website/pages/documentation/api/routing/use_router.py +16 -0
  286. solara/website/pages/documentation/api/utilities/__init__.py +9 -0
  287. solara/website/pages/documentation/api/utilities/component_vue.py +10 -0
  288. solara/website/pages/documentation/api/utilities/computed.py +16 -0
  289. solara/website/pages/documentation/api/utilities/display.py +16 -0
  290. solara/website/pages/documentation/api/utilities/get_kernel_id.py +16 -0
  291. solara/website/pages/documentation/api/utilities/get_session_id.py +16 -0
  292. solara/website/pages/documentation/api/utilities/memoize.py +35 -0
  293. solara/website/pages/documentation/api/utilities/on_kernel_start.py +44 -0
  294. solara/website/pages/documentation/api/utilities/reactive.py +16 -0
  295. solara/website/pages/documentation/api/utilities/widget.py +104 -0
  296. solara/website/pages/documentation/components/__init__.py +12 -0
  297. solara/website/pages/documentation/components/advanced/__init__.py +9 -0
  298. solara/website/pages/documentation/components/advanced/link.py +25 -0
  299. solara/website/pages/documentation/components/advanced/meta.py +17 -0
  300. solara/website/pages/documentation/components/advanced/style.py +43 -0
  301. solara/website/pages/documentation/components/common.py +9 -0
  302. solara/website/pages/documentation/components/data/__init__.py +9 -0
  303. solara/website/pages/documentation/components/data/dataframe.py +44 -0
  304. solara/website/pages/documentation/components/data/pivot_table.py +81 -0
  305. solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
  306. solara/website/pages/documentation/components/enterprise/avatar.py +24 -0
  307. solara/website/pages/documentation/components/enterprise/avatar_menu.py +25 -0
  308. solara/website/pages/documentation/components/input/__init__.py +9 -0
  309. solara/website/pages/documentation/components/input/button.py +23 -0
  310. solara/website/pages/documentation/components/input/checkbox.py +10 -0
  311. solara/website/pages/documentation/components/input/file_browser.py +30 -0
  312. solara/website/pages/documentation/components/input/file_drop.py +76 -0
  313. solara/website/pages/documentation/components/input/input.py +43 -0
  314. solara/website/pages/documentation/components/input/select.py +22 -0
  315. solara/website/pages/documentation/components/input/slider.py +29 -0
  316. solara/website/pages/documentation/components/input/switch.py +10 -0
  317. solara/website/pages/documentation/components/input/togglebuttons.py +21 -0
  318. solara/website/pages/documentation/components/lab/__init__.py +9 -0
  319. solara/website/pages/documentation/components/lab/chat.py +109 -0
  320. solara/website/pages/documentation/components/lab/confirmation_dialog.py +55 -0
  321. solara/website/pages/documentation/components/lab/cookies_headers.py +48 -0
  322. solara/website/pages/documentation/components/lab/input_date.py +20 -0
  323. solara/website/pages/documentation/components/lab/input_time.py +15 -0
  324. solara/website/pages/documentation/components/lab/menu.py +22 -0
  325. solara/website/pages/documentation/components/lab/tab.py +25 -0
  326. solara/website/pages/documentation/components/lab/tabs.py +45 -0
  327. solara/website/pages/documentation/components/lab/task.py +11 -0
  328. solara/website/pages/documentation/components/lab/theming.py +74 -0
  329. solara/website/pages/documentation/components/lab/use_task.py +11 -0
  330. solara/website/pages/documentation/components/layout/__init__.py +9 -0
  331. solara/website/pages/documentation/components/layout/app_bar.py +16 -0
  332. solara/website/pages/documentation/components/layout/app_bar_title.py +16 -0
  333. solara/website/pages/documentation/components/layout/app_layout.py +24 -0
  334. solara/website/pages/documentation/components/layout/card.py +15 -0
  335. solara/website/pages/documentation/components/layout/card_actions.py +16 -0
  336. solara/website/pages/documentation/components/layout/column.py +30 -0
  337. solara/website/pages/documentation/components/layout/columns.py +27 -0
  338. solara/website/pages/documentation/components/layout/columns_responsive.py +66 -0
  339. solara/website/pages/documentation/components/layout/details.py +13 -0
  340. solara/website/pages/documentation/components/layout/griddraggable.py +62 -0
  341. solara/website/pages/documentation/components/layout/gridfixed.py +19 -0
  342. solara/website/pages/documentation/components/layout/hbox.py +18 -0
  343. solara/website/pages/documentation/components/layout/row.py +30 -0
  344. solara/website/pages/documentation/components/layout/sidebar.py +24 -0
  345. solara/website/pages/documentation/components/layout/vbox.py +19 -0
  346. solara/website/pages/documentation/components/output/__init__.py +9 -0
  347. solara/website/pages/documentation/components/output/file_download.py +11 -0
  348. solara/website/pages/documentation/components/output/html.py +19 -0
  349. solara/website/pages/documentation/components/output/image.py +11 -0
  350. solara/website/pages/documentation/components/output/markdown.py +57 -0
  351. solara/website/pages/documentation/components/output/markdown_editor.py +51 -0
  352. solara/website/pages/documentation/components/output/sql_code.py +83 -0
  353. solara/website/pages/documentation/components/output/tooltip.py +11 -0
  354. solara/website/pages/documentation/components/page/__init__.py +9 -0
  355. solara/website/pages/documentation/components/page/head.py +15 -0
  356. solara/website/pages/documentation/components/page/title.py +25 -0
  357. solara/website/pages/documentation/components/status/__init__.py +9 -0
  358. solara/website/pages/documentation/components/status/error.py +39 -0
  359. solara/website/pages/documentation/components/status/info.py +39 -0
  360. solara/website/pages/documentation/components/status/progress.py +10 -0
  361. solara/website/pages/documentation/components/status/spinner.py +11 -0
  362. solara/website/pages/documentation/components/status/success.py +40 -0
  363. solara/website/pages/documentation/components/status/warning.py +47 -0
  364. solara/website/pages/documentation/components/viz/__init__.py +9 -0
  365. solara/website/pages/documentation/components/viz/altair.py +42 -0
  366. solara/website/pages/documentation/components/viz/echarts.py +77 -0
  367. solara/website/pages/documentation/components/viz/matplotlib.py +30 -0
  368. solara/website/pages/documentation/components/viz/plotly.py +63 -0
  369. solara/website/pages/documentation/components/viz/plotly_express.py +41 -0
  370. solara/website/pages/documentation/examples/__init__.py +54 -0
  371. solara/website/pages/documentation/examples/ai/__init__.py +11 -0
  372. solara/website/pages/documentation/examples/ai/chatbot.py +113 -0
  373. solara/website/pages/documentation/examples/ai/tokenizer.py +107 -0
  374. solara/website/pages/documentation/examples/basics/__init__.py +10 -0
  375. solara/website/pages/documentation/examples/basics/sine.py +28 -0
  376. solara/website/pages/documentation/examples/fullscreen/__init__.py +10 -0
  377. solara/website/pages/documentation/examples/fullscreen/authorization.py +3 -0
  378. solara/website/pages/documentation/examples/fullscreen/layout_demo.py +3 -0
  379. solara/website/pages/documentation/examples/fullscreen/multipage.py +3 -0
  380. solara/website/pages/documentation/examples/fullscreen/scatter.py +3 -0
  381. solara/website/pages/documentation/examples/fullscreen/scrolling.py +3 -0
  382. solara/website/pages/documentation/examples/fullscreen/tutorial_streamlit.py +3 -0
  383. solara/website/pages/documentation/examples/general/__init__.py +10 -0
  384. solara/website/pages/documentation/examples/general/custom_storage.py +70 -0
  385. solara/website/pages/documentation/examples/general/deploy_model.py +115 -0
  386. solara/website/pages/documentation/examples/general/live_update.py +32 -0
  387. solara/website/pages/documentation/examples/general/login_oauth.py +81 -0
  388. solara/website/pages/documentation/examples/general/mycard.vue +58 -0
  389. solara/website/pages/documentation/examples/general/pokemon_search.py +51 -0
  390. solara/website/pages/documentation/examples/general/vue_component.py +50 -0
  391. solara/website/pages/documentation/examples/ipycanvas.py +49 -0
  392. solara/website/pages/documentation/examples/libraries/__init__.py +10 -0
  393. solara/website/pages/documentation/examples/libraries/altair.py +65 -0
  394. solara/website/pages/documentation/examples/libraries/bqplot.py +39 -0
  395. solara/website/pages/documentation/examples/libraries/ipyleaflet.py +33 -0
  396. solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +66 -0
  397. solara/website/pages/documentation/examples/utilities/__init__.py +10 -0
  398. solara/website/pages/documentation/examples/utilities/calculator.py +157 -0
  399. solara/website/pages/documentation/examples/utilities/countdown_timer.py +62 -0
  400. solara/website/pages/documentation/examples/utilities/todo.py +196 -0
  401. solara/website/pages/documentation/examples/visualization/__init__.py +6 -0
  402. solara/website/pages/documentation/examples/visualization/annotator.py +67 -0
  403. solara/website/pages/documentation/examples/visualization/linked_views.py +81 -0
  404. solara/website/pages/documentation/examples/visualization/plotly.py +44 -0
  405. solara/website/pages/documentation/faq/__init__.py +12 -0
  406. solara/website/pages/documentation/faq/content/99-faq.md +112 -0
  407. solara/website/pages/documentation/getting_started/__init__.py +9 -0
  408. solara/website/pages/documentation/getting_started/content/00-quickstart.md +107 -0
  409. solara/website/pages/documentation/getting_started/content/01-introduction.md +125 -0
  410. solara/website/pages/documentation/getting_started/content/02-installing.md +134 -0
  411. solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
  412. solara/website/pages/documentation/getting_started/content/04-tutorials/10_data_science.py +13 -0
  413. solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +89 -0
  414. solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +124 -0
  415. solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +146 -0
  416. solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +144 -0
  417. solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +65 -0
  418. solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz +0 -0
  419. solara/website/pages/documentation/getting_started/content/04-tutorials/_data_science.ipynb +445 -0
  420. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +1021 -0
  421. solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
  422. solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +228 -0
  423. solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +278 -0
  424. solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
  425. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +305 -0
  426. solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +80 -0
  427. solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
  428. solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +26 -0
  429. solara/website/pages/docutils.py +38 -0
  430. solara/website/pages/home.vue +1199 -0
  431. solara/website/pages/our_team/__init__.py +83 -0
  432. solara/website/pages/pricing/__init__.py +31 -0
  433. solara/website/pages/roadmap/__init__.py +11 -0
  434. solara/website/pages/roadmap/roadmap.md +47 -0
  435. solara/website/pages/scale_ipywidgets.py +45 -0
  436. solara/website/pages/showcase/__init__.py +105 -0
  437. solara/website/pages/showcase/domino_code_assist.py +60 -0
  438. solara/website/pages/showcase/planeto_tessa.py +19 -0
  439. solara/website/pages/showcase/solara_dev.py +54 -0
  440. solara/website/pages/showcase/solarathon_2023_team_2.py +22 -0
  441. solara/website/pages/showcase/solarathon_2023_team_4.py +22 -0
  442. solara/website/pages/showcase/solarathon_2023_team_5.py +23 -0
  443. solara/website/pages/showcase/solarathon_2023_team_6.py +34 -0
  444. solara/website/pages/showcase/wanderlust.py +27 -0
  445. solara/website/public/beach.jpeg +0 -0
  446. solara/website/public/logo.svg +6 -0
  447. solara/website/public/social/discord.svg +1 -0
  448. solara/website/public/social/github.svg +1 -0
  449. solara/website/public/social/twitter.svg +3 -0
  450. solara/website/public/success.html +25 -0
  451. solara/website/templates/index.html.j2 +117 -0
  452. solara/website/utils.py +51 -0
  453. solara/widgets/__init__.py +1 -0
  454. solara/widgets/vue/gridlayout.vue +107 -0
  455. solara/widgets/vue/html.vue +4 -0
  456. solara/widgets/vue/navigator.vue +134 -0
  457. solara/widgets/vue/vegalite.vue +130 -0
  458. solara/widgets/widgets.py +74 -0
  459. solara_ui-1.45.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  460. solara_ui-1.45.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  461. solara_ui-1.45.0.dist-info/METADATA +162 -0
  462. solara_ui-1.45.0.dist-info/RECORD +464 -0
  463. solara_ui-1.45.0.dist-info/WHEEL +4 -0
  464. solara_ui-1.45.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1021 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "8aeeb4f6",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Build your Jupyter dashboard using Solara\n",
9
+ "\n",
10
+ "Welcome to the first part of a series of tutorials showing you how to create a dashboard in Jupyter and deploy it as a standalone web app. Importantly, you won't need to rewrite your app in a different framework for deployment. We will use a pure Python solution with no JavaScript or CSS required.\n",
11
+ "\n",
12
+ "Jupyter notebooks are an incredible data analysis tool since they blend code, visualization, and narrative into a single document. However, we do not want to show the code if the insights must be presented to a non-technical audience.\n",
13
+ "\n",
14
+ "\n",
15
+ "Built on top of ipywidgets, the Solara framework integrates well into the Jupyter notebook, Jupyter lab as well as other Jupyter environments, and as we will see in a later article, can be deployed efficiently using the Solara server. This, by itself, makes Solara a perfect solution for creating dashboards or data apps.\n",
16
+ "\n",
17
+ "In this tutorial, we will create a simple dashboard using Solara's UI components. The final product will allow an end-user to filter,\n",
18
+ "visualize and explore a dataset on a map.\n",
19
+ "\n",
20
+ "![image](https://dxhl76zpt6fap.cloudfront.net/public/docs/tutorial/jupyter-dashboard1.webp)\n",
21
+ "\n",
22
+ "## Pre-requisites \n",
23
+ "\n",
24
+ "You need to install `pandas`, `matplotlib`, `folium` and `solara`. Assuming you are using pip, you can execute on your shell:\n",
25
+ "\n",
26
+ "```\n",
27
+ "$ pip install pandas matplotlib folium solara\n",
28
+ "```\n",
29
+ "\n",
30
+ "\n",
31
+ "Or in your notebook\n",
32
+ "```\n",
33
+ "%pip install pandas matplotlib folium solara\n",
34
+ "```\n",
35
+ "\n",
36
+ "## The start\n",
37
+ "\n",
38
+ "We will use a subsample of the [San Fanfrisco crime dataset](https://www.kaggle.com/competitions/sf-crime/data) which contains information on types of crimes and where they were committed.\n",
39
+ "\n",
40
+ "[Download the CSV file](https://github.com/widgetti/solara/raw/master/solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz) if you want to run this locally, or let the code below sort it out."
41
+ ]
42
+ },
43
+ {
44
+ "cell_type": "markdown",
45
+ "id": "6cc6256a",
46
+ "metadata": {},
47
+ "source": [
48
+ "The first thing we do when we read in the data is to print it out to see what the dataset contains."
49
+ ]
50
+ },
51
+ {
52
+ "cell_type": "code",
53
+ "execution_count": 1,
54
+ "id": "4f399bdc",
55
+ "metadata": {},
56
+ "outputs": [
57
+ {
58
+ "data": {
59
+ "text/html": [
60
+ "<div>\n",
61
+ "<style scoped>\n",
62
+ " .dataframe tbody tr th:only-of-type {\n",
63
+ " vertical-align: middle;\n",
64
+ " }\n",
65
+ "\n",
66
+ " .dataframe tbody tr th {\n",
67
+ " vertical-align: top;\n",
68
+ " }\n",
69
+ "\n",
70
+ " .dataframe thead th {\n",
71
+ " text-align: right;\n",
72
+ " }\n",
73
+ "</style>\n",
74
+ "<table border=\"1\" class=\"dataframe\">\n",
75
+ " <thead>\n",
76
+ " <tr style=\"text-align: right;\">\n",
77
+ " <th></th>\n",
78
+ " <th>Unnamed: 0</th>\n",
79
+ " <th>IncidntNum</th>\n",
80
+ " <th>Category</th>\n",
81
+ " <th>Descript</th>\n",
82
+ " <th>DayOfWeek</th>\n",
83
+ " <th>Date</th>\n",
84
+ " <th>Time</th>\n",
85
+ " <th>PdDistrict</th>\n",
86
+ " <th>Resolution</th>\n",
87
+ " <th>Address</th>\n",
88
+ " <th>X</th>\n",
89
+ " <th>Y</th>\n",
90
+ " <th>Location</th>\n",
91
+ " <th>PdId</th>\n",
92
+ " </tr>\n",
93
+ " </thead>\n",
94
+ " <tbody>\n",
95
+ " <tr>\n",
96
+ " <th>0</th>\n",
97
+ " <td>50820</td>\n",
98
+ " <td>160525689</td>\n",
99
+ " <td>BURGLARY</td>\n",
100
+ " <td>BURGLARY OF STORE, FORCIBLE ENTRY</td>\n",
101
+ " <td>Tuesday</td>\n",
102
+ " <td>06/28/2016 12:00:00 AM</td>\n",
103
+ " <td>21:25</td>\n",
104
+ " <td>TARAVAL</td>\n",
105
+ " <td>ARREST, BOOKED</td>\n",
106
+ " <td>600 Block of LINCOLN WY</td>\n",
107
+ " <td>-122.464850</td>\n",
108
+ " <td>37.765888</td>\n",
109
+ " <td>(37.7658875448653, -122.464850114297)</td>\n",
110
+ " <td>16052568905051</td>\n",
111
+ " </tr>\n",
112
+ " <tr>\n",
113
+ " <th>1</th>\n",
114
+ " <td>11981</td>\n",
115
+ " <td>160334220</td>\n",
116
+ " <td>LARCENY/THEFT</td>\n",
117
+ " <td>PETTY THEFT FROM LOCKED AUTO</td>\n",
118
+ " <td>Friday</td>\n",
119
+ " <td>04/22/2016 12:00:00 AM</td>\n",
120
+ " <td>19:00</td>\n",
121
+ " <td>TARAVAL</td>\n",
122
+ " <td>NONE</td>\n",
123
+ " <td>SAN JOSE AV / LAKEVIEW AV</td>\n",
124
+ " <td>-122.450378</td>\n",
125
+ " <td>37.716169</td>\n",
126
+ " <td>(37.7161694707734, -122.450378171697)</td>\n",
127
+ " <td>16033422006243</td>\n",
128
+ " </tr>\n",
129
+ " <tr>\n",
130
+ " <th>2</th>\n",
131
+ " <td>74626</td>\n",
132
+ " <td>160740053</td>\n",
133
+ " <td>LARCENY/THEFT</td>\n",
134
+ " <td>PETTY THEFT FROM A BUILDING</td>\n",
135
+ " <td>Monday</td>\n",
136
+ " <td>09/12/2016 12:00:00 AM</td>\n",
137
+ " <td>08:40</td>\n",
138
+ " <td>INGLESIDE</td>\n",
139
+ " <td>NONE</td>\n",
140
+ " <td>0 Block of PHELAN AV</td>\n",
141
+ " <td>-122.452290</td>\n",
142
+ " <td>37.725693</td>\n",
143
+ " <td>(37.7256933575703, -122.452289660492)</td>\n",
144
+ " <td>16074005306303</td>\n",
145
+ " </tr>\n",
146
+ " <tr>\n",
147
+ " <th>3</th>\n",
148
+ " <td>60776</td>\n",
149
+ " <td>160619721</td>\n",
150
+ " <td>WARRANTS</td>\n",
151
+ " <td>WARRANT ARREST</td>\n",
152
+ " <td>Monday</td>\n",
153
+ " <td>08/01/2016 12:00:00 AM</td>\n",
154
+ " <td>16:12</td>\n",
155
+ " <td>PARK</td>\n",
156
+ " <td>ARREST, BOOKED</td>\n",
157
+ " <td>1100 Block of SCOTT ST</td>\n",
158
+ " <td>-122.437099</td>\n",
159
+ " <td>37.780352</td>\n",
160
+ " <td>(37.7803522156893, -122.43709942832)</td>\n",
161
+ " <td>16061972163010</td>\n",
162
+ " </tr>\n",
163
+ " <tr>\n",
164
+ " <th>4</th>\n",
165
+ " <td>34547</td>\n",
166
+ " <td>160345772</td>\n",
167
+ " <td>WARRANTS</td>\n",
168
+ " <td>ENROUTE TO OUTSIDE JURISDICTION</td>\n",
169
+ " <td>Wednesday</td>\n",
170
+ " <td>04/27/2016 12:00:00 AM</td>\n",
171
+ " <td>19:34</td>\n",
172
+ " <td>SOUTHERN</td>\n",
173
+ " <td>ARREST, BOOKED</td>\n",
174
+ " <td>600 Block of BRANNAN ST</td>\n",
175
+ " <td>-122.399841</td>\n",
176
+ " <td>37.775633</td>\n",
177
+ " <td>(37.7756327864282, -122.399841045579)</td>\n",
178
+ " <td>16034577262050</td>\n",
179
+ " </tr>\n",
180
+ " <tr>\n",
181
+ " <th>...</th>\n",
182
+ " <td>...</td>\n",
183
+ " <td>...</td>\n",
184
+ " <td>...</td>\n",
185
+ " <td>...</td>\n",
186
+ " <td>...</td>\n",
187
+ " <td>...</td>\n",
188
+ " <td>...</td>\n",
189
+ " <td>...</td>\n",
190
+ " <td>...</td>\n",
191
+ " <td>...</td>\n",
192
+ " <td>...</td>\n",
193
+ " <td>...</td>\n",
194
+ " <td>...</td>\n",
195
+ " <td>...</td>\n",
196
+ " </tr>\n",
197
+ " <tr>\n",
198
+ " <th>9995</th>\n",
199
+ " <td>137465</td>\n",
200
+ " <td>170013301</td>\n",
201
+ " <td>OTHER OFFENSES</td>\n",
202
+ " <td>HARASSING PHONE CALLS</td>\n",
203
+ " <td>Saturday</td>\n",
204
+ " <td>11/26/2016 12:00:00 AM</td>\n",
205
+ " <td>12:00</td>\n",
206
+ " <td>SOUTHERN</td>\n",
207
+ " <td>NONE</td>\n",
208
+ " <td>1100 Block of MISSION ST</td>\n",
209
+ " <td>-122.412834</td>\n",
210
+ " <td>37.777790</td>\n",
211
+ " <td>(37.7777903094246, -122.412834332129)</td>\n",
212
+ " <td>17001330128135</td>\n",
213
+ " </tr>\n",
214
+ " <tr>\n",
215
+ " <th>9996</th>\n",
216
+ " <td>55811</td>\n",
217
+ " <td>160573939</td>\n",
218
+ " <td>OTHER OFFENSES</td>\n",
219
+ " <td>LOST/STOLEN LICENSE PLATE</td>\n",
220
+ " <td>Thursday</td>\n",
221
+ " <td>07/07/2016 12:00:00 AM</td>\n",
222
+ " <td>19:00</td>\n",
223
+ " <td>BAYVIEW</td>\n",
224
+ " <td>NONE</td>\n",
225
+ " <td>100 Block of TEXAS ST</td>\n",
226
+ " <td>-122.395812</td>\n",
227
+ " <td>37.764531</td>\n",
228
+ " <td>(37.7645312950153, -122.395812338479)</td>\n",
229
+ " <td>16057393971010</td>\n",
230
+ " </tr>\n",
231
+ " <tr>\n",
232
+ " <th>9997</th>\n",
233
+ " <td>120115</td>\n",
234
+ " <td>166110038</td>\n",
235
+ " <td>LARCENY/THEFT</td>\n",
236
+ " <td>GRAND THEFT FROM LOCKED AUTO</td>\n",
237
+ " <td>Saturday</td>\n",
238
+ " <td>05/14/2016 12:00:00 AM</td>\n",
239
+ " <td>03:30</td>\n",
240
+ " <td>CENTRAL</td>\n",
241
+ " <td>NONE</td>\n",
242
+ " <td>BAY ST / VANNESS AV</td>\n",
243
+ " <td>-122.425111</td>\n",
244
+ " <td>37.804146</td>\n",
245
+ " <td>(37.80414615262, -122.425110613231)</td>\n",
246
+ " <td>16611003806244</td>\n",
247
+ " </tr>\n",
248
+ " <tr>\n",
249
+ " <th>9998</th>\n",
250
+ " <td>5069</td>\n",
251
+ " <td>160093943</td>\n",
252
+ " <td>NON-CRIMINAL</td>\n",
253
+ " <td>AIDED CASE</td>\n",
254
+ " <td>Monday</td>\n",
255
+ " <td>02/01/2016 12:00:00 AM</td>\n",
256
+ " <td>15:23</td>\n",
257
+ " <td>TARAVAL</td>\n",
258
+ " <td>ARREST, BOOKED</td>\n",
259
+ " <td>2600 Block of SAN JOSE AV</td>\n",
260
+ " <td>-122.450635</td>\n",
261
+ " <td>37.715772</td>\n",
262
+ " <td>(37.7157715048394, -122.450634805259)</td>\n",
263
+ " <td>16009394351040</td>\n",
264
+ " </tr>\n",
265
+ " <tr>\n",
266
+ " <th>9999</th>\n",
267
+ " <td>35667</td>\n",
268
+ " <td>160373212</td>\n",
269
+ " <td>ASSAULT</td>\n",
270
+ " <td>BATTERY</td>\n",
271
+ " <td>Friday</td>\n",
272
+ " <td>05/06/2016 12:00:00 AM</td>\n",
273
+ " <td>20:30</td>\n",
274
+ " <td>BAYVIEW</td>\n",
275
+ " <td>NONE</td>\n",
276
+ " <td>1300 Block of NEWHALL ST</td>\n",
277
+ " <td>-122.391880</td>\n",
278
+ " <td>37.735936</td>\n",
279
+ " <td>(37.7359364818345, -122.391879837176)</td>\n",
280
+ " <td>16037321204134</td>\n",
281
+ " </tr>\n",
282
+ " </tbody>\n",
283
+ "</table>\n",
284
+ "<p>10000 rows × 14 columns</p>\n",
285
+ "</div>"
286
+ ],
287
+ "text/plain": [
288
+ " Unnamed: 0 IncidntNum Category \\\n",
289
+ "0 50820 160525689 BURGLARY \n",
290
+ "1 11981 160334220 LARCENY/THEFT \n",
291
+ "2 74626 160740053 LARCENY/THEFT \n",
292
+ "3 60776 160619721 WARRANTS \n",
293
+ "4 34547 160345772 WARRANTS \n",
294
+ "... ... ... ... \n",
295
+ "9995 137465 170013301 OTHER OFFENSES \n",
296
+ "9996 55811 160573939 OTHER OFFENSES \n",
297
+ "9997 120115 166110038 LARCENY/THEFT \n",
298
+ "9998 5069 160093943 NON-CRIMINAL \n",
299
+ "9999 35667 160373212 ASSAULT \n",
300
+ "\n",
301
+ " Descript DayOfWeek Date \\\n",
302
+ "0 BURGLARY OF STORE, FORCIBLE ENTRY Tuesday 06/28/2016 12:00:00 AM \n",
303
+ "1 PETTY THEFT FROM LOCKED AUTO Friday 04/22/2016 12:00:00 AM \n",
304
+ "2 PETTY THEFT FROM A BUILDING Monday 09/12/2016 12:00:00 AM \n",
305
+ "3 WARRANT ARREST Monday 08/01/2016 12:00:00 AM \n",
306
+ "4 ENROUTE TO OUTSIDE JURISDICTION Wednesday 04/27/2016 12:00:00 AM \n",
307
+ "... ... ... ... \n",
308
+ "9995 HARASSING PHONE CALLS Saturday 11/26/2016 12:00:00 AM \n",
309
+ "9996 LOST/STOLEN LICENSE PLATE Thursday 07/07/2016 12:00:00 AM \n",
310
+ "9997 GRAND THEFT FROM LOCKED AUTO Saturday 05/14/2016 12:00:00 AM \n",
311
+ "9998 AIDED CASE Monday 02/01/2016 12:00:00 AM \n",
312
+ "9999 BATTERY Friday 05/06/2016 12:00:00 AM \n",
313
+ "\n",
314
+ " Time PdDistrict Resolution Address X \\\n",
315
+ "0 21:25 TARAVAL ARREST, BOOKED 600 Block of LINCOLN WY -122.464850 \n",
316
+ "1 19:00 TARAVAL NONE SAN JOSE AV / LAKEVIEW AV -122.450378 \n",
317
+ "2 08:40 INGLESIDE NONE 0 Block of PHELAN AV -122.452290 \n",
318
+ "3 16:12 PARK ARREST, BOOKED 1100 Block of SCOTT ST -122.437099 \n",
319
+ "4 19:34 SOUTHERN ARREST, BOOKED 600 Block of BRANNAN ST -122.399841 \n",
320
+ "... ... ... ... ... ... \n",
321
+ "9995 12:00 SOUTHERN NONE 1100 Block of MISSION ST -122.412834 \n",
322
+ "9996 19:00 BAYVIEW NONE 100 Block of TEXAS ST -122.395812 \n",
323
+ "9997 03:30 CENTRAL NONE BAY ST / VANNESS AV -122.425111 \n",
324
+ "9998 15:23 TARAVAL ARREST, BOOKED 2600 Block of SAN JOSE AV -122.450635 \n",
325
+ "9999 20:30 BAYVIEW NONE 1300 Block of NEWHALL ST -122.391880 \n",
326
+ "\n",
327
+ " Y Location PdId \n",
328
+ "0 37.765888 (37.7658875448653, -122.464850114297) 16052568905051 \n",
329
+ "1 37.716169 (37.7161694707734, -122.450378171697) 16033422006243 \n",
330
+ "2 37.725693 (37.7256933575703, -122.452289660492) 16074005306303 \n",
331
+ "3 37.780352 (37.7803522156893, -122.43709942832) 16061972163010 \n",
332
+ "4 37.775633 (37.7756327864282, -122.399841045579) 16034577262050 \n",
333
+ "... ... ... ... \n",
334
+ "9995 37.777790 (37.7777903094246, -122.412834332129) 17001330128135 \n",
335
+ "9996 37.764531 (37.7645312950153, -122.395812338479) 16057393971010 \n",
336
+ "9997 37.804146 (37.80414615262, -122.425110613231) 16611003806244 \n",
337
+ "9998 37.715772 (37.7157715048394, -122.450634805259) 16009394351040 \n",
338
+ "9999 37.735936 (37.7359364818345, -122.391879837176) 16037321204134 \n",
339
+ "\n",
340
+ "[10000 rows x 14 columns]"
341
+ ]
342
+ },
343
+ "execution_count": 1,
344
+ "metadata": {},
345
+ "output_type": "execute_result"
346
+ }
347
+ ],
348
+ "source": [
349
+ "import pandas as pd\n",
350
+ "from pathlib import Path\n",
351
+ "import solara\n",
352
+ "\n",
353
+ "ROOT = Path(solara.__file__).parent / \"website\" / \"pages\" / \"docs\" / \"content\" / \"04-tutorial\"\n",
354
+ "path = ROOT / Path(\"SF_crime_sample.csv.gz\")\n",
355
+ "url = \"https://github.com/widgetti/solara/raw/master/solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz\"\n",
356
+ "\n",
357
+ "if path.exists():\n",
358
+ " df_crime = pd.read_csv(path)\n",
359
+ "else:\n",
360
+ " df_crime = pd.read_csv(url)\n",
361
+ "\n",
362
+ "df_crime"
363
+ ]
364
+ },
365
+ {
366
+ "cell_type": "markdown",
367
+ "id": "08a9644a",
368
+ "metadata": {},
369
+ "source": [
370
+ "The data looks clean, but since we will work with the `Category` and `PdDistrict` column data, let us convert those columns to title case."
371
+ ]
372
+ },
373
+ {
374
+ "cell_type": "code",
375
+ "execution_count": 2,
376
+ "id": "f3373227",
377
+ "metadata": {},
378
+ "outputs": [
379
+ {
380
+ "data": {
381
+ "text/html": [
382
+ "<div>\n",
383
+ "<style scoped>\n",
384
+ " .dataframe tbody tr th:only-of-type {\n",
385
+ " vertical-align: middle;\n",
386
+ " }\n",
387
+ "\n",
388
+ " .dataframe tbody tr th {\n",
389
+ " vertical-align: top;\n",
390
+ " }\n",
391
+ "\n",
392
+ " .dataframe thead th {\n",
393
+ " text-align: right;\n",
394
+ " }\n",
395
+ "</style>\n",
396
+ "<table border=\"1\" class=\"dataframe\">\n",
397
+ " <thead>\n",
398
+ " <tr style=\"text-align: right;\">\n",
399
+ " <th></th>\n",
400
+ " <th>Unnamed: 0</th>\n",
401
+ " <th>IncidntNum</th>\n",
402
+ " <th>Category</th>\n",
403
+ " <th>Descript</th>\n",
404
+ " <th>DayOfWeek</th>\n",
405
+ " <th>Date</th>\n",
406
+ " <th>Time</th>\n",
407
+ " <th>PdDistrict</th>\n",
408
+ " <th>Resolution</th>\n",
409
+ " <th>Address</th>\n",
410
+ " <th>X</th>\n",
411
+ " <th>Y</th>\n",
412
+ " <th>Location</th>\n",
413
+ " <th>PdId</th>\n",
414
+ " </tr>\n",
415
+ " </thead>\n",
416
+ " <tbody>\n",
417
+ " <tr>\n",
418
+ " <th>0</th>\n",
419
+ " <td>50820</td>\n",
420
+ " <td>160525689</td>\n",
421
+ " <td>Burglary</td>\n",
422
+ " <td>BURGLARY OF STORE, FORCIBLE ENTRY</td>\n",
423
+ " <td>Tuesday</td>\n",
424
+ " <td>06/28/2016 12:00:00 AM</td>\n",
425
+ " <td>21:25</td>\n",
426
+ " <td>Taraval</td>\n",
427
+ " <td>ARREST, BOOKED</td>\n",
428
+ " <td>600 Block of LINCOLN WY</td>\n",
429
+ " <td>-122.464850</td>\n",
430
+ " <td>37.765888</td>\n",
431
+ " <td>(37.7658875448653, -122.464850114297)</td>\n",
432
+ " <td>16052568905051</td>\n",
433
+ " </tr>\n",
434
+ " <tr>\n",
435
+ " <th>1</th>\n",
436
+ " <td>11981</td>\n",
437
+ " <td>160334220</td>\n",
438
+ " <td>Larceny/Theft</td>\n",
439
+ " <td>PETTY THEFT FROM LOCKED AUTO</td>\n",
440
+ " <td>Friday</td>\n",
441
+ " <td>04/22/2016 12:00:00 AM</td>\n",
442
+ " <td>19:00</td>\n",
443
+ " <td>Taraval</td>\n",
444
+ " <td>NONE</td>\n",
445
+ " <td>SAN JOSE AV / LAKEVIEW AV</td>\n",
446
+ " <td>-122.450378</td>\n",
447
+ " <td>37.716169</td>\n",
448
+ " <td>(37.7161694707734, -122.450378171697)</td>\n",
449
+ " <td>16033422006243</td>\n",
450
+ " </tr>\n",
451
+ " <tr>\n",
452
+ " <th>2</th>\n",
453
+ " <td>74626</td>\n",
454
+ " <td>160740053</td>\n",
455
+ " <td>Larceny/Theft</td>\n",
456
+ " <td>PETTY THEFT FROM A BUILDING</td>\n",
457
+ " <td>Monday</td>\n",
458
+ " <td>09/12/2016 12:00:00 AM</td>\n",
459
+ " <td>08:40</td>\n",
460
+ " <td>Ingleside</td>\n",
461
+ " <td>NONE</td>\n",
462
+ " <td>0 Block of PHELAN AV</td>\n",
463
+ " <td>-122.452290</td>\n",
464
+ " <td>37.725693</td>\n",
465
+ " <td>(37.7256933575703, -122.452289660492)</td>\n",
466
+ " <td>16074005306303</td>\n",
467
+ " </tr>\n",
468
+ " <tr>\n",
469
+ " <th>3</th>\n",
470
+ " <td>60776</td>\n",
471
+ " <td>160619721</td>\n",
472
+ " <td>Warrants</td>\n",
473
+ " <td>WARRANT ARREST</td>\n",
474
+ " <td>Monday</td>\n",
475
+ " <td>08/01/2016 12:00:00 AM</td>\n",
476
+ " <td>16:12</td>\n",
477
+ " <td>Park</td>\n",
478
+ " <td>ARREST, BOOKED</td>\n",
479
+ " <td>1100 Block of SCOTT ST</td>\n",
480
+ " <td>-122.437099</td>\n",
481
+ " <td>37.780352</td>\n",
482
+ " <td>(37.7803522156893, -122.43709942832)</td>\n",
483
+ " <td>16061972163010</td>\n",
484
+ " </tr>\n",
485
+ " <tr>\n",
486
+ " <th>4</th>\n",
487
+ " <td>34547</td>\n",
488
+ " <td>160345772</td>\n",
489
+ " <td>Warrants</td>\n",
490
+ " <td>ENROUTE TO OUTSIDE JURISDICTION</td>\n",
491
+ " <td>Wednesday</td>\n",
492
+ " <td>04/27/2016 12:00:00 AM</td>\n",
493
+ " <td>19:34</td>\n",
494
+ " <td>Southern</td>\n",
495
+ " <td>ARREST, BOOKED</td>\n",
496
+ " <td>600 Block of BRANNAN ST</td>\n",
497
+ " <td>-122.399841</td>\n",
498
+ " <td>37.775633</td>\n",
499
+ " <td>(37.7756327864282, -122.399841045579)</td>\n",
500
+ " <td>16034577262050</td>\n",
501
+ " </tr>\n",
502
+ " <tr>\n",
503
+ " <th>...</th>\n",
504
+ " <td>...</td>\n",
505
+ " <td>...</td>\n",
506
+ " <td>...</td>\n",
507
+ " <td>...</td>\n",
508
+ " <td>...</td>\n",
509
+ " <td>...</td>\n",
510
+ " <td>...</td>\n",
511
+ " <td>...</td>\n",
512
+ " <td>...</td>\n",
513
+ " <td>...</td>\n",
514
+ " <td>...</td>\n",
515
+ " <td>...</td>\n",
516
+ " <td>...</td>\n",
517
+ " <td>...</td>\n",
518
+ " </tr>\n",
519
+ " <tr>\n",
520
+ " <th>9995</th>\n",
521
+ " <td>137465</td>\n",
522
+ " <td>170013301</td>\n",
523
+ " <td>Other Offenses</td>\n",
524
+ " <td>HARASSING PHONE CALLS</td>\n",
525
+ " <td>Saturday</td>\n",
526
+ " <td>11/26/2016 12:00:00 AM</td>\n",
527
+ " <td>12:00</td>\n",
528
+ " <td>Southern</td>\n",
529
+ " <td>NONE</td>\n",
530
+ " <td>1100 Block of MISSION ST</td>\n",
531
+ " <td>-122.412834</td>\n",
532
+ " <td>37.777790</td>\n",
533
+ " <td>(37.7777903094246, -122.412834332129)</td>\n",
534
+ " <td>17001330128135</td>\n",
535
+ " </tr>\n",
536
+ " <tr>\n",
537
+ " <th>9996</th>\n",
538
+ " <td>55811</td>\n",
539
+ " <td>160573939</td>\n",
540
+ " <td>Other Offenses</td>\n",
541
+ " <td>LOST/STOLEN LICENSE PLATE</td>\n",
542
+ " <td>Thursday</td>\n",
543
+ " <td>07/07/2016 12:00:00 AM</td>\n",
544
+ " <td>19:00</td>\n",
545
+ " <td>Bayview</td>\n",
546
+ " <td>NONE</td>\n",
547
+ " <td>100 Block of TEXAS ST</td>\n",
548
+ " <td>-122.395812</td>\n",
549
+ " <td>37.764531</td>\n",
550
+ " <td>(37.7645312950153, -122.395812338479)</td>\n",
551
+ " <td>16057393971010</td>\n",
552
+ " </tr>\n",
553
+ " <tr>\n",
554
+ " <th>9997</th>\n",
555
+ " <td>120115</td>\n",
556
+ " <td>166110038</td>\n",
557
+ " <td>Larceny/Theft</td>\n",
558
+ " <td>GRAND THEFT FROM LOCKED AUTO</td>\n",
559
+ " <td>Saturday</td>\n",
560
+ " <td>05/14/2016 12:00:00 AM</td>\n",
561
+ " <td>03:30</td>\n",
562
+ " <td>Central</td>\n",
563
+ " <td>NONE</td>\n",
564
+ " <td>BAY ST / VANNESS AV</td>\n",
565
+ " <td>-122.425111</td>\n",
566
+ " <td>37.804146</td>\n",
567
+ " <td>(37.80414615262, -122.425110613231)</td>\n",
568
+ " <td>16611003806244</td>\n",
569
+ " </tr>\n",
570
+ " <tr>\n",
571
+ " <th>9998</th>\n",
572
+ " <td>5069</td>\n",
573
+ " <td>160093943</td>\n",
574
+ " <td>Non-Criminal</td>\n",
575
+ " <td>AIDED CASE</td>\n",
576
+ " <td>Monday</td>\n",
577
+ " <td>02/01/2016 12:00:00 AM</td>\n",
578
+ " <td>15:23</td>\n",
579
+ " <td>Taraval</td>\n",
580
+ " <td>ARREST, BOOKED</td>\n",
581
+ " <td>2600 Block of SAN JOSE AV</td>\n",
582
+ " <td>-122.450635</td>\n",
583
+ " <td>37.715772</td>\n",
584
+ " <td>(37.7157715048394, -122.450634805259)</td>\n",
585
+ " <td>16009394351040</td>\n",
586
+ " </tr>\n",
587
+ " <tr>\n",
588
+ " <th>9999</th>\n",
589
+ " <td>35667</td>\n",
590
+ " <td>160373212</td>\n",
591
+ " <td>Assault</td>\n",
592
+ " <td>BATTERY</td>\n",
593
+ " <td>Friday</td>\n",
594
+ " <td>05/06/2016 12:00:00 AM</td>\n",
595
+ " <td>20:30</td>\n",
596
+ " <td>Bayview</td>\n",
597
+ " <td>NONE</td>\n",
598
+ " <td>1300 Block of NEWHALL ST</td>\n",
599
+ " <td>-122.391880</td>\n",
600
+ " <td>37.735936</td>\n",
601
+ " <td>(37.7359364818345, -122.391879837176)</td>\n",
602
+ " <td>16037321204134</td>\n",
603
+ " </tr>\n",
604
+ " </tbody>\n",
605
+ "</table>\n",
606
+ "<p>10000 rows × 14 columns</p>\n",
607
+ "</div>"
608
+ ],
609
+ "text/plain": [
610
+ " Unnamed: 0 IncidntNum Category \\\n",
611
+ "0 50820 160525689 Burglary \n",
612
+ "1 11981 160334220 Larceny/Theft \n",
613
+ "2 74626 160740053 Larceny/Theft \n",
614
+ "3 60776 160619721 Warrants \n",
615
+ "4 34547 160345772 Warrants \n",
616
+ "... ... ... ... \n",
617
+ "9995 137465 170013301 Other Offenses \n",
618
+ "9996 55811 160573939 Other Offenses \n",
619
+ "9997 120115 166110038 Larceny/Theft \n",
620
+ "9998 5069 160093943 Non-Criminal \n",
621
+ "9999 35667 160373212 Assault \n",
622
+ "\n",
623
+ " Descript DayOfWeek Date \\\n",
624
+ "0 BURGLARY OF STORE, FORCIBLE ENTRY Tuesday 06/28/2016 12:00:00 AM \n",
625
+ "1 PETTY THEFT FROM LOCKED AUTO Friday 04/22/2016 12:00:00 AM \n",
626
+ "2 PETTY THEFT FROM A BUILDING Monday 09/12/2016 12:00:00 AM \n",
627
+ "3 WARRANT ARREST Monday 08/01/2016 12:00:00 AM \n",
628
+ "4 ENROUTE TO OUTSIDE JURISDICTION Wednesday 04/27/2016 12:00:00 AM \n",
629
+ "... ... ... ... \n",
630
+ "9995 HARASSING PHONE CALLS Saturday 11/26/2016 12:00:00 AM \n",
631
+ "9996 LOST/STOLEN LICENSE PLATE Thursday 07/07/2016 12:00:00 AM \n",
632
+ "9997 GRAND THEFT FROM LOCKED AUTO Saturday 05/14/2016 12:00:00 AM \n",
633
+ "9998 AIDED CASE Monday 02/01/2016 12:00:00 AM \n",
634
+ "9999 BATTERY Friday 05/06/2016 12:00:00 AM \n",
635
+ "\n",
636
+ " Time PdDistrict Resolution Address X \\\n",
637
+ "0 21:25 Taraval ARREST, BOOKED 600 Block of LINCOLN WY -122.464850 \n",
638
+ "1 19:00 Taraval NONE SAN JOSE AV / LAKEVIEW AV -122.450378 \n",
639
+ "2 08:40 Ingleside NONE 0 Block of PHELAN AV -122.452290 \n",
640
+ "3 16:12 Park ARREST, BOOKED 1100 Block of SCOTT ST -122.437099 \n",
641
+ "4 19:34 Southern ARREST, BOOKED 600 Block of BRANNAN ST -122.399841 \n",
642
+ "... ... ... ... ... ... \n",
643
+ "9995 12:00 Southern NONE 1100 Block of MISSION ST -122.412834 \n",
644
+ "9996 19:00 Bayview NONE 100 Block of TEXAS ST -122.395812 \n",
645
+ "9997 03:30 Central NONE BAY ST / VANNESS AV -122.425111 \n",
646
+ "9998 15:23 Taraval ARREST, BOOKED 2600 Block of SAN JOSE AV -122.450635 \n",
647
+ "9999 20:30 Bayview NONE 1300 Block of NEWHALL ST -122.391880 \n",
648
+ "\n",
649
+ " Y Location PdId \n",
650
+ "0 37.765888 (37.7658875448653, -122.464850114297) 16052568905051 \n",
651
+ "1 37.716169 (37.7161694707734, -122.450378171697) 16033422006243 \n",
652
+ "2 37.725693 (37.7256933575703, -122.452289660492) 16074005306303 \n",
653
+ "3 37.780352 (37.7803522156893, -122.43709942832) 16061972163010 \n",
654
+ "4 37.775633 (37.7756327864282, -122.399841045579) 16034577262050 \n",
655
+ "... ... ... ... \n",
656
+ "9995 37.777790 (37.7777903094246, -122.412834332129) 17001330128135 \n",
657
+ "9996 37.764531 (37.7645312950153, -122.395812338479) 16057393971010 \n",
658
+ "9997 37.804146 (37.80414615262, -122.425110613231) 16611003806244 \n",
659
+ "9998 37.715772 (37.7157715048394, -122.450634805259) 16009394351040 \n",
660
+ "9999 37.735936 (37.7359364818345, -122.391879837176) 16037321204134 \n",
661
+ "\n",
662
+ "[10000 rows x 14 columns]"
663
+ ]
664
+ },
665
+ "execution_count": 2,
666
+ "metadata": {},
667
+ "output_type": "execute_result"
668
+ }
669
+ ],
670
+ "source": [
671
+ "df_crime[\"Category\"] = df_crime[\"Category\"].str.title()\n",
672
+ "df_crime[\"PdDistrict\"] = df_crime[\"PdDistrict\"].str.title()\n",
673
+ "df_crime"
674
+ ]
675
+ },
676
+ {
677
+ "cell_type": "markdown",
678
+ "id": "62df988f",
679
+ "metadata": {},
680
+ "source": [
681
+ "Using proper software engineering practices, we write a function that filters a dataframe to contain only the rows that match our chosen districts and categories."
682
+ ]
683
+ },
684
+ {
685
+ "cell_type": "code",
686
+ "execution_count": null,
687
+ "id": "a7d17a84",
688
+ "metadata": {},
689
+ "outputs": [],
690
+ "source": [
691
+ "def crime_filter(df, district_values, category_values):\n",
692
+ " df_dist = df.loc[df[\"PdDistrict\"].isin(district_values)]\n",
693
+ " df_category = df_dist.loc[df_dist[\"Category\"].isin(category_values)]\n",
694
+ " return df_category\n",
695
+ "\n",
696
+ "\n",
697
+ "dff_crime = crime_filter(df_crime, [\"Bayview\", \"Northern\"], [\"Vandalism\", \"Assault\", \"Robbery\"])"
698
+ ]
699
+ },
700
+ {
701
+ "cell_type": "markdown",
702
+ "id": "b0e37cb4",
703
+ "metadata": {},
704
+ "source": [
705
+ "Now, with our filtered dataset, we create two bar charts. We use regular Pandas and Matplotlib, but Seaborn or Plotly would also be appropriate choices."
706
+ ]
707
+ },
708
+ {
709
+ "cell_type": "code",
710
+ "execution_count": 4,
711
+ "id": "3254c59d",
712
+ "metadata": {},
713
+ "outputs": [
714
+ {
715
+ "data": {
716
+ "image/png": "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",
717
+ "text/plain": [
718
+ "<Figure size 1440x720 with 2 Axes>"
719
+ ]
720
+ },
721
+ "metadata": {},
722
+ "output_type": "display_data"
723
+ }
724
+ ],
725
+ "source": [
726
+ "import matplotlib.pyplot as plt\n",
727
+ "\n",
728
+ "\n",
729
+ "def crime_charts(df):\n",
730
+ " cat_unique = df[\"Category\"].value_counts()\n",
731
+ " cat_unique = cat_unique.reset_index()\n",
732
+ "\n",
733
+ " dist_unique = df[\"PdDistrict\"].value_counts()\n",
734
+ " dist_unique = dist_unique.reset_index()\n",
735
+ "\n",
736
+ " fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))\n",
737
+ "\n",
738
+ " ax1.bar(cat_unique[\"Category\"], cat_unique[\"count\"])\n",
739
+ " ax1.set_title(\"Amount of Criminal Case Based on Category\")\n",
740
+ " ax2.bar(dist_unique[\"PdDistrict\"], dist_unique[\"count\"])\n",
741
+ " ax2.set_title(\"Amount of Criminal Case in Selected District\")\n",
742
+ "\n",
743
+ " display(fig)\n",
744
+ " plt.close(fig)\n",
745
+ "\n",
746
+ "\n",
747
+ "crime_charts(dff_crime)"
748
+ ]
749
+ },
750
+ {
751
+ "cell_type": "markdown",
752
+ "id": "0e71ff2f",
753
+ "metadata": {},
754
+ "source": [
755
+ "Since we do not need bidirectional communication (e.g., we do not need to receive events or data from our map), we use Folium to display the locations of the committed crimes on a map. If we do need bidirectional communication, we can also decide to use [ipyleaflet](https://ipyleaflet.readthedocs.io/).\n",
756
+ "\n",
757
+ "Since we cannot display all the data on the map without crashing your browser, we limit it to a maximum of 50 points."
758
+ ]
759
+ },
760
+ {
761
+ "cell_type": "code",
762
+ "execution_count": null,
763
+ "id": "82f1d2f7",
764
+ "metadata": {},
765
+ "outputs": [],
766
+ "source": [
767
+ "import folium\n",
768
+ "import folium.plugins\n",
769
+ "\n",
770
+ "\n",
771
+ "def crime_map(df):\n",
772
+ " latitude = 37.77\n",
773
+ " longitude = -122.42\n",
774
+ "\n",
775
+ " sanfran_map = folium.Map(location=[latitude, longitude], zoom_start=12)\n",
776
+ "\n",
777
+ " incidents = folium.plugins.MarkerCluster().add_to(sanfran_map)\n",
778
+ "\n",
779
+ " # loop through the dataframe and add each data point to the mark cluster\n",
780
+ " for (\n",
781
+ " lat,\n",
782
+ " lng,\n",
783
+ " label,\n",
784
+ " ) in zip(df.Y, df.X, df.Category):\n",
785
+ " folium.Marker(\n",
786
+ " location=[lat, lng],\n",
787
+ " icon=None,\n",
788
+ " popup=label,\n",
789
+ " ).add_to(incidents)\n",
790
+ "\n",
791
+ " # show map\n",
792
+ " display(sanfran_map)\n",
793
+ "\n",
794
+ "\n",
795
+ "crime_map(dff_crime.iloc[0:50, :])"
796
+ ]
797
+ },
798
+ {
799
+ "cell_type": "markdown",
800
+ "id": "b74914fd",
801
+ "metadata": {},
802
+ "source": [
803
+ "## Making our first reactive visualization\n",
804
+ "\n",
805
+ "The above code works nicely, but if we want to explore different types of crimes, we need to modify and run all cells that determine our output manually. Would it not be much better to have a UI with controls determining the filtering and a view displaying the filtered data interactively?\n",
806
+ "\n",
807
+ "Let's start by importing the solara package and creating three reactive variables."
808
+ ]
809
+ },
810
+ {
811
+ "cell_type": "code",
812
+ "execution_count": null,
813
+ "id": "3e7ea361",
814
+ "metadata": {},
815
+ "outputs": [],
816
+ "source": [
817
+ "import solara\n",
818
+ "\n",
819
+ "districts = solara.reactive([\"Bayview\", \"Northern\"])\n",
820
+ "categories = solara.reactive([\"Vandalism\", \"Assault\", \"Robbery\"])\n",
821
+ "limit = solara.reactive(100)"
822
+ ]
823
+ },
824
+ {
825
+ "cell_type": "markdown",
826
+ "id": "28622c20",
827
+ "metadata": {},
828
+ "source": [
829
+ "A reactive variable is a container around a value (like an int, string, or list) that allows the UI to listen to changes automatically. Any change to your_reactive_variable.value will be picked up by Solara components that use them so that they can automatically redraw or update themselves.\n",
830
+ "\n",
831
+ "Let us now create our first component (View), which filters the data based on the reactive variables and shows the map and the charts. Solara supports the display mechanism of Jupyter so that we can use our previously defined functions."
832
+ ]
833
+ },
834
+ {
835
+ "cell_type": "code",
836
+ "execution_count": null,
837
+ "id": "56055643",
838
+ "metadata": {},
839
+ "outputs": [],
840
+ "source": [
841
+ "@solara.component\n",
842
+ "def View():\n",
843
+ " dff = crime_filter(df_crime, districts.value, categories.value)\n",
844
+ " row_count = len(dff)\n",
845
+ " if row_count > limit.value:\n",
846
+ " solara.Warning(f\"Only showing the first {limit.value} of {row_count:,} crimes on map\")\n",
847
+ " crime_map(dff.iloc[: limit.value])\n",
848
+ " if row_count > 0:\n",
849
+ " crime_charts(dff)\n",
850
+ " else:\n",
851
+ " solara.Warning(\"You filtered out all the data, no charts shown\")\n",
852
+ "\n",
853
+ "\n",
854
+ "View()"
855
+ ]
856
+ },
857
+ {
858
+ "cell_type": "markdown",
859
+ "id": "0b05c1db",
860
+ "metadata": {},
861
+ "source": [
862
+ "Note that some UI parts (like the warning and the charts) are conditional. Solara will automatically find out what to add, remove, or update without you having to do this manually. Solara is declarative (similar to ReactJS) but also reactive. If we change the reactive variables, Solara will see those changes and notify the component instances that use its value.\n",
863
+ "\n",
864
+ "If we run the next lines of code in our notebook, our View will automatically update."
865
+ ]
866
+ },
867
+ {
868
+ "cell_type": "code",
869
+ "execution_count": null,
870
+ "id": "fef5d187",
871
+ "metadata": {},
872
+ "outputs": [],
873
+ "source": [
874
+ "limit.value = 70\n",
875
+ "districts.value = [\"Soutern\", \"Northern\"]"
876
+ ]
877
+ },
878
+ {
879
+ "cell_type": "markdown",
880
+ "id": "8822d100",
881
+ "metadata": {},
882
+ "source": [
883
+ "We can now explore our data much faster since we don't need to re-run the cells that depend on it.\n",
884
+ "\n",
885
+ "Solara's reactive and declarative nature makes it scalable to much larger applications than regular ipywidgets, where keeping the UI in sync and adding, removing, and updating widgets is a manual and bug-prone process."
886
+ ]
887
+ },
888
+ {
889
+ "cell_type": "markdown",
890
+ "id": "917a1501",
891
+ "metadata": {},
892
+ "source": [
893
+ "## Adding controls\n",
894
+ "\n",
895
+ "We created a declarative and reactive mini app in our notebook, but we still need to manually modify the values by executing a code cell in our Notebook. Now, let us create a UI to control it. All Solara input components support reactive variables. This means that controlling a reactive variable using a UI element is often a one-liner."
896
+ ]
897
+ },
898
+ {
899
+ "cell_type": "code",
900
+ "execution_count": null,
901
+ "id": "c78010ec",
902
+ "metadata": {},
903
+ "outputs": [],
904
+ "source": [
905
+ "solara.SelectMultiple(\"District\", all_values=[str(k) for k in df_crime[\"PdDistrict\"].unique().tolist()], values=districts)"
906
+ ]
907
+ },
908
+ {
909
+ "cell_type": "markdown",
910
+ "id": "74484e0c",
911
+ "metadata": {},
912
+ "source": [
913
+ "Whow, that was simple! We can now easily change the filter and see the results update. Lets do this for all our reactive variables, and put them into a single component."
914
+ ]
915
+ },
916
+ {
917
+ "cell_type": "code",
918
+ "execution_count": null,
919
+ "id": "18290364",
920
+ "metadata": {
921
+ "scrolled": true
922
+ },
923
+ "outputs": [],
924
+ "source": [
925
+ "@solara.component\n",
926
+ "def Controls():\n",
927
+ " solara.SelectMultiple(\"District\", all_values=[str(k) for k in df_crime[\"PdDistrict\"].unique().tolist()], values=districts)\n",
928
+ " solara.SelectMultiple(\"Category\", all_values=[str(k) for k in df_crime[\"Category\"].unique().tolist()], values=categories)\n",
929
+ " solara.Text(\"Maximum number of rows to show on map\")\n",
930
+ " solara.SliderInt(\"\", value=limit, min=1, max=1000)\n",
931
+ "\n",
932
+ "\n",
933
+ "Controls()"
934
+ ]
935
+ },
936
+ {
937
+ "cell_type": "markdown",
938
+ "id": "0c1ac606",
939
+ "metadata": {},
940
+ "source": [
941
+ "Note that the reactive variables are bi-directional, meaning that if you change it in the UI elements, it gets reflected on the Python code!"
942
+ ]
943
+ },
944
+ {
945
+ "cell_type": "code",
946
+ "execution_count": null,
947
+ "id": "0ca68fe8",
948
+ "metadata": {},
949
+ "outputs": [],
950
+ "source": [
951
+ "# Note that we can read AND write reactive variables\n",
952
+ "categories.value = [*categories.value, \"Warrants\"]"
953
+ ]
954
+ },
955
+ {
956
+ "cell_type": "markdown",
957
+ "id": "5af8e2b8",
958
+ "metadata": {},
959
+ "source": [
960
+ "## The final dashboard\n",
961
+ "\n",
962
+ "We now have two parts of our UI in separate cells. This can be an amazing experience when developing in a notebook, as it flows naturally in the data exploration process while writing your notebook.\n",
963
+ "\n",
964
+ "However, your end user will probably want something more coherent. The components we created are perfectly reusable, so we put them together in a single UI."
965
+ ]
966
+ },
967
+ {
968
+ "cell_type": "code",
969
+ "execution_count": null,
970
+ "id": "af686391",
971
+ "metadata": {},
972
+ "outputs": [],
973
+ "source": [
974
+ "@solara.component\n",
975
+ "def Page():\n",
976
+ " with solara.Sidebar():\n",
977
+ " Controls()\n",
978
+ " View()\n",
979
+ "\n",
980
+ "\n",
981
+ "Page()"
982
+ ]
983
+ },
984
+ {
985
+ "cell_type": "markdown",
986
+ "id": "e07fd010",
987
+ "metadata": {},
988
+ "source": [
989
+ "## Conclusions\n",
990
+ "\n",
991
+ "Using Solara, you created an interactive dashboard within a Jupyter notebook. Your Solara components are declarative, and when using reactive variables also reactive. Whether you change a reactive variables via code or the UI elements, your visualizations and map update automatically.\n",
992
+ "\n",
993
+ "Your dashboard prototype now runs in your Jupyter notebook environment, but we still have a few steps to we want to take. In our next tutorial, we will focus on deploying our notebook, without making any code changes. In our third tutorial we will expand our dashboard with a few more components and focus on creating a more advanced layout.\n",
994
+ "\n",
995
+ "\n",
996
+ "\n"
997
+ ]
998
+ }
999
+ ],
1000
+ "metadata": {
1001
+ "kernelspec": {
1002
+ "display_name": "Python 3 (ipykernel)",
1003
+ "language": "python",
1004
+ "name": "python3"
1005
+ },
1006
+ "language_info": {
1007
+ "codemirror_mode": {
1008
+ "name": "ipython",
1009
+ "version": 3
1010
+ },
1011
+ "file_extension": ".py",
1012
+ "mimetype": "text/x-python",
1013
+ "name": "python",
1014
+ "nbconvert_exporter": "python",
1015
+ "pygments_lexer": "ipython3",
1016
+ "version": "3.9.16"
1017
+ }
1018
+ },
1019
+ "nbformat": 4,
1020
+ "nbformat_minor": 5
1021
+ }