streamlit-data-editor-plus 0.1.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 (456) hide show
  1. streamlit/__init__.py +292 -0
  2. streamlit/__main__.py +20 -0
  3. streamlit/auth_util.py +583 -0
  4. streamlit/cli_util.py +107 -0
  5. streamlit/column_config.py +60 -0
  6. streamlit/commands/__init__.py +13 -0
  7. streamlit/commands/echo.py +128 -0
  8. streamlit/commands/execution_control.py +318 -0
  9. streamlit/commands/logo.py +250 -0
  10. streamlit/commands/navigation.py +430 -0
  11. streamlit/commands/page_config.py +335 -0
  12. streamlit/components/__init__.py +13 -0
  13. streamlit/components/lib/__init__.py +13 -0
  14. streamlit/components/lib/local_component_registry.py +84 -0
  15. streamlit/components/types/__init__.py +13 -0
  16. streamlit/components/types/base_component_registry.py +99 -0
  17. streamlit/components/types/base_custom_component.py +158 -0
  18. streamlit/components/v1/__init__.py +29 -0
  19. streamlit/components/v1/component_arrow.py +141 -0
  20. streamlit/components/v1/component_registry.py +147 -0
  21. streamlit/components/v1/components.py +38 -0
  22. streamlit/components/v1/custom_component.py +239 -0
  23. streamlit/components/v2/__init__.py +531 -0
  24. streamlit/components/v2/bidi_component/__init__.py +20 -0
  25. streamlit/components/v2/bidi_component/constants.py +29 -0
  26. streamlit/components/v2/bidi_component/main.py +537 -0
  27. streamlit/components/v2/bidi_component/serialization.py +272 -0
  28. streamlit/components/v2/bidi_component/state.py +92 -0
  29. streamlit/components/v2/component_definition_resolver.py +143 -0
  30. streamlit/components/v2/component_file_watcher.py +403 -0
  31. streamlit/components/v2/component_manager.py +439 -0
  32. streamlit/components/v2/component_manifest_handler.py +122 -0
  33. streamlit/components/v2/component_path_utils.py +233 -0
  34. streamlit/components/v2/component_registry.py +426 -0
  35. streamlit/components/v2/get_bidi_component_manager.py +51 -0
  36. streamlit/components/v2/manifest_scanner.py +620 -0
  37. streamlit/components/v2/presentation.py +198 -0
  38. streamlit/components/v2/types.py +317 -0
  39. streamlit/config.py +2977 -0
  40. streamlit/config_option.py +319 -0
  41. streamlit/config_util.py +887 -0
  42. streamlit/connections/__init__.py +32 -0
  43. streamlit/connections/base_connection.py +215 -0
  44. streamlit/connections/snowflake_connection.py +765 -0
  45. streamlit/connections/snowpark_connection.py +213 -0
  46. streamlit/connections/sql_connection.py +427 -0
  47. streamlit/connections/util.py +97 -0
  48. streamlit/cursor.py +314 -0
  49. streamlit/dataframe_util.py +1434 -0
  50. streamlit/delta_generator.py +696 -0
  51. streamlit/delta_generator_singletons.py +243 -0
  52. streamlit/deprecation_util.py +253 -0
  53. streamlit/development.py +21 -0
  54. streamlit/elements/__init__.py +13 -0
  55. streamlit/elements/alert.py +341 -0
  56. streamlit/elements/arrow.py +1065 -0
  57. streamlit/elements/balloons.py +47 -0
  58. streamlit/elements/bokeh_chart.py +75 -0
  59. streamlit/elements/code.py +154 -0
  60. streamlit/elements/deck_gl_json_chart.py +627 -0
  61. streamlit/elements/dialog_decorator.py +320 -0
  62. streamlit/elements/empty.py +130 -0
  63. streamlit/elements/exception.py +370 -0
  64. streamlit/elements/form.py +481 -0
  65. streamlit/elements/graphviz_chart.py +226 -0
  66. streamlit/elements/heading.py +407 -0
  67. streamlit/elements/help.py +565 -0
  68. streamlit/elements/html.py +189 -0
  69. streamlit/elements/iframe.py +245 -0
  70. streamlit/elements/image.py +221 -0
  71. streamlit/elements/json.py +171 -0
  72. streamlit/elements/layouts.py +1534 -0
  73. streamlit/elements/lib/__init__.py +13 -0
  74. streamlit/elements/lib/built_in_chart_utils.py +1316 -0
  75. streamlit/elements/lib/color_util.py +272 -0
  76. streamlit/elements/lib/column_config_utils.py +555 -0
  77. streamlit/elements/lib/column_types.py +2699 -0
  78. streamlit/elements/lib/dialog.py +212 -0
  79. streamlit/elements/lib/dicttools.py +152 -0
  80. streamlit/elements/lib/file_uploader_utils.py +91 -0
  81. streamlit/elements/lib/form_utils.py +77 -0
  82. streamlit/elements/lib/image_utils.py +447 -0
  83. streamlit/elements/lib/js_number.py +105 -0
  84. streamlit/elements/lib/layout_utils.py +325 -0
  85. streamlit/elements/lib/mutable_expander_container.py +73 -0
  86. streamlit/elements/lib/mutable_popover_container.py +73 -0
  87. streamlit/elements/lib/mutable_status_container.py +194 -0
  88. streamlit/elements/lib/mutable_tab_container.py +73 -0
  89. streamlit/elements/lib/options_selector_utils.py +480 -0
  90. streamlit/elements/lib/pandas_styler_utils.py +302 -0
  91. streamlit/elements/lib/policies.py +198 -0
  92. streamlit/elements/lib/shortcut_utils.py +152 -0
  93. streamlit/elements/lib/streamlit_plotly_theme.py +206 -0
  94. streamlit/elements/lib/subtitle_utils.py +175 -0
  95. streamlit/elements/lib/utils.py +274 -0
  96. streamlit/elements/map.py +526 -0
  97. streamlit/elements/markdown.py +529 -0
  98. streamlit/elements/media.py +843 -0
  99. streamlit/elements/metric.py +529 -0
  100. streamlit/elements/pdf.py +190 -0
  101. streamlit/elements/plotly_chart.py +779 -0
  102. streamlit/elements/progress.py +172 -0
  103. streamlit/elements/pyplot.py +240 -0
  104. streamlit/elements/snow.py +47 -0
  105. streamlit/elements/space.py +121 -0
  106. streamlit/elements/spinner.py +152 -0
  107. streamlit/elements/table.py +240 -0
  108. streamlit/elements/text.py +113 -0
  109. streamlit/elements/toast.py +175 -0
  110. streamlit/elements/vega_charts.py +2510 -0
  111. streamlit/elements/widgets/__init__.py +13 -0
  112. streamlit/elements/widgets/audio_input.py +334 -0
  113. streamlit/elements/widgets/button.py +1559 -0
  114. streamlit/elements/widgets/button_group.py +1061 -0
  115. streamlit/elements/widgets/camera_input.py +281 -0
  116. streamlit/elements/widgets/chat.py +1028 -0
  117. streamlit/elements/widgets/checkbox.py +420 -0
  118. streamlit/elements/widgets/color_picker.py +329 -0
  119. streamlit/elements/widgets/data_editor.py +1168 -0
  120. streamlit/elements/widgets/data_editor_plus.py +902 -0
  121. streamlit/elements/widgets/feedback.py +322 -0
  122. streamlit/elements/widgets/file_uploader.py +592 -0
  123. streamlit/elements/widgets/multiselect.py +654 -0
  124. streamlit/elements/widgets/number_input.py +719 -0
  125. streamlit/elements/widgets/radio.py +551 -0
  126. streamlit/elements/widgets/select_slider.py +568 -0
  127. streamlit/elements/widgets/selectbox.py +680 -0
  128. streamlit/elements/widgets/slider.py +1093 -0
  129. streamlit/elements/widgets/text_widgets.py +779 -0
  130. streamlit/elements/widgets/time_widgets.py +1712 -0
  131. streamlit/elements/write.py +585 -0
  132. streamlit/emojis.py +34 -0
  133. streamlit/env_util.py +56 -0
  134. streamlit/error_util.py +112 -0
  135. streamlit/errors.py +680 -0
  136. streamlit/external/__init__.py +13 -0
  137. streamlit/external/langchain/__init__.py +23 -0
  138. streamlit/external/langchain/streamlit_callback_handler.py +410 -0
  139. streamlit/file_util.py +266 -0
  140. streamlit/git_util.py +200 -0
  141. streamlit/hello/__init__.py +13 -0
  142. streamlit/hello/__pycache__/__init__.cpython-312.pyc +0 -0
  143. streamlit/hello/__pycache__/streamlit_app.cpython-312.pyc +0 -0
  144. streamlit/hello/animation_demo.py +82 -0
  145. streamlit/hello/dataframe_demo.py +71 -0
  146. streamlit/hello/hello.py +46 -0
  147. streamlit/hello/mapping_demo.py +113 -0
  148. streamlit/hello/plotting_demo.py +62 -0
  149. streamlit/hello/streamlit_app.py +57 -0
  150. streamlit/hello/utils.py +30 -0
  151. streamlit/logger.py +129 -0
  152. streamlit/material_icon_names.py +25 -0
  153. streamlit/navigation/__init__.py +13 -0
  154. streamlit/navigation/page.py +365 -0
  155. streamlit/net_util.py +125 -0
  156. streamlit/path_security.py +98 -0
  157. streamlit/platform.py +31 -0
  158. streamlit/proto/Alert_pb2.py +28 -0
  159. streamlit/proto/Alert_pb2.pyi +100 -0
  160. streamlit/proto/AppPage_pb2.py +25 -0
  161. streamlit/proto/AppPage_pb2.pyi +75 -0
  162. streamlit/proto/ArrowData_pb2.py +27 -0
  163. streamlit/proto/ArrowData_pb2.pyi +103 -0
  164. streamlit/proto/ArrowNamedDataSet_pb2.py +26 -0
  165. streamlit/proto/ArrowNamedDataSet_pb2.pyi +65 -0
  166. streamlit/proto/AudioInput_pb2.py +26 -0
  167. streamlit/proto/AudioInput_pb2.pyi +72 -0
  168. streamlit/proto/Audio_pb2.py +26 -0
  169. streamlit/proto/Audio_pb2.pyi +75 -0
  170. streamlit/proto/AuthRedirect_pb2.py +25 -0
  171. streamlit/proto/AuthRedirect_pb2.pyi +48 -0
  172. streamlit/proto/AutoRerun_pb2.py +25 -0
  173. streamlit/proto/AutoRerun_pb2.pyi +52 -0
  174. streamlit/proto/BackMsg_pb2.py +29 -0
  175. streamlit/proto/BackMsg_pb2.pyi +132 -0
  176. streamlit/proto/Balloons_pb2.py +25 -0
  177. streamlit/proto/Balloons_pb2.pyi +48 -0
  178. streamlit/proto/BidiComponent_pb2.py +32 -0
  179. streamlit/proto/BidiComponent_pb2.pyi +176 -0
  180. streamlit/proto/Block_pb2.py +62 -0
  181. streamlit/proto/Block_pb2.pyi +518 -0
  182. streamlit/proto/ButtonGroup_pb2.py +32 -0
  183. streamlit/proto/ButtonGroup_pb2.pyi +157 -0
  184. streamlit/proto/ButtonLikeIconPosition_pb2.py +25 -0
  185. streamlit/proto/ButtonLikeIconPosition_pb2.pyi +47 -0
  186. streamlit/proto/Button_pb2.py +26 -0
  187. streamlit/proto/Button_pb2.pyi +82 -0
  188. streamlit/proto/CameraInput_pb2.py +26 -0
  189. streamlit/proto/CameraInput_pb2.pyi +66 -0
  190. streamlit/proto/ChatInput_pb2.py +27 -0
  191. streamlit/proto/ChatInput_pb2.pyi +113 -0
  192. streamlit/proto/Checkbox_pb2.py +28 -0
  193. streamlit/proto/Checkbox_pb2.pyi +99 -0
  194. streamlit/proto/ClientState_pb2.py +28 -0
  195. streamlit/proto/ClientState_pb2.pyi +137 -0
  196. streamlit/proto/Code_pb2.py +25 -0
  197. streamlit/proto/Code_pb2.pyi +59 -0
  198. streamlit/proto/ColorPicker_pb2.py +26 -0
  199. streamlit/proto/ColorPicker_pb2.pyi +82 -0
  200. streamlit/proto/Common_pb2.py +49 -0
  201. streamlit/proto/Common_pb2.pyi +325 -0
  202. streamlit/proto/Components_pb2.py +33 -0
  203. streamlit/proto/Components_pb2.pyi +196 -0
  204. streamlit/proto/Dataframe_pb2.py +30 -0
  205. streamlit/proto/Dataframe_pb2.pyi +172 -0
  206. streamlit/proto/DateInput_pb2.py +26 -0
  207. streamlit/proto/DateInput_pb2.pyi +91 -0
  208. streamlit/proto/DateTimeInput_pb2.py +26 -0
  209. streamlit/proto/DateTimeInput_pb2.pyi +94 -0
  210. streamlit/proto/DeckGlJsonChart_pb2.py +27 -0
  211. streamlit/proto/DeckGlJsonChart_pb2.pyi +91 -0
  212. streamlit/proto/Delta_pb2.py +29 -0
  213. streamlit/proto/Delta_pb2.pyi +82 -0
  214. streamlit/proto/DownloadButton_pb2.py +26 -0
  215. streamlit/proto/DownloadButton_pb2.pyi +92 -0
  216. streamlit/proto/Element_pb2.py +81 -0
  217. streamlit/proto/Element_pb2.pyi +348 -0
  218. streamlit/proto/Empty_pb2.py +25 -0
  219. streamlit/proto/Empty_pb2.pyi +42 -0
  220. streamlit/proto/Exception_pb2.py +26 -0
  221. streamlit/proto/Exception_pb2.pyi +88 -0
  222. streamlit/proto/Favicon_pb2.py +25 -0
  223. streamlit/proto/Favicon_pb2.pyi +47 -0
  224. streamlit/proto/Feedback_pb2.py +27 -0
  225. streamlit/proto/Feedback_pb2.pyi +93 -0
  226. streamlit/proto/FileUploader_pb2.py +26 -0
  227. streamlit/proto/FileUploader_pb2.pyi +90 -0
  228. streamlit/proto/ForwardMsg_pb2.py +48 -0
  229. streamlit/proto/ForwardMsg_pb2.pyi +296 -0
  230. streamlit/proto/GapSize_pb2.py +27 -0
  231. streamlit/proto/GapSize_pb2.pyi +82 -0
  232. streamlit/proto/GitInfo_pb2.py +27 -0
  233. streamlit/proto/GitInfo_pb2.pyi +84 -0
  234. streamlit/proto/GraphVizChart_pb2.py +25 -0
  235. streamlit/proto/GraphVizChart_pb2.pyi +56 -0
  236. streamlit/proto/Heading_pb2.py +25 -0
  237. streamlit/proto/Heading_pb2.pyi +63 -0
  238. streamlit/proto/HeightConfig_pb2.py +25 -0
  239. streamlit/proto/HeightConfig_pb2.pyi +61 -0
  240. streamlit/proto/Help_pb2.py +27 -0
  241. streamlit/proto/Help_pb2.pyi +104 -0
  242. streamlit/proto/Html_pb2.py +25 -0
  243. streamlit/proto/Html_pb2.pyi +52 -0
  244. streamlit/proto/IFrame_pb2.py +25 -0
  245. streamlit/proto/IFrame_pb2.pyi +68 -0
  246. streamlit/proto/Image_pb2.py +27 -0
  247. streamlit/proto/Image_pb2.pyi +73 -0
  248. streamlit/proto/Json_pb2.py +25 -0
  249. streamlit/proto/Json_pb2.pyi +63 -0
  250. streamlit/proto/LabelVisibility_pb2.py +27 -0
  251. streamlit/proto/LabelVisibility_pb2.pyi +69 -0
  252. streamlit/proto/LinkButton_pb2.py +26 -0
  253. streamlit/proto/LinkButton_pb2.pyi +76 -0
  254. streamlit/proto/Logo_pb2.py +27 -0
  255. streamlit/proto/Logo_pb2.pyi +82 -0
  256. streamlit/proto/Markdown_pb2.py +27 -0
  257. streamlit/proto/Markdown_pb2.pyi +83 -0
  258. streamlit/proto/Metric_pb2.py +32 -0
  259. streamlit/proto/Metric_pb2.pyi +145 -0
  260. streamlit/proto/MetricsEvent_pb2.py +28 -0
  261. streamlit/proto/MetricsEvent_pb2.pyi +224 -0
  262. streamlit/proto/MultiSelect_pb2.py +26 -0
  263. streamlit/proto/MultiSelect_pb2.pyi +108 -0
  264. streamlit/proto/Navigation_pb2.py +28 -0
  265. streamlit/proto/Navigation_pb2.pyi +89 -0
  266. streamlit/proto/NewSession_pb2.py +47 -0
  267. streamlit/proto/NewSession_pb2.pyi +753 -0
  268. streamlit/proto/NumberInput_pb2.py +28 -0
  269. streamlit/proto/NumberInput_pb2.pyi +138 -0
  270. streamlit/proto/PageConfig_pb2.py +33 -0
  271. streamlit/proto/PageConfig_pb2.pyi +179 -0
  272. streamlit/proto/PageInfo_pb2.py +25 -0
  273. streamlit/proto/PageInfo_pb2.pyi +50 -0
  274. streamlit/proto/PageLink_pb2.py +26 -0
  275. streamlit/proto/PageLink_pb2.pyi +80 -0
  276. streamlit/proto/PageNotFound_pb2.py +25 -0
  277. streamlit/proto/PageNotFound_pb2.pyi +49 -0
  278. streamlit/proto/PageProfile_pb2.py +29 -0
  279. streamlit/proto/PageProfile_pb2.pyi +146 -0
  280. streamlit/proto/ParentMessage_pb2.py +25 -0
  281. streamlit/proto/ParentMessage_pb2.pyi +53 -0
  282. streamlit/proto/PlotlyChart_pb2.py +27 -0
  283. streamlit/proto/PlotlyChart_pb2.pyi +96 -0
  284. streamlit/proto/Progress_pb2.py +25 -0
  285. streamlit/proto/Progress_pb2.pyi +50 -0
  286. streamlit/proto/Radio_pb2.py +26 -0
  287. streamlit/proto/Radio_pb2.pyi +104 -0
  288. streamlit/proto/RootContainer_pb2.py +25 -0
  289. streamlit/proto/RootContainer_pb2.pyi +56 -0
  290. streamlit/proto/Selectbox_pb2.py +26 -0
  291. streamlit/proto/Selectbox_pb2.pyi +107 -0
  292. streamlit/proto/SessionEvent_pb2.py +26 -0
  293. streamlit/proto/SessionEvent_pb2.pyi +72 -0
  294. streamlit/proto/SessionStatus_pb2.py +25 -0
  295. streamlit/proto/SessionStatus_pb2.pyi +64 -0
  296. streamlit/proto/Skeleton_pb2.py +27 -0
  297. streamlit/proto/Skeleton_pb2.pyi +75 -0
  298. streamlit/proto/Slider_pb2.py +30 -0
  299. streamlit/proto/Slider_pb2.pyi +161 -0
  300. streamlit/proto/Snow_pb2.py +25 -0
  301. streamlit/proto/Snow_pb2.pyi +48 -0
  302. streamlit/proto/Space_pb2.py +25 -0
  303. streamlit/proto/Space_pb2.pyi +48 -0
  304. streamlit/proto/Spinner_pb2.py +25 -0
  305. streamlit/proto/Spinner_pb2.pyi +56 -0
  306. streamlit/proto/Table_pb2.py +28 -0
  307. streamlit/proto/Table_pb2.pyi +83 -0
  308. streamlit/proto/TextAlignmentConfig_pb2.py +27 -0
  309. streamlit/proto/TextAlignmentConfig_pb2.pyi +69 -0
  310. streamlit/proto/TextArea_pb2.py +26 -0
  311. streamlit/proto/TextArea_pb2.pyi +97 -0
  312. streamlit/proto/TextInput_pb2.py +28 -0
  313. streamlit/proto/TextInput_pb2.pyi +124 -0
  314. streamlit/proto/Text_pb2.py +25 -0
  315. streamlit/proto/Text_pb2.pyi +53 -0
  316. streamlit/proto/TimeInput_pb2.py +26 -0
  317. streamlit/proto/TimeInput_pb2.pyi +86 -0
  318. streamlit/proto/Toast_pb2.py +25 -0
  319. streamlit/proto/Toast_pb2.pyi +64 -0
  320. streamlit/proto/Transient_pb2.py +26 -0
  321. streamlit/proto/Transient_pb2.pyi +53 -0
  322. streamlit/proto/VegaLiteChart_pb2.py +27 -0
  323. streamlit/proto/VegaLiteChart_pb2.pyi +92 -0
  324. streamlit/proto/Video_pb2.py +30 -0
  325. streamlit/proto/Video_pb2.pyi +129 -0
  326. streamlit/proto/WidgetStates_pb2.py +31 -0
  327. streamlit/proto/WidgetStates_pb2.pyi +157 -0
  328. streamlit/proto/WidthConfig_pb2.py +25 -0
  329. streamlit/proto/WidthConfig_pb2.pyi +61 -0
  330. streamlit/proto/__init__.py +15 -0
  331. streamlit/proto/openmetrics_data_model_pb2.py +58 -0
  332. streamlit/proto/openmetrics_data_model_pb2.pyi +558 -0
  333. streamlit/py.typed +0 -0
  334. streamlit/runtime/__init__.py +50 -0
  335. streamlit/runtime/app_session.py +1302 -0
  336. streamlit/runtime/caching/__init__.py +118 -0
  337. streamlit/runtime/caching/cache_data_api.py +775 -0
  338. streamlit/runtime/caching/cache_errors.py +143 -0
  339. streamlit/runtime/caching/cache_resource_api.py +756 -0
  340. streamlit/runtime/caching/cache_type.py +33 -0
  341. streamlit/runtime/caching/cache_utils.py +601 -0
  342. streamlit/runtime/caching/cached_message_replay.py +290 -0
  343. streamlit/runtime/caching/hashing.py +655 -0
  344. streamlit/runtime/caching/legacy_cache_api.py +170 -0
  345. streamlit/runtime/caching/storage/__init__.py +29 -0
  346. streamlit/runtime/caching/storage/cache_storage_protocol.py +236 -0
  347. streamlit/runtime/caching/storage/dummy_cache_storage.py +60 -0
  348. streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py +159 -0
  349. streamlit/runtime/caching/storage/local_disk_cache_storage.py +223 -0
  350. streamlit/runtime/caching/ttl_cleanup_cache.py +85 -0
  351. streamlit/runtime/connection_factory.py +498 -0
  352. streamlit/runtime/context.py +449 -0
  353. streamlit/runtime/context_util.py +49 -0
  354. streamlit/runtime/credentials.py +355 -0
  355. streamlit/runtime/download_data_util.py +53 -0
  356. streamlit/runtime/forward_msg_cache.py +101 -0
  357. streamlit/runtime/forward_msg_queue.py +263 -0
  358. streamlit/runtime/fragment.py +441 -0
  359. streamlit/runtime/media_file_manager.py +409 -0
  360. streamlit/runtime/media_file_storage.py +143 -0
  361. streamlit/runtime/memory_media_file_storage.py +201 -0
  362. streamlit/runtime/memory_session_storage.py +77 -0
  363. streamlit/runtime/memory_uploaded_file_manager.py +149 -0
  364. streamlit/runtime/metrics_util.py +612 -0
  365. streamlit/runtime/pages_manager.py +160 -0
  366. streamlit/runtime/runtime.py +775 -0
  367. streamlit/runtime/runtime_util.py +108 -0
  368. streamlit/runtime/script_data.py +46 -0
  369. streamlit/runtime/scriptrunner/__init__.py +38 -0
  370. streamlit/runtime/scriptrunner/exec_code.py +167 -0
  371. streamlit/runtime/scriptrunner/magic.py +277 -0
  372. streamlit/runtime/scriptrunner/magic_funcs.py +32 -0
  373. streamlit/runtime/scriptrunner/script_cache.py +89 -0
  374. streamlit/runtime/scriptrunner/script_runner.py +805 -0
  375. streamlit/runtime/scriptrunner_utils/__init__.py +19 -0
  376. streamlit/runtime/scriptrunner_utils/exceptions.py +44 -0
  377. streamlit/runtime/scriptrunner_utils/script_requests.py +311 -0
  378. streamlit/runtime/scriptrunner_utils/script_run_context.py +277 -0
  379. streamlit/runtime/secrets.py +533 -0
  380. streamlit/runtime/session_manager.py +430 -0
  381. streamlit/runtime/state/__init__.py +47 -0
  382. streamlit/runtime/state/common.py +256 -0
  383. streamlit/runtime/state/presentation.py +85 -0
  384. streamlit/runtime/state/query_params.py +767 -0
  385. streamlit/runtime/state/query_params_proxy.py +222 -0
  386. streamlit/runtime/state/safe_session_state.py +146 -0
  387. streamlit/runtime/state/session_state.py +1299 -0
  388. streamlit/runtime/state/session_state_proxy.py +153 -0
  389. streamlit/runtime/state/widgets.py +197 -0
  390. streamlit/runtime/stats.py +356 -0
  391. streamlit/runtime/theme_util.py +148 -0
  392. streamlit/runtime/uploaded_file_manager.py +152 -0
  393. streamlit/runtime/websocket_session_manager.py +301 -0
  394. streamlit/source_util.py +97 -0
  395. streamlit/starlette.py +34 -0
  396. streamlit/string_util.py +264 -0
  397. streamlit/temporary_directory.py +67 -0
  398. streamlit/testing/__init__.py +13 -0
  399. streamlit/testing/v1/__init__.py +17 -0
  400. streamlit/testing/v1/app_test.py +1089 -0
  401. streamlit/testing/v1/element_tree.py +2272 -0
  402. streamlit/testing/v1/local_script_runner.py +176 -0
  403. streamlit/testing/v1/util.py +61 -0
  404. streamlit/time_util.py +73 -0
  405. streamlit/type_util.py +480 -0
  406. streamlit/url_util.py +120 -0
  407. streamlit/user_info.py +698 -0
  408. streamlit/util.py +108 -0
  409. streamlit/vendor/__init__.py +0 -0
  410. streamlit/vendor/pympler/__init__.py +0 -0
  411. streamlit/vendor/pympler/asizeof.py +2870 -0
  412. streamlit/version.py +18 -0
  413. streamlit/watcher/__init__.py +28 -0
  414. streamlit/watcher/event_based_path_watcher.py +500 -0
  415. streamlit/watcher/folder_black_list.py +82 -0
  416. streamlit/watcher/local_sources_watcher.py +297 -0
  417. streamlit/watcher/path_watcher.py +244 -0
  418. streamlit/watcher/polling_path_watcher.py +138 -0
  419. streamlit/watcher/util.py +223 -0
  420. streamlit/web/__init__.py +13 -0
  421. streamlit/web/bootstrap.py +463 -0
  422. streamlit/web/cache_storage_manager_config.py +38 -0
  423. streamlit/web/cli.py +465 -0
  424. streamlit/web/server/__init__.py +30 -0
  425. streamlit/web/server/app_discovery.py +422 -0
  426. streamlit/web/server/app_static_file_handler.py +102 -0
  427. streamlit/web/server/authlib_tornado_integration.py +125 -0
  428. streamlit/web/server/bidi_component_request_handler.py +193 -0
  429. streamlit/web/server/browser_websocket_handler.py +341 -0
  430. streamlit/web/server/component_file_utils.py +105 -0
  431. streamlit/web/server/component_request_handler.py +107 -0
  432. streamlit/web/server/media_file_handler.py +148 -0
  433. streamlit/web/server/oauth_authlib_routes.py +314 -0
  434. streamlit/web/server/oidc_mixin.py +138 -0
  435. streamlit/web/server/routes.py +257 -0
  436. streamlit/web/server/server.py +555 -0
  437. streamlit/web/server/server_util.py +235 -0
  438. streamlit/web/server/starlette/__init__.py +23 -0
  439. streamlit/web/server/starlette/starlette_app.py +541 -0
  440. streamlit/web/server/starlette/starlette_app_utils.py +306 -0
  441. streamlit/web/server/starlette/starlette_auth_routes.py +584 -0
  442. streamlit/web/server/starlette/starlette_gzip_middleware.py +121 -0
  443. streamlit/web/server/starlette/starlette_path_security_middleware.py +97 -0
  444. streamlit/web/server/starlette/starlette_routes.py +884 -0
  445. streamlit/web/server/starlette/starlette_server.py +496 -0
  446. streamlit/web/server/starlette/starlette_server_config.py +55 -0
  447. streamlit/web/server/starlette/starlette_static_routes.py +181 -0
  448. streamlit/web/server/starlette/starlette_websocket.py +560 -0
  449. streamlit/web/server/stats_request_handler.py +116 -0
  450. streamlit/web/server/upload_file_request_handler.py +158 -0
  451. streamlit/web/server/websocket_headers.py +56 -0
  452. streamlit_data_editor_plus-0.1.0.dist-info/METADATA +76 -0
  453. streamlit_data_editor_plus-0.1.0.dist-info/RECORD +456 -0
  454. streamlit_data_editor_plus-0.1.0.dist-info/WHEEL +5 -0
  455. streamlit_data_editor_plus-0.1.0.dist-info/entry_points.txt +2 -0
  456. streamlit_data_editor_plus-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1316 @@
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Utilities for our built-in charts commands."""
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from datetime import date
21
+ from enum import Enum
22
+ from typing import TYPE_CHECKING, Any, Final, Literal, TypeAlias, TypedDict, cast
23
+
24
+ from streamlit import dataframe_util, type_util
25
+ from streamlit.elements.lib.color_util import (
26
+ Color,
27
+ is_builtin_color_name,
28
+ is_color_like,
29
+ is_color_tuple_like,
30
+ is_hex_color_like,
31
+ to_css_color,
32
+ )
33
+ from streamlit.errors import Error, StreamlitAPIException
34
+
35
+ if TYPE_CHECKING:
36
+ from collections.abc import Collection, Hashable, Sequence
37
+
38
+ import altair as alt
39
+ import pandas as pd
40
+
41
+ from streamlit.dataframe_util import Data
42
+ from streamlit.elements.lib.layout_utils import (
43
+ Height,
44
+ Width,
45
+ )
46
+
47
+ VegaLiteType: TypeAlias = Literal["quantitative", "ordinal", "temporal", "nominal"]
48
+ ChartStackType: TypeAlias = Literal["normalize", "center", "layered"]
49
+
50
+ # Threshold for applying hover event throttling on large datasets.
51
+ # For datasets with more points than this threshold, hover events are throttled
52
+ # to 16ms (~60fps) to improve performance.
53
+ _LARGE_DATASET_POINT_THRESHOLD: Final = 1000
54
+
55
+
56
+ class PrepDataColumns(TypedDict):
57
+ """Columns used for the prep_data step in Altair Arrow charts."""
58
+
59
+ x_column: str | None
60
+ y_column_list: list[str]
61
+ color_column: str | None
62
+ size_column: str | None
63
+ sort_column: str | None
64
+
65
+
66
+ @dataclass
67
+ class AddRowsMetadata:
68
+ """Metadata needed by add_rows on native charts.
69
+
70
+ This class is used to pass some important info to add_rows.
71
+ """
72
+
73
+ chart_command: str
74
+ last_index: Hashable | None
75
+ columns: PrepDataColumns
76
+ # Chart styling properties
77
+ color: str | Color | list[Color] | None = None
78
+ width: Width | None = None
79
+ height: Height | None = None
80
+ use_container_width: bool | None = None
81
+ # Only applicable for bar & area charts
82
+ stack: bool | ChartStackType | None = None
83
+ # Only applicable for bar charts
84
+ horizontal: bool = False
85
+ sort: bool | str = False
86
+
87
+
88
+ class ChartType(Enum):
89
+ AREA: Final = {"mark_type": "area", "command": "area_chart"}
90
+ VERTICAL_BAR: Final = {
91
+ "mark_type": "bar",
92
+ "command": "bar_chart",
93
+ "horizontal": False,
94
+ }
95
+ HORIZONTAL_BAR: Final = {
96
+ "mark_type": "bar",
97
+ "command": "bar_chart",
98
+ "horizontal": True,
99
+ }
100
+ LINE: Final = {"mark_type": "line", "command": "line_chart"}
101
+ SCATTER: Final = {"mark_type": "circle", "command": "scatter_chart"}
102
+
103
+
104
+ # Color and size legends need different title paddings in order for them
105
+ # to be vertically aligned.
106
+ #
107
+ # NOTE: I don't think it's possible to *perfectly* align the size and
108
+ # color legends in all instances, since the "size" circles vary in size based
109
+ # on the data, and their container is top-aligned with the color container. But
110
+ # through trial-and-error I found this value to be a good enough middle ground.
111
+ #
112
+ # NOTE #2: In theory, we could move COLOR_LEGEND_SETTINGS into
113
+ # ArrowVegaLiteChart/CustomTheme.tsx, but this would impact existing behavior.
114
+ # (See https://github.com/streamlit/streamlit/pull/7164#discussion_r1307707345)
115
+ _COLOR_LEGEND_SETTINGS: Final = {"titlePadding": 5, "offset": 5, "orient": "bottom"}
116
+ _SIZE_LEGEND_SETTINGS: Final = {"titlePadding": 0.5, "offset": 5, "orient": "bottom"}
117
+
118
+ # User-readable names to give the index and melted columns.
119
+ _SEPARATED_INDEX_COLUMN_TITLE: Final = "index"
120
+ _MELTED_Y_COLUMN_TITLE: Final = "value"
121
+ _MELTED_COLOR_COLUMN_TITLE: Final = "color"
122
+
123
+ # Crazy internal (non-user-visible) names for the index and melted columns, in order to
124
+ # avoid collision with existing column names.
125
+ _PROTECTION_SUFFIX: Final = " -- streamlit-generated"
126
+ _SEPARATED_INDEX_COLUMN_NAME: Final = _SEPARATED_INDEX_COLUMN_TITLE + _PROTECTION_SUFFIX
127
+ _MELTED_Y_COLUMN_NAME: Final = _MELTED_Y_COLUMN_TITLE + _PROTECTION_SUFFIX
128
+ _MELTED_COLOR_COLUMN_NAME: Final = _MELTED_COLOR_COLUMN_TITLE + _PROTECTION_SUFFIX
129
+
130
+ # Name we use for a column we know doesn't exist in the data, to address a Vega-Lite
131
+ # rendering bug
132
+ # where empty charts need x, y encodings set in order to take up space.
133
+ _NON_EXISTENT_COLUMN_NAME: Final = "DOES_NOT_EXIST" + _PROTECTION_SUFFIX
134
+
135
+
136
+ def maybe_raise_stack_warning(
137
+ stack: bool | ChartStackType | None, command: str | None, docs_link: str
138
+ ) -> None:
139
+ # Check that the stack parameter is valid, raise more informative error if not
140
+ if stack not in {None, True, False, "normalize", "center", "layered"}:
141
+ raise StreamlitAPIException(
142
+ f"Invalid value for stack parameter: {stack}. Stack must be one of True, "
143
+ 'False, "normalize", "center", "layered" or None. See documentation '
144
+ f"for `{command}` [here]({docs_link}) for more information."
145
+ )
146
+
147
+
148
+ def generate_chart(
149
+ chart_type: ChartType,
150
+ data: Data | None,
151
+ x_from_user: str | None = None,
152
+ y_from_user: str | Sequence[str] | None = None,
153
+ x_axis_label: str | None = None,
154
+ y_axis_label: str | None = None,
155
+ color_from_user: str | Color | list[Color] | None = None,
156
+ size_from_user: str | float | None = None,
157
+ width: Width | None = None,
158
+ height: Height | None = None,
159
+ use_container_width: bool | None = None,
160
+ # Bar & Area charts only:
161
+ stack: bool | ChartStackType | None = None,
162
+ # Bar charts only:
163
+ horizontal: bool = False,
164
+ sort_from_user: bool | str = False,
165
+ ) -> tuple[alt.Chart | alt.LayerChart, AddRowsMetadata]:
166
+ """Function to use the chart's type, data columns and indices to figure out the
167
+ chart's spec.
168
+ """
169
+ import altair as alt
170
+
171
+ df = dataframe_util.convert_anything_to_pandas_df(data, ensure_copy=True)
172
+
173
+ # From now on, use "df" instead of "data". Deleting "data" to guarantee we follow
174
+ # this.
175
+ del data
176
+
177
+ # Convert arguments received from the user to things Vega-Lite understands.
178
+ # Get name of column to use for x.
179
+ x_column = _parse_x_column(df, x_from_user)
180
+ # Get name of columns to use for y.
181
+ y_column_list = _parse_y_columns(df, y_from_user, x_column)
182
+ # Get name of column to use for color, or constant value to use. Any/both could
183
+ # be None.
184
+ color_column, color_value = _parse_generic_column(df, color_from_user)
185
+ # Get name of column to use for size, or constant value to use. Any/both could
186
+ # be None.
187
+ size_column, size_value = _parse_generic_column(df, size_from_user)
188
+ # Get name of column to use for sort.
189
+ sort_column = _parse_sort_column(df, sort_from_user)
190
+
191
+ # Store some info so we can use it in add_rows.
192
+ add_rows_metadata = AddRowsMetadata(
193
+ # The st command that was used to generate this chart.
194
+ chart_command=chart_type.value["command"],
195
+ # The last index of df so we can adjust the input df in add_rows:
196
+ last_index=_last_index_for_melted_dataframes(df),
197
+ # This is the input to prep_data (except for the df):
198
+ columns={
199
+ "x_column": x_column,
200
+ "y_column_list": y_column_list,
201
+ "color_column": color_column,
202
+ "size_column": size_column,
203
+ "sort_column": sort_column,
204
+ },
205
+ # Chart styling properties
206
+ color=color_from_user,
207
+ width=width,
208
+ height=height,
209
+ use_container_width=use_container_width,
210
+ stack=stack,
211
+ horizontal=horizontal,
212
+ sort=sort_from_user,
213
+ )
214
+
215
+ # At this point, all foo_column variables are either None/empty or contain actual
216
+ # columns that are guaranteed to exist.
217
+ df, x_column, y_column, color_column, size_column, sort_column = _prep_data(
218
+ df, x_column, y_column_list, color_column, size_column, sort_column
219
+ )
220
+
221
+ # At this point, x_column is only None if user did not provide one AND df is empty.
222
+
223
+ # Get x and y encodings
224
+ x_encoding, y_encoding = _get_axis_encodings(
225
+ df,
226
+ chart_type,
227
+ x_column,
228
+ y_column,
229
+ x_from_user,
230
+ y_from_user,
231
+ x_axis_label,
232
+ y_axis_label,
233
+ stack,
234
+ sort_from_user,
235
+ )
236
+
237
+ chart_width = width if isinstance(width, int) else None
238
+ chart_height = height if isinstance(height, int) else None
239
+
240
+ # Create a Chart with x and y encodings.
241
+ chart = alt.Chart(
242
+ data=df,
243
+ mark=chart_type.value["mark_type"],
244
+ width=chart_width or 0,
245
+ height=chart_height or 0,
246
+ ).encode(
247
+ x=x_encoding,
248
+ y=y_encoding,
249
+ )
250
+
251
+ # Offset encoding only works for Altair >= 5.0.0
252
+ is_altair_version_5_or_greater = not type_util.is_altair_version_less_than("5.0.0")
253
+ # Set up offset encoding (creates grouped/non-stacked bar charts, so only applicable
254
+ # when stack=False).
255
+ if is_altair_version_5_or_greater and stack is False and color_column is not None:
256
+ x_offset, y_offset = _get_offset_encoding(chart_type, color_column)
257
+ chart = chart.encode(xOffset=x_offset, yOffset=y_offset)
258
+
259
+ # Set up opacity encoding.
260
+ opacity_enc = _get_opacity_encoding(chart_type, stack, color_column)
261
+ if opacity_enc is not None:
262
+ chart = chart.encode(opacity=opacity_enc)
263
+
264
+ # Set up color encoding.
265
+ color_enc = _get_color_encoding(
266
+ df, color_value, color_column, y_column_list, color_from_user
267
+ )
268
+ if color_enc is not None:
269
+ chart = chart.encode(color=color_enc)
270
+
271
+ # Set up size encoding.
272
+ size_enc = _get_size_encoding(chart_type, size_column, size_value)
273
+ if size_enc is not None:
274
+ chart = chart.encode(size=size_enc)
275
+
276
+ # Set up tooltip encoding.
277
+ if x_column is not None and y_column is not None:
278
+ chart = chart.encode(
279
+ tooltip=_get_tooltip_encoding(
280
+ x_column,
281
+ y_column,
282
+ size_column,
283
+ color_column,
284
+ color_enc,
285
+ )
286
+ )
287
+
288
+ if (
289
+ chart_type is ChartType.LINE
290
+ and x_column is not None
291
+ # This is using the new selection API that was added in Altair 5.0.0
292
+ and is_altair_version_5_or_greater
293
+ ):
294
+ return _add_improved_hover_tooltips(
295
+ chart, x_column, chart_width, chart_height, len(df)
296
+ ).interactive(), add_rows_metadata
297
+
298
+ return chart.interactive(), add_rows_metadata
299
+
300
+
301
+ def _add_improved_hover_tooltips(
302
+ chart: alt.Chart,
303
+ x_column: str,
304
+ width: int | None,
305
+ height: int | None,
306
+ data_point_count: int,
307
+ ) -> alt.LayerChart:
308
+ """Adds improved hover tooltips to an existing line chart.
309
+
310
+ This implementation uses a three-layer approach for better performance:
311
+ 1. Base chart layer: The original line chart
312
+ 2. Detection layer: Invisible points for detecting the nearest point on hover
313
+ 3. Highlight layer: Only renders the selected point(s) using transform_filter
314
+
315
+ The filter-based approach is more efficient than using conditional opacity
316
+ because it only renders the selected point(s) rather than evaluating opacity
317
+ for every single data point on each hover event.
318
+ """
319
+
320
+ import altair as alt
321
+
322
+ # Throttle hover events for large datasets to 16ms (~60fps) to improve performance.
323
+ # For smaller datasets, use standard mousemove without throttling.
324
+ hover_event = (
325
+ "mousemove{16}"
326
+ if data_point_count > _LARGE_DATASET_POINT_THRESHOLD
327
+ else "mousemove"
328
+ )
329
+
330
+ # Create a selection that chooses the nearest point & selects based on x-value.
331
+ # Uses mouseleave instead of mouseout/pointerout for more reliable hover clearing
332
+ # (mouseout fires when moving over child elements like tooltips).
333
+ nearest = alt.selection_point(
334
+ nearest=True,
335
+ on=hover_event,
336
+ fields=[x_column],
337
+ empty=False,
338
+ clear="mouseleave",
339
+ )
340
+
341
+ # Detection layer: Invisible points for detecting the nearest point.
342
+ # This layer is needed because selections must be attached to a mark.
343
+ detection_points = chart.mark_point(opacity=0).add_params(nearest)
344
+
345
+ # Highlight layer: Only renders the selected point(s) using transform_filter.
346
+ # This is more efficient than conditional opacity because it only renders
347
+ # the filtered data (typically 1-2 points) rather than all points.
348
+ highlighted_points = chart.mark_point(filled=True, size=65).transform_filter(
349
+ nearest
350
+ )
351
+
352
+ layer_chart = (
353
+ alt.layer(chart, detection_points, highlighted_points)
354
+ .configure_legend(symbolType="stroke")
355
+ .properties(
356
+ width=width or 0,
357
+ height=height or 0,
358
+ )
359
+ )
360
+
361
+ return cast("alt.LayerChart", layer_chart)
362
+
363
+
364
+ def prep_chart_data_for_add_rows(
365
+ data: Data,
366
+ add_rows_metadata: AddRowsMetadata,
367
+ ) -> tuple[Data, AddRowsMetadata]:
368
+ """Prepares the data for add_rows on our built-in charts.
369
+
370
+ This includes aspects like conversion of the data to Pandas DataFrame,
371
+ changes to the index, and melting the data if needed.
372
+ """
373
+ import pandas as pd
374
+
375
+ df = dataframe_util.convert_anything_to_pandas_df(data)
376
+
377
+ # Make range indices start at last_index.
378
+ if isinstance(df.index, pd.RangeIndex):
379
+ old_step = _get_pandas_index_attr(df, "step")
380
+
381
+ # We have to drop the predefined index
382
+ df = df.reset_index(drop=True)
383
+
384
+ old_stop = _get_pandas_index_attr(df, "stop")
385
+
386
+ if old_step is None or old_stop is None:
387
+ raise StreamlitAPIException("'RangeIndex' object has no attribute 'step'")
388
+
389
+ start = add_rows_metadata.last_index + old_step
390
+ stop = add_rows_metadata.last_index + old_step + old_stop
391
+
392
+ df.index = pd.RangeIndex(start=start, stop=stop, step=old_step)
393
+ add_rows_metadata.last_index = stop - 1
394
+
395
+ out_data, *_ = _prep_data(
396
+ df,
397
+ x_column=add_rows_metadata.columns["x_column"],
398
+ y_column_list=add_rows_metadata.columns["y_column_list"],
399
+ color_column=add_rows_metadata.columns["color_column"],
400
+ size_column=add_rows_metadata.columns["size_column"],
401
+ sort_column=add_rows_metadata.columns["sort_column"],
402
+ )
403
+
404
+ return out_data, add_rows_metadata
405
+
406
+
407
+ def _infer_vegalite_type(
408
+ data: pd.Series[Any],
409
+ ) -> VegaLiteType:
410
+ """
411
+ From an array-like input, infer the correct vega typecode
412
+ ('ordinal', 'nominal', 'quantitative', or 'temporal').
413
+
414
+ Parameters
415
+ ----------
416
+ data: Numpy array or Pandas Series
417
+ """
418
+ # The code below is copied from Altair, and slightly modified.
419
+ # We copy this code here so we don't depend on private Altair functions.
420
+ # Source: https://github.com/altair-viz/altair/blob/62ca5e37776f5cecb27e83c1fbd5d685a173095d/altair/utils/core.py#L193
421
+
422
+ from pandas.api.types import infer_dtype
423
+
424
+ # STREAMLIT MOD: I'm using infer_dtype directly here, rather than using Altair's
425
+ # wrapper. Their wrapper is only there to support Pandas < 0.20, but Streamlit
426
+ # requires Pandas 1.3.
427
+ typ = infer_dtype(data)
428
+
429
+ if typ in {
430
+ "floating",
431
+ "mixed-integer-float",
432
+ "integer",
433
+ "mixed-integer",
434
+ "complex",
435
+ }:
436
+ return "quantitative"
437
+
438
+ if typ == "categorical" and data.cat.ordered:
439
+ # The original code returns a tuple here:
440
+ # return ("ordinal", data.cat.categories.tolist()) # noqa: ERA001
441
+ # But returning the tuple here isn't compatible with our
442
+ # built-in chart implementation. And it also doesn't seem to be necessary.
443
+ # Altair already extracts the correct sort order somewhere else.
444
+ # More info about the issue here: https://github.com/streamlit/streamlit/issues/7776
445
+ return "ordinal"
446
+ if typ in {"string", "bytes", "categorical", "boolean", "mixed", "unicode"}:
447
+ return "nominal"
448
+ if typ in {
449
+ "datetime",
450
+ "datetime64",
451
+ "timedelta",
452
+ "timedelta64",
453
+ "date",
454
+ "time",
455
+ "period",
456
+ }:
457
+ return "temporal"
458
+ # STREAMLIT MOD: I commented this out since Streamlit doesn't use warnings.warn.
459
+ # > warnings.warn(
460
+ # > "I don't know how to infer vegalite type from '{}'. "
461
+ # > "Defaulting to nominal.".format(typ),
462
+ # > stacklevel=1,
463
+ # > )
464
+ return "nominal"
465
+
466
+
467
+ def _get_pandas_index_attr(
468
+ data: pd.DataFrame | pd.Series[Any],
469
+ attr: str,
470
+ ) -> Any | None:
471
+ return getattr(data.index, attr, None)
472
+
473
+
474
+ def _prep_data(
475
+ df: pd.DataFrame,
476
+ x_column: str | None,
477
+ y_column_list: list[str],
478
+ color_column: str | None,
479
+ size_column: str | None,
480
+ sort_column: str | None = None,
481
+ ) -> tuple[pd.DataFrame, str | None, str | None, str | None, str | None, str | None]:
482
+ """Prepares the data for charting. This is also used in add_rows.
483
+
484
+ Returns the prepared dataframe and the new names of the x column (taking the index
485
+ reset into consideration) and y, color, and size columns.
486
+ """
487
+
488
+ # If y is provided, but x is not, we'll use the index as x.
489
+ # So we need to pull the index into its own column.
490
+ x_column = _maybe_reset_index_in_place(df, x_column, y_column_list)
491
+
492
+ # Drop columns we're not using.
493
+ selected_data = _drop_unused_columns(
494
+ df, x_column, color_column, size_column, sort_column, *y_column_list
495
+ )
496
+
497
+ # Maybe convert color to Vega colors.
498
+ _maybe_convert_color_column_in_place(selected_data, color_column)
499
+
500
+ # Make sure all columns have string names.
501
+ (
502
+ x_column,
503
+ y_column_list,
504
+ color_column,
505
+ size_column,
506
+ sort_column,
507
+ ) = _convert_col_names_to_str_in_place(
508
+ selected_data, x_column, y_column_list, color_column, size_column, sort_column
509
+ )
510
+
511
+ # Maybe melt data from wide format into long format.
512
+ melted_data, y_column, color_column = _maybe_melt(
513
+ selected_data, x_column, y_column_list, color_column, size_column, sort_column
514
+ )
515
+
516
+ # Return the data, but also the new names to use for x, y, and color.
517
+ return melted_data, x_column, y_column, color_column, size_column, sort_column
518
+
519
+
520
+ def _last_index_for_melted_dataframes(
521
+ data: pd.DataFrame,
522
+ ) -> Hashable | None:
523
+ return cast("Hashable", data.index[-1]) if data.index.size > 0 else None
524
+
525
+
526
+ def _is_date_column(df: pd.DataFrame, name: str | None) -> bool:
527
+ """True if the column with the given name stores datetime.date values.
528
+
529
+ This function just checks the first value in the given column, so
530
+ it's meaningful only for columns whose values all share the same type.
531
+
532
+ Parameters
533
+ ----------
534
+ df : pd.DataFrame
535
+ name : str
536
+ The column name
537
+
538
+ Returns
539
+ -------
540
+ bool
541
+
542
+ """
543
+ if name is None:
544
+ return False
545
+
546
+ column = df[name]
547
+ if column.size == 0:
548
+ return False
549
+
550
+ return isinstance(column.iat[0], date)
551
+
552
+
553
+ def _melt_data(
554
+ df: pd.DataFrame,
555
+ columns_to_leave_alone: list[str],
556
+ columns_to_melt: list[str] | None,
557
+ new_y_column_name: str,
558
+ new_color_column_name: str,
559
+ ) -> pd.DataFrame:
560
+ """Converts a wide-format dataframe to a long-format dataframe.
561
+
562
+ You can find more info about melting on the Pandas documentation:
563
+ https://pandas.pydata.org/docs/reference/api/pandas.melt.html
564
+
565
+ Parameters
566
+ ----------
567
+ df : pd.DataFrame
568
+ The dataframe to melt.
569
+ columns_to_leave_alone : list[str]
570
+ The columns to leave as they are.
571
+ columns_to_melt : list[str]
572
+ The columns to melt.
573
+ new_y_column_name : str
574
+ The name of the new column that will store the values of the melted columns.
575
+ new_color_column_name : str
576
+ The name of column that will store the original column names.
577
+
578
+ Returns
579
+ -------
580
+ pd.DataFrame
581
+ The melted dataframe.
582
+
583
+
584
+ Examples
585
+ --------
586
+ >>> import pandas as pd
587
+ >>> df = pd.DataFrame(
588
+ ... {
589
+ ... "a": [1, 2, 3],
590
+ ... "b": [4, 5, 6],
591
+ ... "c": [7, 8, 9],
592
+ ... }
593
+ ... )
594
+ >>> _melt_data(df, ["a"], ["b", "c"], "value", "color")
595
+ >>> a color value
596
+ >>> 0 1 b 4
597
+ >>> 1 2 b 5
598
+ >>> 2 3 b 6
599
+ >>> ...
600
+
601
+ """
602
+ import pandas as pd
603
+ from pandas.api.types import infer_dtype
604
+
605
+ melted_df = pd.melt(
606
+ df,
607
+ id_vars=columns_to_leave_alone,
608
+ value_vars=columns_to_melt,
609
+ var_name=new_color_column_name,
610
+ value_name=new_y_column_name,
611
+ )
612
+
613
+ y_series = melted_df[new_y_column_name]
614
+ if (
615
+ # After melting columns of different dtypes, the result has object dtype.
616
+ # In pandas 3.0+, melting columns with the same StringDtype keeps StringDtype,
617
+ # so this check correctly identifies only truly mixed-type scenarios.
618
+ y_series.dtype == "object"
619
+ and "mixed" in infer_dtype(y_series)
620
+ and len(y_series.unique()) > 100
621
+ ):
622
+ raise StreamlitAPIException(
623
+ "The columns used for rendering the chart contain too many values with "
624
+ "mixed types. Please select the columns manually via the y parameter."
625
+ )
626
+
627
+ # Arrow has problems with object types after melting two different dtypes
628
+ # > pyarrow.lib.ArrowTypeError: "Expected a <TYPE> object, got a object"
629
+ return dataframe_util.fix_arrow_incompatible_column_types(
630
+ melted_df,
631
+ selected_columns=[
632
+ *columns_to_leave_alone,
633
+ new_color_column_name,
634
+ new_y_column_name,
635
+ ],
636
+ )
637
+
638
+
639
+ def _maybe_reset_index_in_place(
640
+ df: pd.DataFrame, x_column: str | None, y_column_list: list[str]
641
+ ) -> str | None:
642
+ if x_column is None and len(y_column_list) > 0:
643
+ if df.index.name is None:
644
+ # Pick column name that is unlikely to collide with user-given names.
645
+ x_column = _SEPARATED_INDEX_COLUMN_NAME
646
+ else:
647
+ # Reuse index's name for the new column.
648
+ x_column = str(df.index.name)
649
+
650
+ df.index.name = x_column
651
+ df.reset_index(inplace=True) # noqa: PD002
652
+
653
+ return x_column
654
+
655
+
656
+ def _drop_unused_columns(df: pd.DataFrame, *column_names: str | None) -> pd.DataFrame:
657
+ """Returns a subset of df, selecting only column_names that aren't None."""
658
+
659
+ # We can't just call set(col_names) because sets don't have stable ordering,
660
+ # which means tests that depend on ordering will fail.
661
+ # Performance-wise, it's not a problem, though, since this function is only ever
662
+ # used on very small lists.
663
+ seen = set()
664
+ keep = []
665
+
666
+ for x in column_names:
667
+ if x is None:
668
+ continue
669
+ if x in seen:
670
+ continue
671
+ seen.add(x)
672
+ keep.append(x)
673
+
674
+ return df[keep] # type: ignore[no-any-return, unused-ignore]
675
+
676
+
677
+ def _maybe_convert_color_column_in_place(
678
+ df: pd.DataFrame, color_column: str | None
679
+ ) -> None:
680
+ """If needed, convert color column to a format Vega understands."""
681
+ if color_column is None or len(df[color_column]) == 0:
682
+ return
683
+
684
+ first_color_datum = df[color_column].iat[0]
685
+
686
+ if is_hex_color_like(first_color_datum): # type: ignore[arg-type]
687
+ # Hex is already CSS-valid.
688
+ pass
689
+ elif is_color_tuple_like(first_color_datum): # type: ignore[arg-type]
690
+ # Tuples need to be converted to CSS-valid.
691
+ df.loc[:, color_column] = df[color_column].apply(to_css_color)
692
+ else:
693
+ # Other kinds of colors columns (i.e. pure numbers or nominal strings) shouldn't
694
+ # be converted since they are treated by Vega-Lite as sequential or categorical
695
+ # colors.
696
+ pass
697
+
698
+
699
+ def _convert_col_names_to_str_in_place(
700
+ df: pd.DataFrame,
701
+ x_column: str | None,
702
+ y_column_list: list[str],
703
+ color_column: str | None,
704
+ size_column: str | None,
705
+ sort_column: str | None,
706
+ ) -> tuple[str | None, list[str], str | None, str | None, str | None]:
707
+ """Converts column names to strings, since Vega-Lite does not accept ints, etc."""
708
+ import pandas as pd
709
+
710
+ column_names = list(df.columns) # list() converts RangeIndex, etc, to regular list.
711
+ str_column_names = [str(c) for c in column_names]
712
+ df.columns = pd.Index(str_column_names)
713
+
714
+ return (
715
+ None if x_column is None else str(x_column),
716
+ [str(c) for c in y_column_list],
717
+ None if color_column is None else str(color_column),
718
+ None if size_column is None else str(size_column),
719
+ None if sort_column is None else str(sort_column),
720
+ )
721
+
722
+
723
+ def _parse_generic_column(
724
+ df: pd.DataFrame, column_or_value: Any
725
+ ) -> tuple[str | None, Any]:
726
+ if isinstance(column_or_value, str) and column_or_value in df.columns:
727
+ column_name = column_or_value
728
+ value = None
729
+ else:
730
+ column_name = None
731
+ value = column_or_value
732
+
733
+ return column_name, value
734
+
735
+
736
+ def _parse_x_column(df: pd.DataFrame, x_from_user: str | None) -> str | None:
737
+ if x_from_user is None:
738
+ return None
739
+
740
+ if isinstance(x_from_user, str):
741
+ if x_from_user not in df.columns:
742
+ raise StreamlitColumnNotFoundError(df, x_from_user)
743
+
744
+ return x_from_user
745
+
746
+ raise StreamlitAPIException(
747
+ "x parameter should be a column name (str) or None to use the "
748
+ f" dataframe's index. Value given: {x_from_user} "
749
+ f"(type {type(x_from_user)})"
750
+ )
751
+
752
+
753
+ def _parse_sort_column(df: pd.DataFrame, sort_from_user: bool | str) -> str | None:
754
+ if sort_from_user is False or sort_from_user is True:
755
+ return None
756
+
757
+ sort_column = sort_from_user.removeprefix("-")
758
+ if sort_column not in df.columns:
759
+ raise StreamlitColumnNotFoundError(df, sort_column)
760
+
761
+ return sort_column
762
+
763
+
764
+ def _parse_y_columns(
765
+ df: pd.DataFrame,
766
+ y_from_user: str | Sequence[str] | None,
767
+ x_column: str | None,
768
+ ) -> list[str]:
769
+ y_column_list: list[str] = []
770
+
771
+ if y_from_user is None:
772
+ y_column_list = list(df.columns)
773
+
774
+ elif isinstance(y_from_user, str):
775
+ y_column_list = [y_from_user]
776
+
777
+ else:
778
+ y_column_list = [
779
+ str(col) for col in dataframe_util.convert_anything_to_list(y_from_user)
780
+ ]
781
+
782
+ for col in y_column_list:
783
+ if col not in df.columns:
784
+ raise StreamlitColumnNotFoundError(df, col)
785
+
786
+ # y_column_list should only include x_column when user explicitly asked for it.
787
+ if x_column in y_column_list and (not y_from_user or x_column not in y_from_user):
788
+ y_column_list.remove(x_column)
789
+
790
+ return y_column_list
791
+
792
+
793
+ def _get_offset_encoding(
794
+ chart_type: ChartType,
795
+ color_column: str | None,
796
+ ) -> tuple[alt.XOffset, alt.YOffset]:
797
+ # Vega's Offset encoding channel is used to create grouped/non-stacked bar charts
798
+ import altair as alt
799
+
800
+ x_offset = alt.XOffset()
801
+ y_offset = alt.YOffset()
802
+
803
+ _color_column: str | alt.typing.Optional[Any] = (
804
+ color_column if color_column is not None else alt.Undefined
805
+ )
806
+
807
+ if chart_type is ChartType.VERTICAL_BAR:
808
+ x_offset = alt.XOffset(field=_color_column)
809
+ elif chart_type is ChartType.HORIZONTAL_BAR:
810
+ y_offset = alt.YOffset(field=_color_column)
811
+
812
+ return x_offset, y_offset
813
+
814
+
815
+ def _get_opacity_encoding(
816
+ chart_type: ChartType,
817
+ stack: bool | ChartStackType | None,
818
+ color_column: str | None,
819
+ ) -> alt.OpacityValue | None:
820
+ import altair as alt
821
+
822
+ # Opacity set to 0.7 for all area charts
823
+ if color_column and chart_type == ChartType.AREA:
824
+ return alt.OpacityValue(0.7)
825
+
826
+ # Layered bar chart
827
+ if color_column and stack == "layered":
828
+ return alt.OpacityValue(0.7)
829
+
830
+ return None
831
+
832
+
833
+ def _get_axis_config(df: pd.DataFrame, column_name: str | None, grid: bool) -> alt.Axis:
834
+ import altair as alt
835
+ from pandas.api.types import is_integer_dtype
836
+
837
+ if column_name is not None and is_integer_dtype(df[column_name]):
838
+ # Use a max tick size of 1 for integer columns (prevents zoom into
839
+ # float numbers) and deactivate grid lines for x-axis
840
+ return alt.Axis(tickMinStep=1, grid=grid)
841
+
842
+ return alt.Axis(grid=grid)
843
+
844
+
845
+ def _maybe_melt(
846
+ df: pd.DataFrame,
847
+ x_column: str | None,
848
+ y_column_list: list[str],
849
+ color_column: str | None,
850
+ size_column: str | None,
851
+ sort_column: str | None,
852
+ ) -> tuple[pd.DataFrame, str | None, str | None]:
853
+ """If multiple columns are set for y, melt the dataframe into long format."""
854
+ y_column: str | None = None
855
+
856
+ if len(y_column_list) == 0:
857
+ y_column = None
858
+ elif len(y_column_list) == 1:
859
+ y_column = y_column_list[0]
860
+ elif x_column is not None:
861
+ # Pick column names that are unlikely to collide with user-given names.
862
+ y_column = _MELTED_Y_COLUMN_NAME
863
+ color_column = _MELTED_COLOR_COLUMN_NAME
864
+
865
+ columns_to_leave_alone = [x_column]
866
+ if size_column and size_column not in columns_to_leave_alone:
867
+ columns_to_leave_alone.append(size_column)
868
+ if sort_column and sort_column not in columns_to_leave_alone:
869
+ columns_to_leave_alone.append(sort_column)
870
+
871
+ df = _melt_data(
872
+ df=df,
873
+ columns_to_leave_alone=columns_to_leave_alone,
874
+ columns_to_melt=y_column_list,
875
+ new_y_column_name=y_column,
876
+ new_color_column_name=color_column,
877
+ )
878
+
879
+ return df, y_column, color_column
880
+
881
+
882
+ def _get_axis_encodings(
883
+ df: pd.DataFrame,
884
+ chart_type: ChartType,
885
+ x_column: str | None,
886
+ y_column: str | None,
887
+ x_from_user: str | None,
888
+ y_from_user: str | Sequence[str] | None,
889
+ x_axis_label: str | None,
890
+ y_axis_label: str | None,
891
+ stack: bool | ChartStackType | None,
892
+ sort_from_user: bool | str,
893
+ ) -> tuple[alt.X, alt.Y]:
894
+ stack_encoding: alt.X | alt.Y
895
+ sort_encoding: alt.X | alt.Y
896
+ if chart_type == ChartType.HORIZONTAL_BAR:
897
+ # Handle horizontal bar chart - switches x and y data:
898
+ x_encoding = _get_x_encoding(
899
+ df, y_column, y_from_user, x_axis_label, chart_type
900
+ )
901
+ y_encoding = _get_y_encoding(
902
+ df, x_column, x_from_user, y_axis_label, chart_type
903
+ )
904
+ stack_encoding = x_encoding
905
+ sort_encoding = y_encoding
906
+ else:
907
+ x_encoding = _get_x_encoding(
908
+ df, x_column, x_from_user, x_axis_label, chart_type
909
+ )
910
+ y_encoding = _get_y_encoding(
911
+ df, y_column, y_from_user, y_axis_label, chart_type
912
+ )
913
+ stack_encoding = y_encoding
914
+ sort_encoding = x_encoding
915
+
916
+ # Handle stacking - only relevant for bar & area charts
917
+ _update_encoding_with_stack(stack, stack_encoding)
918
+
919
+ # Handle sorting - only relevant for bar charts
920
+ if chart_type in {ChartType.VERTICAL_BAR, ChartType.HORIZONTAL_BAR}:
921
+ _update_encoding_with_sort(sort_from_user, sort_encoding)
922
+
923
+ return x_encoding, y_encoding
924
+
925
+
926
+ def _get_x_encoding(
927
+ df: pd.DataFrame,
928
+ x_column: str | None,
929
+ x_from_user: str | Sequence[str] | None,
930
+ x_axis_label: str | None,
931
+ chart_type: ChartType,
932
+ ) -> alt.X:
933
+ import altair as alt
934
+
935
+ if x_column is None:
936
+ # If no field is specified, the full axis disappears when no data is present.
937
+ # Maybe a bug in vega-lite? So we pass a field that doesn't exist.
938
+ x_field = _NON_EXISTENT_COLUMN_NAME
939
+ x_title = ""
940
+ elif x_column == _SEPARATED_INDEX_COLUMN_NAME:
941
+ # If the x column name is the crazy anti-collision name we gave it, then need to
942
+ # set up a title so we never show the crazy name to the user.
943
+ x_field = x_column
944
+ # Don't show a label in the x axis (not even a nice label like
945
+ # SEPARATED_INDEX_COLUMN_TITLE) when we pull the x axis from the index.
946
+ x_title = ""
947
+ else:
948
+ x_field = x_column
949
+
950
+ # Only show a label in the x axis if the user passed a column explicitly. We
951
+ # could go either way here, but I'm keeping this to avoid breaking the existing
952
+ # behavior.
953
+ x_title = "" if x_from_user is None else x_column
954
+
955
+ # User specified x-axis label takes precedence
956
+ if x_axis_label is not None:
957
+ x_title = x_axis_label
958
+
959
+ # grid lines on x axis for horizontal bar charts only
960
+ grid = chart_type == ChartType.HORIZONTAL_BAR
961
+
962
+ return alt.X(
963
+ x_field,
964
+ title=x_title,
965
+ type=_get_x_encoding_type(df, chart_type, x_column),
966
+ scale=alt.Scale(),
967
+ axis=_get_axis_config(df, x_column, grid=grid),
968
+ )
969
+
970
+
971
+ def _get_y_encoding(
972
+ df: pd.DataFrame,
973
+ y_column: str | None,
974
+ y_from_user: str | Sequence[str] | None,
975
+ y_axis_label: str | None,
976
+ chart_type: ChartType,
977
+ ) -> alt.Y:
978
+ import altair as alt
979
+
980
+ if y_column is None:
981
+ # If no field is specified, the full axis disappears when no data is present.
982
+ # Maybe a bug in vega-lite? So we pass a field that doesn't exist.
983
+ y_field = _NON_EXISTENT_COLUMN_NAME
984
+ y_title = ""
985
+ elif y_column == _MELTED_Y_COLUMN_NAME:
986
+ # If the y column name is the crazy anti-collision name we gave it, then need to
987
+ # set up a title so we never show the crazy name to the user.
988
+ y_field = y_column
989
+ # Don't show a label in the y axis (not even a nice label like
990
+ # MELTED_Y_COLUMN_TITLE) when we pull the x axis from the index.
991
+ y_title = ""
992
+ else:
993
+ y_field = y_column
994
+
995
+ # Only show a label in the y axis if the user passed a column explicitly. We
996
+ # could go either way here, but I'm keeping this to avoid breaking the existing
997
+ # behavior.
998
+ y_title = "" if y_from_user is None else y_column
999
+
1000
+ # User specified y-axis label takes precedence
1001
+ if y_axis_label is not None:
1002
+ y_title = y_axis_label
1003
+
1004
+ # grid lines on y axis for all charts except horizontal bar charts
1005
+ grid = chart_type != ChartType.HORIZONTAL_BAR
1006
+
1007
+ return alt.Y(
1008
+ field=y_field,
1009
+ title=y_title,
1010
+ type=_get_y_encoding_type(df, chart_type, y_column),
1011
+ scale=alt.Scale(),
1012
+ axis=_get_axis_config(df, y_column, grid=grid),
1013
+ )
1014
+
1015
+
1016
+ def _update_encoding_with_stack(
1017
+ stack: bool | ChartStackType | None,
1018
+ encoding: alt.X | alt.Y,
1019
+ ) -> None:
1020
+ if stack is None:
1021
+ return
1022
+ # Our layered option maps to vega's stack=False option
1023
+ if stack == "layered":
1024
+ stack = False
1025
+
1026
+ encoding["stack"] = stack
1027
+
1028
+
1029
+ def _update_encoding_with_sort(
1030
+ sort_from_user: bool | str,
1031
+ encoding: alt.X | alt.Y,
1032
+ ) -> None:
1033
+ """Apply sort to the given encoding in-place.
1034
+
1035
+ - If sort is False: disable Altair's default sorting on the bar's categorical axis
1036
+ (i.e., set to None).
1037
+ - If sort is True: use Altair's default sorting.
1038
+ - If sort is a column name (optionally starting with '-') set a SortField with the correct order.
1039
+
1040
+ Note: Column validation should be done before calling this function.
1041
+ """
1042
+ import altair as alt
1043
+
1044
+ if sort_from_user is False:
1045
+ # Disable Altair's default sorting
1046
+ encoding["sort"] = None
1047
+ elif sort_from_user is True:
1048
+ # Use Altair's default sorting
1049
+ pass
1050
+ else:
1051
+ # String: sort by column name (optional '-' prefix for descending)
1052
+ sort_order: Literal["ascending", "descending"]
1053
+ if sort_from_user.startswith("-"):
1054
+ sort_order = "descending"
1055
+ else:
1056
+ sort_order = "ascending"
1057
+ sort_field = sort_from_user.removeprefix("-")
1058
+ encoding["sort"] = alt.SortField(field=sort_field, order=sort_order)
1059
+
1060
+
1061
+ def _get_color_encoding(
1062
+ df: pd.DataFrame,
1063
+ color_value: Color | None,
1064
+ color_column: str | None,
1065
+ y_column_list: list[str],
1066
+ color_from_user: str | Color | list[Color] | None,
1067
+ ) -> alt.Color | alt.ColorValue | None:
1068
+ import altair as alt
1069
+
1070
+ has_color_value = color_value not in [None, [], ()] # type: ignore[comparison-overlap]
1071
+
1072
+ # If user passed a color value, that should win over colors coming from the
1073
+ # color column (be they manual or auto-assigned due to melting)
1074
+ if has_color_value:
1075
+ # If the color value is color-like, return that.
1076
+ if is_color_like(cast("Any", color_value)):
1077
+ if len(y_column_list) != 1:
1078
+ raise StreamlitColorLengthError(
1079
+ [color_value] if color_value else [], y_column_list
1080
+ )
1081
+
1082
+ return alt.ColorValue(to_css_color(cast("Any", color_value)))
1083
+
1084
+ # Check for built-in color names (resolved on frontend, not converted here)
1085
+ if isinstance(color_value, str) and is_builtin_color_name(color_value):
1086
+ if len(y_column_list) != 1:
1087
+ raise StreamlitColorLengthError(
1088
+ [color_value] if color_value else [], y_column_list
1089
+ )
1090
+ return alt.ColorValue(color_value)
1091
+
1092
+ # If the color value is a list of colors of appropriate length, return that.
1093
+ if isinstance(color_value, (list, tuple)):
1094
+ color_values = cast("Collection[Color]", color_value)
1095
+
1096
+ if len(color_values) != len(y_column_list):
1097
+ raise StreamlitColorLengthError(color_values, y_column_list)
1098
+
1099
+ if len(color_values) == 1:
1100
+ first_color = cast("Any", color_value[0])
1101
+ # Pass through built-in color names as-is (resolved on frontend)
1102
+ if isinstance(first_color, str) and is_builtin_color_name(first_color):
1103
+ return alt.ColorValue(first_color)
1104
+ return alt.ColorValue(to_css_color(first_color))
1105
+
1106
+ # Convert colors, but pass through built-in color names as-is
1107
+ resolved_colors: list[Color] = []
1108
+ for c in color_values:
1109
+ if isinstance(c, str) and is_builtin_color_name(c):
1110
+ resolved_colors.append(c)
1111
+ else:
1112
+ resolved_colors.append(to_css_color(c))
1113
+
1114
+ return alt.Color(
1115
+ field=color_column if color_column is not None else alt.Undefined,
1116
+ scale=alt.Scale(domain=y_column_list, range=resolved_colors),
1117
+ legend=_COLOR_LEGEND_SETTINGS,
1118
+ type="nominal",
1119
+ title=" ",
1120
+ )
1121
+
1122
+ raise StreamlitInvalidColorError(color_from_user)
1123
+
1124
+ if color_column is not None:
1125
+ column_type: VegaLiteType
1126
+
1127
+ column_type = (
1128
+ "nominal"
1129
+ if color_column == _MELTED_COLOR_COLUMN_NAME
1130
+ else _infer_vegalite_type(df[color_column])
1131
+ )
1132
+
1133
+ color_enc = alt.Color(
1134
+ field=color_column, legend=_COLOR_LEGEND_SETTINGS, type=column_type
1135
+ )
1136
+
1137
+ # Fix title if DF was melted
1138
+ if color_column == _MELTED_COLOR_COLUMN_NAME:
1139
+ # This has to contain an empty space, otherwise the
1140
+ # full y-axis disappears (maybe a bug in vega-lite)?
1141
+ color_enc["title"] = " "
1142
+
1143
+ # If the 0th element in the color column looks like a color, we'll use the color
1144
+ # column's values as the colors in our chart.
1145
+ elif len(df[color_column]) and is_color_like(df[color_column].iat[0]): # type: ignore[arg-type]
1146
+ color_range = [to_css_color(c) for c in df[color_column].unique()]
1147
+ color_enc["scale"] = alt.Scale(range=color_range)
1148
+ # Don't show the color legend, because it will just show text with the
1149
+ # color values, like #f00, #00f, etc, which are not user-readable.
1150
+ color_enc["legend"] = None
1151
+
1152
+ # Otherwise, let Vega-Lite auto-assign colors.
1153
+ # This codepath is typically reached when the color column contains numbers
1154
+ # (in which case Vega-Lite uses a color gradient to represent them) or strings
1155
+ # (in which case Vega-Lite assigns one color for each unique value).
1156
+ else:
1157
+ pass
1158
+
1159
+ return color_enc
1160
+
1161
+ return None
1162
+
1163
+
1164
+ def _get_size_encoding(
1165
+ chart_type: ChartType,
1166
+ size_column: str | None,
1167
+ size_value: str | float | None,
1168
+ ) -> alt.Size | alt.SizeValue | None:
1169
+ import altair as alt
1170
+
1171
+ if chart_type == ChartType.SCATTER:
1172
+ if size_column is not None:
1173
+ return alt.Size(
1174
+ size_column,
1175
+ legend=_SIZE_LEGEND_SETTINGS,
1176
+ )
1177
+
1178
+ if isinstance(size_value, (float, int)):
1179
+ return alt.SizeValue(size_value)
1180
+ if size_value is None:
1181
+ return alt.SizeValue(100)
1182
+ raise StreamlitAPIException(
1183
+ f"This does not look like a valid size: {size_value!r}"
1184
+ )
1185
+
1186
+ if size_column is not None or size_value is not None:
1187
+ raise Error(
1188
+ f"Chart type {chart_type.name} does not support size argument. "
1189
+ "This should never happen!"
1190
+ )
1191
+
1192
+ return None
1193
+
1194
+
1195
+ def _get_tooltip_encoding(
1196
+ x_column: str,
1197
+ y_column: str,
1198
+ size_column: str | None,
1199
+ color_column: str | None,
1200
+ color_enc: alt.Color | alt.ColorValue | None,
1201
+ ) -> list[alt.Tooltip]:
1202
+ import altair as alt
1203
+
1204
+ tooltip = []
1205
+
1206
+ # If the x column name is the crazy anti-collision name we gave it, then need to set
1207
+ # up a tooltip title so we never show the crazy name to the user.
1208
+ if x_column == _SEPARATED_INDEX_COLUMN_NAME:
1209
+ tooltip.append(alt.Tooltip(x_column, title=_SEPARATED_INDEX_COLUMN_TITLE))
1210
+ else:
1211
+ tooltip.append(alt.Tooltip(x_column))
1212
+
1213
+ # If the y column name is the crazy anti-collision name we gave it, then need to set
1214
+ # up a tooltip title so we never show the crazy name to the user.
1215
+ if y_column == _MELTED_Y_COLUMN_NAME:
1216
+ tooltip.append(
1217
+ alt.Tooltip(
1218
+ y_column,
1219
+ title=_MELTED_Y_COLUMN_TITLE,
1220
+ # Just picked something random. Doesn't really matter:
1221
+ type="quantitative",
1222
+ )
1223
+ )
1224
+ else:
1225
+ tooltip.append(alt.Tooltip(y_column))
1226
+
1227
+ # If we earlier decided that there should be no color legend, that's because the
1228
+ # user passed a color column with actual color values (like "#ff0"), so we should
1229
+ # not show the color values in the tooltip.
1230
+ if color_column and getattr(color_enc, "legend", True) is not None:
1231
+ # Use a human-readable title for the color.
1232
+ if color_column == _MELTED_COLOR_COLUMN_NAME:
1233
+ tooltip.append(
1234
+ alt.Tooltip(
1235
+ color_column,
1236
+ title=_MELTED_COLOR_COLUMN_TITLE,
1237
+ type="nominal",
1238
+ )
1239
+ )
1240
+ else:
1241
+ tooltip.append(alt.Tooltip(color_column))
1242
+
1243
+ if size_column:
1244
+ tooltip.append(alt.Tooltip(size_column))
1245
+
1246
+ return tooltip
1247
+
1248
+
1249
+ def _get_x_encoding_type(
1250
+ df: pd.DataFrame, chart_type: ChartType, x_column: str | None
1251
+ ) -> VegaLiteType:
1252
+ if x_column is None:
1253
+ return "quantitative" # Anything. If None, Vega-Lite may hide the axis.
1254
+
1255
+ # Vertical bar charts should have a discrete (ordinal) x-axis,
1256
+ # UNLESS type is date/time
1257
+ # https://github.com/streamlit/streamlit/pull/2097#issuecomment-714802475
1258
+ if chart_type == ChartType.VERTICAL_BAR and not _is_date_column(df, x_column):
1259
+ return "ordinal"
1260
+
1261
+ return _infer_vegalite_type(df[x_column])
1262
+
1263
+
1264
+ def _get_y_encoding_type(
1265
+ df: pd.DataFrame, chart_type: ChartType, y_column: str | None
1266
+ ) -> VegaLiteType:
1267
+ # Horizontal bar charts should have a discrete (ordinal) y-axis,
1268
+ # UNLESS type is date/time
1269
+ if chart_type == ChartType.HORIZONTAL_BAR and not _is_date_column(df, y_column):
1270
+ return "ordinal"
1271
+
1272
+ if y_column:
1273
+ return _infer_vegalite_type(df[y_column])
1274
+
1275
+ return "quantitative" # Pick anything. If undefined, Vega-Lite may hide the axis.
1276
+
1277
+
1278
+ class StreamlitColumnNotFoundError(StreamlitAPIException):
1279
+ def __init__(self, df: pd.DataFrame, col_name: str, *args: Any) -> None:
1280
+ available_columns = ", ".join(str(c) for c in list(df.columns))
1281
+ message = (
1282
+ f'Data does not have a column named `"{col_name}"`. '
1283
+ f"Available columns are `{available_columns}`"
1284
+ )
1285
+ super().__init__(message, *args)
1286
+
1287
+
1288
+ class StreamlitInvalidColorError(StreamlitAPIException):
1289
+ def __init__(self, color_from_user: str | Color | list[Color] | None) -> None:
1290
+ message = f"""
1291
+ This does not look like a valid color argument: `{color_from_user}`.
1292
+
1293
+ The color argument can be:
1294
+
1295
+ * A hex string like "#ffaa00" or "#ffaa0088".
1296
+ * An RGB or RGBA tuple with the red, green, blue, and alpha
1297
+ components specified as ints from 0 to 255 or floats from 0.0 to
1298
+ 1.0.
1299
+ * The name of a column.
1300
+ * Or a list of colors, matching the number of y columns to draw.
1301
+ """
1302
+ super().__init__(message)
1303
+
1304
+
1305
+ class StreamlitColorLengthError(StreamlitAPIException):
1306
+ def __init__(
1307
+ self,
1308
+ color_values: str | Color | Collection[Color] | None,
1309
+ y_column_list: list[str],
1310
+ ) -> None:
1311
+ message = (
1312
+ f"The list of colors `{color_values}` must have the same "
1313
+ "length as the list of columns to be colored "
1314
+ f"`{y_column_list}`."
1315
+ )
1316
+ super().__init__(message)