streamlit-nightly 1.43.2.dev20250307__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.
- streamlit/__init__.py +306 -0
- streamlit/__main__.py +20 -0
- streamlit/auth_util.py +218 -0
- streamlit/cli_util.py +105 -0
- streamlit/column_config.py +56 -0
- streamlit/commands/__init__.py +13 -0
- streamlit/commands/echo.py +126 -0
- streamlit/commands/execution_control.py +238 -0
- streamlit/commands/experimental_query_params.py +169 -0
- streamlit/commands/logo.py +189 -0
- streamlit/commands/navigation.py +385 -0
- streamlit/commands/page_config.py +311 -0
- streamlit/components/__init__.py +13 -0
- streamlit/components/lib/__init__.py +13 -0
- streamlit/components/lib/local_component_registry.py +84 -0
- streamlit/components/types/__init__.py +13 -0
- streamlit/components/types/base_component_registry.py +99 -0
- streamlit/components/types/base_custom_component.py +150 -0
- streamlit/components/v1/__init__.py +31 -0
- streamlit/components/v1/component_arrow.py +141 -0
- streamlit/components/v1/component_registry.py +130 -0
- streamlit/components/v1/components.py +38 -0
- streamlit/components/v1/custom_component.py +243 -0
- streamlit/config.py +1513 -0
- streamlit/config_option.py +311 -0
- streamlit/config_util.py +177 -0
- streamlit/connections/__init__.py +28 -0
- streamlit/connections/base_connection.py +174 -0
- streamlit/connections/snowflake_connection.py +562 -0
- streamlit/connections/snowpark_connection.py +213 -0
- streamlit/connections/sql_connection.py +425 -0
- streamlit/connections/util.py +97 -0
- streamlit/cursor.py +210 -0
- streamlit/dataframe_util.py +1416 -0
- streamlit/delta_generator.py +602 -0
- streamlit/delta_generator_singletons.py +204 -0
- streamlit/deprecation_util.py +209 -0
- streamlit/development.py +21 -0
- streamlit/elements/__init__.py +13 -0
- streamlit/elements/alert.py +234 -0
- streamlit/elements/arrow.py +962 -0
- streamlit/elements/balloons.py +47 -0
- streamlit/elements/bokeh_chart.py +133 -0
- streamlit/elements/code.py +114 -0
- streamlit/elements/deck_gl_json_chart.py +546 -0
- streamlit/elements/dialog_decorator.py +267 -0
- streamlit/elements/doc_string.py +558 -0
- streamlit/elements/empty.py +130 -0
- streamlit/elements/exception.py +331 -0
- streamlit/elements/form.py +354 -0
- streamlit/elements/graphviz_chart.py +150 -0
- streamlit/elements/heading.py +302 -0
- streamlit/elements/html.py +105 -0
- streamlit/elements/iframe.py +191 -0
- streamlit/elements/image.py +196 -0
- streamlit/elements/json.py +139 -0
- streamlit/elements/layouts.py +879 -0
- streamlit/elements/lib/__init__.py +13 -0
- streamlit/elements/lib/built_in_chart_utils.py +1157 -0
- streamlit/elements/lib/color_util.py +263 -0
- streamlit/elements/lib/column_config_utils.py +542 -0
- streamlit/elements/lib/column_types.py +2188 -0
- streamlit/elements/lib/dialog.py +147 -0
- streamlit/elements/lib/dicttools.py +154 -0
- streamlit/elements/lib/event_utils.py +37 -0
- streamlit/elements/lib/file_uploader_utils.py +66 -0
- streamlit/elements/lib/form_utils.py +77 -0
- streamlit/elements/lib/image_utils.py +441 -0
- streamlit/elements/lib/js_number.py +105 -0
- streamlit/elements/lib/mutable_status_container.py +183 -0
- streamlit/elements/lib/options_selector_utils.py +250 -0
- streamlit/elements/lib/pandas_styler_utils.py +274 -0
- streamlit/elements/lib/policies.py +194 -0
- streamlit/elements/lib/streamlit_plotly_theme.py +207 -0
- streamlit/elements/lib/subtitle_utils.py +176 -0
- streamlit/elements/lib/utils.py +250 -0
- streamlit/elements/map.py +508 -0
- streamlit/elements/markdown.py +277 -0
- streamlit/elements/media.py +793 -0
- streamlit/elements/metric.py +301 -0
- streamlit/elements/plotly_chart.py +546 -0
- streamlit/elements/progress.py +156 -0
- streamlit/elements/pyplot.py +194 -0
- streamlit/elements/snow.py +47 -0
- streamlit/elements/spinner.py +113 -0
- streamlit/elements/text.py +76 -0
- streamlit/elements/toast.py +98 -0
- streamlit/elements/vega_charts.py +1984 -0
- streamlit/elements/widgets/__init__.py +13 -0
- streamlit/elements/widgets/audio_input.py +310 -0
- streamlit/elements/widgets/button.py +1123 -0
- streamlit/elements/widgets/button_group.py +1008 -0
- streamlit/elements/widgets/camera_input.py +263 -0
- streamlit/elements/widgets/chat.py +647 -0
- streamlit/elements/widgets/checkbox.py +352 -0
- streamlit/elements/widgets/color_picker.py +265 -0
- streamlit/elements/widgets/data_editor.py +983 -0
- streamlit/elements/widgets/file_uploader.py +486 -0
- streamlit/elements/widgets/multiselect.py +338 -0
- streamlit/elements/widgets/number_input.py +545 -0
- streamlit/elements/widgets/radio.py +407 -0
- streamlit/elements/widgets/select_slider.py +437 -0
- streamlit/elements/widgets/selectbox.py +366 -0
- streamlit/elements/widgets/slider.py +880 -0
- streamlit/elements/widgets/text_widgets.py +628 -0
- streamlit/elements/widgets/time_widgets.py +970 -0
- streamlit/elements/write.py +574 -0
- streamlit/emojis.py +34 -0
- streamlit/env_util.py +61 -0
- streamlit/error_util.py +105 -0
- streamlit/errors.py +452 -0
- streamlit/external/__init__.py +13 -0
- streamlit/external/langchain/__init__.py +23 -0
- streamlit/external/langchain/streamlit_callback_handler.py +406 -0
- streamlit/file_util.py +247 -0
- streamlit/git_util.py +173 -0
- streamlit/hello/__init__.py +13 -0
- streamlit/hello/animation_demo.py +82 -0
- streamlit/hello/dataframe_demo.py +71 -0
- streamlit/hello/hello.py +37 -0
- streamlit/hello/mapping_demo.py +114 -0
- streamlit/hello/plotting_demo.py +55 -0
- streamlit/hello/streamlit_app.py +55 -0
- streamlit/hello/utils.py +28 -0
- streamlit/logger.py +130 -0
- streamlit/material_icon_names.py +25 -0
- streamlit/navigation/__init__.py +13 -0
- streamlit/navigation/page.py +302 -0
- streamlit/net_util.py +125 -0
- streamlit/platform.py +33 -0
- streamlit/proto/Alert_pb2.py +29 -0
- streamlit/proto/Alert_pb2.pyi +90 -0
- streamlit/proto/AppPage_pb2.py +27 -0
- streamlit/proto/AppPage_pb2.pyi +64 -0
- streamlit/proto/ArrowNamedDataSet_pb2.py +28 -0
- streamlit/proto/ArrowNamedDataSet_pb2.pyi +57 -0
- streamlit/proto/ArrowVegaLiteChart_pb2.py +29 -0
- streamlit/proto/ArrowVegaLiteChart_pb2.pyi +84 -0
- streamlit/proto/Arrow_pb2.py +33 -0
- streamlit/proto/Arrow_pb2.pyi +188 -0
- streamlit/proto/AudioInput_pb2.py +28 -0
- streamlit/proto/AudioInput_pb2.pyi +58 -0
- streamlit/proto/Audio_pb2.py +27 -0
- streamlit/proto/Audio_pb2.pyi +58 -0
- streamlit/proto/AuthRedirect_pb2.py +27 -0
- streamlit/proto/AuthRedirect_pb2.pyi +41 -0
- streamlit/proto/AutoRerun_pb2.py +27 -0
- streamlit/proto/AutoRerun_pb2.pyi +45 -0
- streamlit/proto/BackMsg_pb2.py +29 -0
- streamlit/proto/BackMsg_pb2.pyi +105 -0
- streamlit/proto/Balloons_pb2.py +27 -0
- streamlit/proto/Balloons_pb2.pyi +43 -0
- streamlit/proto/Block_pb2.py +53 -0
- streamlit/proto/Block_pb2.pyi +322 -0
- streamlit/proto/BokehChart_pb2.py +27 -0
- streamlit/proto/BokehChart_pb2.pyi +49 -0
- streamlit/proto/ButtonGroup_pb2.py +36 -0
- streamlit/proto/ButtonGroup_pb2.pyi +169 -0
- streamlit/proto/Button_pb2.py +27 -0
- streamlit/proto/Button_pb2.pyi +71 -0
- streamlit/proto/CameraInput_pb2.py +28 -0
- streamlit/proto/CameraInput_pb2.pyi +58 -0
- streamlit/proto/ChatInput_pb2.py +31 -0
- streamlit/proto/ChatInput_pb2.pyi +111 -0
- streamlit/proto/Checkbox_pb2.py +30 -0
- streamlit/proto/Checkbox_pb2.pyi +90 -0
- streamlit/proto/ClientState_pb2.py +30 -0
- streamlit/proto/ClientState_pb2.pyi +90 -0
- streamlit/proto/Code_pb2.py +27 -0
- streamlit/proto/Code_pb2.pyi +55 -0
- streamlit/proto/ColorPicker_pb2.py +28 -0
- streamlit/proto/ColorPicker_pb2.pyi +67 -0
- streamlit/proto/Common_pb2.py +51 -0
- streamlit/proto/Common_pb2.pyi +293 -0
- streamlit/proto/Components_pb2.py +35 -0
- streamlit/proto/Components_pb2.pyi +172 -0
- streamlit/proto/DataFrame_pb2.py +56 -0
- streamlit/proto/DataFrame_pb2.pyi +397 -0
- streamlit/proto/DateInput_pb2.py +28 -0
- streamlit/proto/DateInput_pb2.pyi +83 -0
- streamlit/proto/DeckGlJsonChart_pb2.py +29 -0
- streamlit/proto/DeckGlJsonChart_pb2.pyi +102 -0
- streamlit/proto/Delta_pb2.py +31 -0
- streamlit/proto/Delta_pb2.pyi +74 -0
- streamlit/proto/DocString_pb2.py +29 -0
- streamlit/proto/DocString_pb2.pyi +93 -0
- streamlit/proto/DownloadButton_pb2.py +27 -0
- streamlit/proto/DownloadButton_pb2.pyi +70 -0
- streamlit/proto/Element_pb2.py +78 -0
- streamlit/proto/Element_pb2.pyi +312 -0
- streamlit/proto/Empty_pb2.py +27 -0
- streamlit/proto/Empty_pb2.pyi +36 -0
- streamlit/proto/Exception_pb2.py +27 -0
- streamlit/proto/Exception_pb2.pyi +72 -0
- streamlit/proto/Favicon_pb2.py +27 -0
- streamlit/proto/Favicon_pb2.pyi +40 -0
- streamlit/proto/FileUploader_pb2.py +28 -0
- streamlit/proto/FileUploader_pb2.pyi +78 -0
- streamlit/proto/ForwardMsg_pb2.py +53 -0
- streamlit/proto/ForwardMsg_pb2.pyi +293 -0
- streamlit/proto/GitInfo_pb2.py +29 -0
- streamlit/proto/GitInfo_pb2.pyi +83 -0
- streamlit/proto/GraphVizChart_pb2.py +27 -0
- streamlit/proto/GraphVizChart_pb2.pyi +53 -0
- streamlit/proto/Heading_pb2.py +27 -0
- streamlit/proto/Heading_pb2.pyi +56 -0
- streamlit/proto/Html_pb2.py +27 -0
- streamlit/proto/Html_pb2.pyi +42 -0
- streamlit/proto/IFrame_pb2.py +27 -0
- streamlit/proto/IFrame_pb2.pyi +59 -0
- streamlit/proto/Image_pb2.py +29 -0
- streamlit/proto/Image_pb2.pyi +84 -0
- streamlit/proto/Json_pb2.py +27 -0
- streamlit/proto/Json_pb2.pyi +53 -0
- streamlit/proto/LabelVisibilityMessage_pb2.py +29 -0
- streamlit/proto/LabelVisibilityMessage_pb2.pyi +68 -0
- streamlit/proto/LinkButton_pb2.py +27 -0
- streamlit/proto/LinkButton_pb2.pyi +58 -0
- streamlit/proto/Logo_pb2.py +27 -0
- streamlit/proto/Logo_pb2.pyi +51 -0
- streamlit/proto/Markdown_pb2.py +29 -0
- streamlit/proto/Markdown_pb2.pyi +86 -0
- streamlit/proto/Metric_pb2.py +32 -0
- streamlit/proto/Metric_pb2.pyi +101 -0
- streamlit/proto/MetricsEvent_pb2.py +30 -0
- streamlit/proto/MetricsEvent_pb2.pyi +200 -0
- streamlit/proto/MultiSelect_pb2.py +28 -0
- streamlit/proto/MultiSelect_pb2.pyi +81 -0
- streamlit/proto/NamedDataSet_pb2.py +28 -0
- streamlit/proto/NamedDataSet_pb2.pyi +59 -0
- streamlit/proto/Navigation_pb2.py +30 -0
- streamlit/proto/Navigation_pb2.pyi +84 -0
- streamlit/proto/NewSession_pb2.py +51 -0
- streamlit/proto/NewSession_pb2.pyi +481 -0
- streamlit/proto/NumberInput_pb2.py +30 -0
- streamlit/proto/NumberInput_pb2.pyi +121 -0
- streamlit/proto/PageConfig_pb2.py +33 -0
- streamlit/proto/PageConfig_pb2.pyi +126 -0
- streamlit/proto/PageInfo_pb2.py +27 -0
- streamlit/proto/PageInfo_pb2.pyi +43 -0
- streamlit/proto/PageLink_pb2.py +27 -0
- streamlit/proto/PageLink_pb2.pyi +63 -0
- streamlit/proto/PageNotFound_pb2.py +27 -0
- streamlit/proto/PageNotFound_pb2.pyi +42 -0
- streamlit/proto/PageProfile_pb2.py +31 -0
- streamlit/proto/PageProfile_pb2.pyi +127 -0
- streamlit/proto/PagesChanged_pb2.py +28 -0
- streamlit/proto/PagesChanged_pb2.pyi +48 -0
- streamlit/proto/ParentMessage_pb2.py +27 -0
- streamlit/proto/ParentMessage_pb2.pyi +46 -0
- streamlit/proto/PlotlyChart_pb2.py +31 -0
- streamlit/proto/PlotlyChart_pb2.pyi +131 -0
- streamlit/proto/Progress_pb2.py +27 -0
- streamlit/proto/Progress_pb2.pyi +43 -0
- streamlit/proto/Radio_pb2.py +28 -0
- streamlit/proto/Radio_pb2.pyi +84 -0
- streamlit/proto/RootContainer_pb2.py +27 -0
- streamlit/proto/RootContainer_pb2.pyi +56 -0
- streamlit/proto/Selectbox_pb2.py +28 -0
- streamlit/proto/Selectbox_pb2.pyi +80 -0
- streamlit/proto/SessionEvent_pb2.py +28 -0
- streamlit/proto/SessionEvent_pb2.pyi +62 -0
- streamlit/proto/SessionStatus_pb2.py +27 -0
- streamlit/proto/SessionStatus_pb2.pyi +57 -0
- streamlit/proto/Skeleton_pb2.py +29 -0
- streamlit/proto/Skeleton_pb2.pyi +71 -0
- streamlit/proto/Slider_pb2.py +32 -0
- streamlit/proto/Slider_pb2.pyi +142 -0
- streamlit/proto/Snow_pb2.py +27 -0
- streamlit/proto/Snow_pb2.pyi +43 -0
- streamlit/proto/Spinner_pb2.py +27 -0
- streamlit/proto/Spinner_pb2.pyi +49 -0
- streamlit/proto/TextArea_pb2.py +28 -0
- streamlit/proto/TextArea_pb2.pyi +80 -0
- streamlit/proto/TextInput_pb2.py +30 -0
- streamlit/proto/TextInput_pb2.pyi +107 -0
- streamlit/proto/Text_pb2.py +27 -0
- streamlit/proto/Text_pb2.pyi +46 -0
- streamlit/proto/TimeInput_pb2.py +28 -0
- streamlit/proto/TimeInput_pb2.pyi +74 -0
- streamlit/proto/Toast_pb2.py +27 -0
- streamlit/proto/Toast_pb2.pyi +45 -0
- streamlit/proto/VegaLiteChart_pb2.py +29 -0
- streamlit/proto/VegaLiteChart_pb2.pyi +71 -0
- streamlit/proto/Video_pb2.py +31 -0
- streamlit/proto/Video_pb2.pyi +117 -0
- streamlit/proto/WidgetStates_pb2.py +31 -0
- streamlit/proto/WidgetStates_pb2.pyi +126 -0
- streamlit/proto/__init__.py +15 -0
- streamlit/proto/openmetrics_data_model_pb2.py +60 -0
- streamlit/proto/openmetrics_data_model_pb2.pyi +522 -0
- streamlit/py.typed +0 -0
- streamlit/runtime/__init__.py +50 -0
- streamlit/runtime/app_session.py +982 -0
- streamlit/runtime/caching/__init__.py +98 -0
- streamlit/runtime/caching/cache_data_api.py +665 -0
- streamlit/runtime/caching/cache_errors.py +142 -0
- streamlit/runtime/caching/cache_resource_api.py +527 -0
- streamlit/runtime/caching/cache_type.py +33 -0
- streamlit/runtime/caching/cache_utils.py +523 -0
- streamlit/runtime/caching/cached_message_replay.py +290 -0
- streamlit/runtime/caching/hashing.py +637 -0
- streamlit/runtime/caching/legacy_cache_api.py +169 -0
- streamlit/runtime/caching/storage/__init__.py +29 -0
- streamlit/runtime/caching/storage/cache_storage_protocol.py +239 -0
- streamlit/runtime/caching/storage/dummy_cache_storage.py +60 -0
- streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py +145 -0
- streamlit/runtime/caching/storage/local_disk_cache_storage.py +223 -0
- streamlit/runtime/connection_factory.py +436 -0
- streamlit/runtime/context.py +280 -0
- streamlit/runtime/credentials.py +364 -0
- streamlit/runtime/forward_msg_cache.py +296 -0
- streamlit/runtime/forward_msg_queue.py +240 -0
- streamlit/runtime/fragment.py +477 -0
- streamlit/runtime/media_file_manager.py +234 -0
- streamlit/runtime/media_file_storage.py +143 -0
- streamlit/runtime/memory_media_file_storage.py +181 -0
- streamlit/runtime/memory_session_storage.py +77 -0
- streamlit/runtime/memory_uploaded_file_manager.py +138 -0
- streamlit/runtime/metrics_util.py +486 -0
- streamlit/runtime/pages_manager.py +165 -0
- streamlit/runtime/runtime.py +792 -0
- streamlit/runtime/runtime_util.py +106 -0
- streamlit/runtime/script_data.py +46 -0
- streamlit/runtime/scriptrunner/__init__.py +38 -0
- streamlit/runtime/scriptrunner/exec_code.py +159 -0
- streamlit/runtime/scriptrunner/magic.py +273 -0
- streamlit/runtime/scriptrunner/magic_funcs.py +32 -0
- streamlit/runtime/scriptrunner/script_cache.py +89 -0
- streamlit/runtime/scriptrunner/script_runner.py +756 -0
- streamlit/runtime/scriptrunner_utils/__init__.py +19 -0
- streamlit/runtime/scriptrunner_utils/exceptions.py +48 -0
- streamlit/runtime/scriptrunner_utils/script_requests.py +307 -0
- streamlit/runtime/scriptrunner_utils/script_run_context.py +287 -0
- streamlit/runtime/secrets.py +534 -0
- streamlit/runtime/session_manager.py +394 -0
- streamlit/runtime/state/__init__.py +41 -0
- streamlit/runtime/state/common.py +191 -0
- streamlit/runtime/state/query_params.py +205 -0
- streamlit/runtime/state/query_params_proxy.py +218 -0
- streamlit/runtime/state/safe_session_state.py +138 -0
- streamlit/runtime/state/session_state.py +772 -0
- streamlit/runtime/state/session_state_proxy.py +153 -0
- streamlit/runtime/state/widgets.py +135 -0
- streamlit/runtime/stats.py +109 -0
- streamlit/runtime/uploaded_file_manager.py +148 -0
- streamlit/runtime/websocket_session_manager.py +167 -0
- streamlit/source_util.py +98 -0
- streamlit/static/favicon.png +0 -0
- streamlit/static/index.html +61 -0
- streamlit/static/static/css/index.Bmkmz40k.css +1 -0
- streamlit/static/static/css/index.DpJG_94W.css +1 -0
- streamlit/static/static/css/index.DzuxGC_t.css +1 -0
- streamlit/static/static/js/FileDownload.esm.Bp9m5jrx.js +1 -0
- streamlit/static/static/js/FileHelper.D_3pbilj.js +5 -0
- streamlit/static/static/js/FormClearHelper.Ct2rwLXo.js +1 -0
- streamlit/static/static/js/Hooks.BKdzj5MJ.js +1 -0
- streamlit/static/static/js/InputInstructions.DB3QGNJP.js +1 -0
- streamlit/static/static/js/ProgressBar.D40A5xc2.js +2 -0
- streamlit/static/static/js/RenderInPortalIfExists.DLUCooTN.js +1 -0
- streamlit/static/static/js/Toolbar.BiGGIQun.js +1 -0
- streamlit/static/static/js/UploadFileInfo.C-jY39rj.js +1 -0
- streamlit/static/static/js/base-input.CQBQT24M.js +4 -0
- streamlit/static/static/js/checkbox.Buj8gd_M.js +9 -0
- streamlit/static/static/js/createDownloadLinkElement.DZMwyjvU.js +1 -0
- streamlit/static/static/js/createSuper.CesK3I23.js +1 -0
- streamlit/static/static/js/data-grid-overlay-editor.B69OOFM4.js +1 -0
- streamlit/static/static/js/downloader.BZQhlBNT.js +1 -0
- streamlit/static/static/js/es6.D9Zhqujy.js +2 -0
- streamlit/static/static/js/iframeResizer.contentWindow.CAzcBpCC.js +1 -0
- streamlit/static/static/js/index.08vcOOvb.js +1 -0
- streamlit/static/static/js/index.0uqKfJUS.js +1 -0
- streamlit/static/static/js/index.B02M5u69.js +203 -0
- streamlit/static/static/js/index.B7mcZKMx.js +1 -0
- streamlit/static/static/js/index.BAQDHFA_.js +1 -0
- streamlit/static/static/js/index.BI60cMVr.js +2 -0
- streamlit/static/static/js/index.BLug2inK.js +1 -0
- streamlit/static/static/js/index.BM6TMY8g.js +2 -0
- streamlit/static/static/js/index.BZ9p1t7G.js +1 -0
- streamlit/static/static/js/index.BZqa87a1.js +2 -0
- streamlit/static/static/js/index.BcsRUzZZ.js +1 -0
- streamlit/static/static/js/index.BgVMiY_P.js +197 -0
- streamlit/static/static/js/index.BtuGy7By.js +6 -0
- streamlit/static/static/js/index.BuDuBmrs.js +1 -0
- streamlit/static/static/js/index.BvXU2oKV.js +1 -0
- streamlit/static/static/js/index.BxcwPacT.js +73 -0
- streamlit/static/static/js/index.CWX8KB81.js +1 -0
- streamlit/static/static/js/index.CXzZTo_q.js +1 -0
- streamlit/static/static/js/index.CcRWp_KL.js +1 -0
- streamlit/static/static/js/index.Cd-_xe55.js +3 -0
- streamlit/static/static/js/index.CdG2PXln.js +4537 -0
- streamlit/static/static/js/index.CjXvXmcP.js +1 -0
- streamlit/static/static/js/index.D1HZENZx.js +776 -0
- streamlit/static/static/js/index.D21Efo64.js +1617 -0
- streamlit/static/static/js/index.D9WgGVBx.js +7 -0
- streamlit/static/static/js/index.DEcsHtvb.js +12 -0
- streamlit/static/static/js/index.DFeMfr_K.js +1 -0
- streamlit/static/static/js/index.DHFBoItz.js +1 -0
- streamlit/static/static/js/index.D_PrBKnJ.js +3 -0
- streamlit/static/static/js/index.DmuRkekN.js +3855 -0
- streamlit/static/static/js/index.Do6eY8sf.js +1 -0
- streamlit/static/static/js/index.Dz3lP2P-.js +1 -0
- streamlit/static/static/js/index.Dz_UqF-s.js +1 -0
- streamlit/static/static/js/index.GkSUsPhJ.js +1 -0
- streamlit/static/static/js/index.H1U1IC_d.js +3 -0
- streamlit/static/static/js/index.g6p_4DPr.js +1 -0
- streamlit/static/static/js/index.g9x_GKss.js +1 -0
- streamlit/static/static/js/index.zo9jm08y.js +1 -0
- streamlit/static/static/js/input.DnaFglHq.js +2 -0
- streamlit/static/static/js/inputUtils.CQWz5UKz.js +1 -0
- streamlit/static/static/js/memory.Crb9x4-F.js +1 -0
- streamlit/static/static/js/mergeWith.ouAz0sK3.js +1 -0
- streamlit/static/static/js/number-overlay-editor._UaN-O48.js +9 -0
- streamlit/static/static/js/possibleConstructorReturn.CtGjGFHz.js +1 -0
- streamlit/static/static/js/sandbox.CBybYOhV.js +1 -0
- streamlit/static/static/js/sprintf.D7DtBTRn.js +1 -0
- streamlit/static/static/js/textarea.Cb_uJt5U.js +2 -0
- streamlit/static/static/js/threshold.DjX0wlsa.js +1 -0
- streamlit/static/static/js/timepicker.DKT7pfoF.js +4 -0
- streamlit/static/static/js/timer.CAwTRJ_g.js +1 -0
- streamlit/static/static/js/toConsumableArray.05Ikp13-.js +3 -0
- streamlit/static/static/js/uniqueId.D2FMWUEI.js +1 -0
- streamlit/static/static/js/useBasicWidgetState.urnZLANY.js +1 -0
- streamlit/static/static/js/useOnInputChange.BOKIIdJ1.js +1 -0
- streamlit/static/static/js/value.CgPGBV_l.js +1 -0
- streamlit/static/static/js/withFullScreenWrapper.C_N8J0Xx.js +1 -0
- streamlit/static/static/media/KaTeX_AMS-Regular.BQhdFMY1.woff2 +0 -0
- streamlit/static/static/media/KaTeX_AMS-Regular.DMm9YOAa.woff +0 -0
- streamlit/static/static/media/KaTeX_AMS-Regular.DRggAlZN.ttf +0 -0
- streamlit/static/static/media/KaTeX_Caligraphic-Bold.ATXxdsX0.ttf +0 -0
- streamlit/static/static/media/KaTeX_Caligraphic-Bold.BEiXGLvX.woff +0 -0
- streamlit/static/static/media/KaTeX_Caligraphic-Bold.Dq_IR9rO.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Caligraphic-Regular.CTRA-rTL.woff +0 -0
- streamlit/static/static/media/KaTeX_Caligraphic-Regular.Di6jR-x-.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Caligraphic-Regular.wX97UBjC.ttf +0 -0
- streamlit/static/static/media/KaTeX_Fraktur-Bold.BdnERNNW.ttf +0 -0
- streamlit/static/static/media/KaTeX_Fraktur-Bold.BsDP51OF.woff +0 -0
- streamlit/static/static/media/KaTeX_Fraktur-Bold.CL6g_b3V.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Fraktur-Regular.CB_wures.ttf +0 -0
- streamlit/static/static/media/KaTeX_Fraktur-Regular.CTYiF6lA.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Fraktur-Regular.Dxdc4cR9.woff +0 -0
- streamlit/static/static/media/KaTeX_Main-Bold.Cx986IdX.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Main-Bold.Jm3AIy58.woff +0 -0
- streamlit/static/static/media/KaTeX_Main-Bold.waoOVXN0.ttf +0 -0
- streamlit/static/static/media/KaTeX_Main-BoldItalic.DxDJ3AOS.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Main-BoldItalic.DzxPMmG6.ttf +0 -0
- streamlit/static/static/media/KaTeX_Main-BoldItalic.SpSLRI95.woff +0 -0
- streamlit/static/static/media/KaTeX_Main-Italic.3WenGoN9.ttf +0 -0
- streamlit/static/static/media/KaTeX_Main-Italic.BMLOBm91.woff +0 -0
- streamlit/static/static/media/KaTeX_Main-Italic.NWA7e6Wa.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Main-Regular.B22Nviop.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Main-Regular.Dr94JaBh.woff +0 -0
- streamlit/static/static/media/KaTeX_Main-Regular.ypZvNtVU.ttf +0 -0
- streamlit/static/static/media/KaTeX_Math-BoldItalic.B3XSjfu4.ttf +0 -0
- streamlit/static/static/media/KaTeX_Math-BoldItalic.CZnvNsCZ.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Math-BoldItalic.iY-2wyZ7.woff +0 -0
- streamlit/static/static/media/KaTeX_Math-Italic.DA0__PXp.woff +0 -0
- streamlit/static/static/media/KaTeX_Math-Italic.flOr_0UB.ttf +0 -0
- streamlit/static/static/media/KaTeX_Math-Italic.t53AETM-.woff2 +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Bold.CFMepnvq.ttf +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Bold.D1sUS0GD.woff2 +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Bold.DbIhKOiC.woff +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Italic.C3H0VqGB.woff2 +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Italic.DN2j7dab.woff +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Italic.YYjJ1zSn.ttf +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Regular.BNo7hRIc.ttf +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Regular.CS6fqUqJ.woff +0 -0
- streamlit/static/static/media/KaTeX_SansSerif-Regular.DDBCnlJ7.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Script-Regular.C5JkGWo-.ttf +0 -0
- streamlit/static/static/media/KaTeX_Script-Regular.D3wIWfF6.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Script-Regular.D5yQViql.woff +0 -0
- streamlit/static/static/media/KaTeX_Size1-Regular.C195tn64.woff +0 -0
- streamlit/static/static/media/KaTeX_Size1-Regular.Dbsnue_I.ttf +0 -0
- streamlit/static/static/media/KaTeX_Size1-Regular.mCD8mA8B.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Size2-Regular.B7gKUWhC.ttf +0 -0
- streamlit/static/static/media/KaTeX_Size2-Regular.Dy4dx90m.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Size2-Regular.oD1tc_U0.woff +0 -0
- streamlit/static/static/media/KaTeX_Size3-Regular.CTq5MqoE.woff +0 -0
- streamlit/static/static/media/KaTeX_Size3-Regular.DgpXs0kz.ttf +0 -0
- streamlit/static/static/media/KaTeX_Size4-Regular.BF-4gkZK.woff +0 -0
- streamlit/static/static/media/KaTeX_Size4-Regular.DWFBv043.ttf +0 -0
- streamlit/static/static/media/KaTeX_Size4-Regular.Dl5lxZxV.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Typewriter-Regular.C0xS9mPB.woff +0 -0
- streamlit/static/static/media/KaTeX_Typewriter-Regular.CO6r4hn1.woff2 +0 -0
- streamlit/static/static/media/KaTeX_Typewriter-Regular.D3Ib7_Hf.ttf +0 -0
- streamlit/static/static/media/MaterialSymbols-Rounded.DcZbplWk.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-Bold.CFEfr7-q.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-BoldItalic.C-LkFXxa.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-Italic.CxFOx7N-.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-Regular.CBOlD63d.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-SemiBold.CFHwW3Wd.woff2 +0 -0
- streamlit/static/static/media/SourceCodePro-SemiBoldItalic.Cg2yRu82.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-Bold.-6c9oR8J.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-BoldItalic.DmM_grLY.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-Italic.I1ipWe7Q.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-Regular.DZLUzqI4.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-SemiBold.sKQIyTMz.woff2 +0 -0
- streamlit/static/static/media/SourceSansPro-SemiBoldItalic.C0wP0icr.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-Bold.8TUnKj4x.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-BoldItalic.CBVO7Ve7.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-Italic.DkFgL2HZ.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-Regular.CNJNET2S.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-SemiBold.CHyh9GC5.woff2 +0 -0
- streamlit/static/static/media/SourceSerifPro-SemiBoldItalic.CBtz8sWN.woff2 +0 -0
- streamlit/static/static/media/balloon-0.Czj7AKwE.png +0 -0
- streamlit/static/static/media/balloon-1.CNvFFrND.png +0 -0
- streamlit/static/static/media/balloon-2.DTvC6B1t.png +0 -0
- streamlit/static/static/media/balloon-3.CgSk4tbL.png +0 -0
- streamlit/static/static/media/balloon-4.mbtFrzxf.png +0 -0
- streamlit/static/static/media/balloon-5.CSwkUfRA.png +0 -0
- streamlit/static/static/media/fireworks.B4d-_KUe.gif +0 -0
- streamlit/static/static/media/flake-0.DgWaVvm5.png +0 -0
- streamlit/static/static/media/flake-1.B2r5AHMK.png +0 -0
- streamlit/static/static/media/flake-2.BnWSExPC.png +0 -0
- streamlit/static/static/media/snowflake.JU2jBHL8.svg +11 -0
- streamlit/string_util.py +203 -0
- streamlit/temporary_directory.py +56 -0
- streamlit/testing/__init__.py +13 -0
- streamlit/testing/v1/__init__.py +17 -0
- streamlit/testing/v1/app_test.py +1050 -0
- streamlit/testing/v1/element_tree.py +2083 -0
- streamlit/testing/v1/local_script_runner.py +180 -0
- streamlit/testing/v1/util.py +53 -0
- streamlit/time_util.py +75 -0
- streamlit/type_util.py +460 -0
- streamlit/url_util.py +122 -0
- streamlit/user_info.py +519 -0
- streamlit/util.py +72 -0
- streamlit/vendor/__init__.py +0 -0
- streamlit/vendor/pympler/__init__.py +0 -0
- streamlit/vendor/pympler/asizeof.py +2869 -0
- streamlit/version.py +18 -0
- streamlit/watcher/__init__.py +28 -0
- streamlit/watcher/event_based_path_watcher.py +406 -0
- streamlit/watcher/folder_black_list.py +82 -0
- streamlit/watcher/local_sources_watcher.py +233 -0
- streamlit/watcher/path_watcher.py +185 -0
- streamlit/watcher/polling_path_watcher.py +124 -0
- streamlit/watcher/util.py +207 -0
- streamlit/web/__init__.py +13 -0
- streamlit/web/bootstrap.py +353 -0
- streamlit/web/cache_storage_manager_config.py +38 -0
- streamlit/web/cli.py +369 -0
- streamlit/web/server/__init__.py +26 -0
- streamlit/web/server/app_static_file_handler.py +93 -0
- streamlit/web/server/authlib_tornado_integration.py +60 -0
- streamlit/web/server/browser_websocket_handler.py +246 -0
- streamlit/web/server/component_request_handler.py +116 -0
- streamlit/web/server/media_file_handler.py +141 -0
- streamlit/web/server/oauth_authlib_routes.py +176 -0
- streamlit/web/server/oidc_mixin.py +108 -0
- streamlit/web/server/routes.py +295 -0
- streamlit/web/server/server.py +479 -0
- streamlit/web/server/server_util.py +161 -0
- streamlit/web/server/stats_request_handler.py +95 -0
- streamlit/web/server/upload_file_request_handler.py +137 -0
- streamlit/web/server/websocket_headers.py +56 -0
- streamlit_nightly-1.43.2.dev20250307.data/scripts/streamlit.cmd +16 -0
- streamlit_nightly-1.43.2.dev20250307.dist-info/METADATA +207 -0
- streamlit_nightly-1.43.2.dev20250307.dist-info/RECORD +563 -0
- streamlit_nightly-1.43.2.dev20250307.dist-info/WHEEL +5 -0
- streamlit_nightly-1.43.2.dev20250307.dist-info/entry_points.txt +2 -0
- streamlit_nightly-1.43.2.dev20250307.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1157 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
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 collections.abc import Collection, Hashable, Sequence
|
20
|
+
from dataclasses import dataclass
|
21
|
+
from datetime import date
|
22
|
+
from enum import Enum
|
23
|
+
from typing import (
|
24
|
+
TYPE_CHECKING,
|
25
|
+
Any,
|
26
|
+
Final,
|
27
|
+
Literal,
|
28
|
+
TypedDict,
|
29
|
+
cast,
|
30
|
+
)
|
31
|
+
|
32
|
+
from typing_extensions import TypeAlias
|
33
|
+
|
34
|
+
from streamlit import dataframe_util, type_util
|
35
|
+
from streamlit.elements.lib.color_util import (
|
36
|
+
Color,
|
37
|
+
is_color_like,
|
38
|
+
is_color_tuple_like,
|
39
|
+
is_hex_color_like,
|
40
|
+
to_css_color,
|
41
|
+
)
|
42
|
+
from streamlit.errors import Error, StreamlitAPIException
|
43
|
+
|
44
|
+
if TYPE_CHECKING:
|
45
|
+
import altair as alt
|
46
|
+
import pandas as pd
|
47
|
+
|
48
|
+
from streamlit.dataframe_util import Data
|
49
|
+
|
50
|
+
VegaLiteType: TypeAlias = Literal["quantitative", "ordinal", "temporal", "nominal"]
|
51
|
+
ChartStackType: TypeAlias = Literal["normalize", "center", "layered"]
|
52
|
+
|
53
|
+
|
54
|
+
class PrepDataColumns(TypedDict):
|
55
|
+
"""Columns used for the prep_data step in Altair Arrow charts."""
|
56
|
+
|
57
|
+
x_column: str | None
|
58
|
+
y_column_list: list[str]
|
59
|
+
color_column: str | None
|
60
|
+
size_column: str | None
|
61
|
+
|
62
|
+
|
63
|
+
@dataclass
|
64
|
+
class AddRowsMetadata:
|
65
|
+
"""Metadata needed by add_rows on native charts.
|
66
|
+
|
67
|
+
This class is used to pass some important info to add_rows.
|
68
|
+
"""
|
69
|
+
|
70
|
+
chart_command: str
|
71
|
+
last_index: Hashable | None
|
72
|
+
columns: PrepDataColumns
|
73
|
+
|
74
|
+
|
75
|
+
class ChartType(Enum):
|
76
|
+
AREA = {"mark_type": "area", "command": "area_chart"}
|
77
|
+
VERTICAL_BAR = {"mark_type": "bar", "command": "bar_chart", "horizontal": False}
|
78
|
+
HORIZONTAL_BAR = {"mark_type": "bar", "command": "bar_chart", "horizontal": True}
|
79
|
+
LINE = {"mark_type": "line", "command": "line_chart"}
|
80
|
+
SCATTER = {"mark_type": "circle", "command": "scatter_chart"}
|
81
|
+
|
82
|
+
|
83
|
+
# Color and size legends need different title paddings in order for them
|
84
|
+
# to be vertically aligned.
|
85
|
+
#
|
86
|
+
# NOTE: I don't think it's possible to *perfectly* align the size and
|
87
|
+
# color legends in all instances, since the "size" circles vary in size based
|
88
|
+
# on the data, and their container is top-aligned with the color container. But
|
89
|
+
# through trial-and-error I found this value to be a good enough middle ground.
|
90
|
+
#
|
91
|
+
# NOTE #2: In theory, we could move COLOR_LEGEND_SETTINGS into
|
92
|
+
# ArrowVegaLiteChart/CustomTheme.tsx, but this would impact existing behavior.
|
93
|
+
# (See https://github.com/streamlit/streamlit/pull/7164#discussion_r1307707345)
|
94
|
+
_COLOR_LEGEND_SETTINGS: Final = {"titlePadding": 5, "offset": 5, "orient": "bottom"}
|
95
|
+
_SIZE_LEGEND_SETTINGS: Final = {"titlePadding": 0.5, "offset": 5, "orient": "bottom"}
|
96
|
+
|
97
|
+
# User-readable names to give the index and melted columns.
|
98
|
+
_SEPARATED_INDEX_COLUMN_TITLE: Final = "index"
|
99
|
+
_MELTED_Y_COLUMN_TITLE: Final = "value"
|
100
|
+
_MELTED_COLOR_COLUMN_TITLE: Final = "color"
|
101
|
+
|
102
|
+
# Crazy internal (non-user-visible) names for the index and melted columns, in order to
|
103
|
+
# avoid collision with existing column names. The suffix below was generated with an
|
104
|
+
# online random number generator. Rationale: because it makes it even less likely to
|
105
|
+
# lead to a conflict than something that's human-readable (like "--streamlit-fake-field"
|
106
|
+
# or something).
|
107
|
+
_PROTECTION_SUFFIX: Final = "--p5bJXXpQgvPz6yvQMFiy"
|
108
|
+
_SEPARATED_INDEX_COLUMN_NAME: Final = _SEPARATED_INDEX_COLUMN_TITLE + _PROTECTION_SUFFIX
|
109
|
+
_MELTED_Y_COLUMN_NAME: Final = _MELTED_Y_COLUMN_TITLE + _PROTECTION_SUFFIX
|
110
|
+
_MELTED_COLOR_COLUMN_NAME: Final = _MELTED_COLOR_COLUMN_TITLE + _PROTECTION_SUFFIX
|
111
|
+
|
112
|
+
# Name we use for a column we know doesn't exist in the data, to address a Vega-Lite rendering bug
|
113
|
+
# where empty charts need x, y encodings set in order to take up space.
|
114
|
+
_NON_EXISTENT_COLUMN_NAME: Final = "DOES_NOT_EXIST" + _PROTECTION_SUFFIX
|
115
|
+
|
116
|
+
|
117
|
+
def maybe_raise_stack_warning(
|
118
|
+
stack: bool | ChartStackType | None, command: str | None, docs_link: str
|
119
|
+
):
|
120
|
+
# Check that the stack parameter is valid, raise more informative error message if not
|
121
|
+
if stack not in (None, True, False, "normalize", "center", "layered"):
|
122
|
+
raise StreamlitAPIException(
|
123
|
+
f'Invalid value for stack parameter: {stack}. Stack must be one of True, False, "normalize", "center", "layered" or None. '
|
124
|
+
f"See documentation for `{command}` [here]({docs_link}) for more information."
|
125
|
+
)
|
126
|
+
|
127
|
+
|
128
|
+
def generate_chart(
|
129
|
+
chart_type: ChartType,
|
130
|
+
data: Data | None,
|
131
|
+
x_from_user: str | None = None,
|
132
|
+
y_from_user: str | Sequence[str] | None = None,
|
133
|
+
x_axis_label: str | None = None,
|
134
|
+
y_axis_label: str | None = None,
|
135
|
+
color_from_user: str | Color | list[Color] | None = None,
|
136
|
+
size_from_user: str | float | None = None,
|
137
|
+
width: int | None = None,
|
138
|
+
height: int | None = None,
|
139
|
+
# Bar & Area charts only:
|
140
|
+
stack: bool | ChartStackType | None = None,
|
141
|
+
) -> tuple[alt.Chart | alt.LayerChart, AddRowsMetadata]:
|
142
|
+
"""Function to use the chart's type, data columns and indices to figure out the chart's spec."""
|
143
|
+
import altair as alt
|
144
|
+
|
145
|
+
df = dataframe_util.convert_anything_to_pandas_df(data, ensure_copy=True)
|
146
|
+
|
147
|
+
# From now on, use "df" instead of "data". Deleting "data" to guarantee we follow this.
|
148
|
+
del data
|
149
|
+
|
150
|
+
# Convert arguments received from the user to things Vega-Lite understands.
|
151
|
+
# Get name of column to use for x.
|
152
|
+
x_column = _parse_x_column(df, x_from_user)
|
153
|
+
# Get name of columns to use for y.
|
154
|
+
y_column_list = _parse_y_columns(df, y_from_user, x_column)
|
155
|
+
# Get name of column to use for color, or constant value to use. Any/both could be None.
|
156
|
+
color_column, color_value = _parse_generic_column(df, color_from_user)
|
157
|
+
# Get name of column to use for size, or constant value to use. Any/both could be None.
|
158
|
+
size_column, size_value = _parse_generic_column(df, size_from_user)
|
159
|
+
|
160
|
+
# Store some info so we can use it in add_rows.
|
161
|
+
add_rows_metadata = AddRowsMetadata(
|
162
|
+
# The st command that was used to generate this chart.
|
163
|
+
chart_command=chart_type.value["command"],
|
164
|
+
# The last index of df so we can adjust the input df in add_rows:
|
165
|
+
last_index=_last_index_for_melted_dataframes(df),
|
166
|
+
# This is the input to prep_data (except for the df):
|
167
|
+
columns={
|
168
|
+
"x_column": x_column,
|
169
|
+
"y_column_list": y_column_list,
|
170
|
+
"color_column": color_column,
|
171
|
+
"size_column": size_column,
|
172
|
+
},
|
173
|
+
)
|
174
|
+
|
175
|
+
# At this point, all foo_column variables are either None/empty or contain actual
|
176
|
+
# columns that are guaranteed to exist.
|
177
|
+
|
178
|
+
df, x_column, y_column, color_column, size_column = _prep_data(
|
179
|
+
df, x_column, y_column_list, color_column, size_column
|
180
|
+
)
|
181
|
+
|
182
|
+
# At this point, x_column is only None if user did not provide one AND df is empty.
|
183
|
+
|
184
|
+
# Get x and y encodings
|
185
|
+
x_encoding, y_encoding = _get_axis_encodings(
|
186
|
+
df,
|
187
|
+
chart_type,
|
188
|
+
x_column,
|
189
|
+
y_column,
|
190
|
+
x_from_user,
|
191
|
+
y_from_user,
|
192
|
+
x_axis_label,
|
193
|
+
y_axis_label,
|
194
|
+
stack,
|
195
|
+
)
|
196
|
+
|
197
|
+
# Create a Chart with x and y encodings.
|
198
|
+
chart = alt.Chart(
|
199
|
+
data=df,
|
200
|
+
mark=chart_type.value["mark_type"],
|
201
|
+
width=width or 0,
|
202
|
+
height=height or 0,
|
203
|
+
).encode(
|
204
|
+
x=x_encoding,
|
205
|
+
y=y_encoding,
|
206
|
+
)
|
207
|
+
|
208
|
+
# Offset encoding only works for Altair >= 5.0.0
|
209
|
+
is_altair_version_5_or_greater = not type_util.is_altair_version_less_than("5.0.0")
|
210
|
+
# Set up offset encoding (creates grouped/non-stacked bar charts, so only applicable
|
211
|
+
# when stack=False).
|
212
|
+
if is_altair_version_5_or_greater and stack is False and color_column is not None:
|
213
|
+
x_offset, y_offset = _get_offset_encoding(chart_type, color_column)
|
214
|
+
chart = chart.encode(xOffset=x_offset, yOffset=y_offset)
|
215
|
+
|
216
|
+
# Set up opacity encoding.
|
217
|
+
opacity_enc = _get_opacity_encoding(chart_type, stack, color_column)
|
218
|
+
if opacity_enc is not None:
|
219
|
+
chart = chart.encode(opacity=opacity_enc)
|
220
|
+
|
221
|
+
# Set up color encoding.
|
222
|
+
color_enc = _get_color_encoding(
|
223
|
+
df, color_value, color_column, y_column_list, color_from_user
|
224
|
+
)
|
225
|
+
if color_enc is not None:
|
226
|
+
chart = chart.encode(color=color_enc)
|
227
|
+
|
228
|
+
# Set up size encoding.
|
229
|
+
size_enc = _get_size_encoding(chart_type, size_column, size_value)
|
230
|
+
if size_enc is not None:
|
231
|
+
chart = chart.encode(size=size_enc)
|
232
|
+
|
233
|
+
# Set up tooltip encoding.
|
234
|
+
if x_column is not None and y_column is not None:
|
235
|
+
chart = chart.encode(
|
236
|
+
tooltip=_get_tooltip_encoding(
|
237
|
+
x_column,
|
238
|
+
y_column,
|
239
|
+
size_column,
|
240
|
+
color_column,
|
241
|
+
color_enc,
|
242
|
+
)
|
243
|
+
)
|
244
|
+
|
245
|
+
if (
|
246
|
+
chart_type is ChartType.LINE
|
247
|
+
and x_column is not None
|
248
|
+
# This is using the new selection API that was added in Altair 5.0.0
|
249
|
+
and is_altair_version_5_or_greater
|
250
|
+
):
|
251
|
+
return _add_improved_hover_tooltips(
|
252
|
+
chart, x_column, width, height
|
253
|
+
).interactive(), add_rows_metadata
|
254
|
+
|
255
|
+
return chart.interactive(), add_rows_metadata
|
256
|
+
|
257
|
+
|
258
|
+
def _add_improved_hover_tooltips(
|
259
|
+
chart: alt.Chart, x_column: str, width: int | None, height: int | None
|
260
|
+
) -> alt.LayerChart:
|
261
|
+
"""Adds improved hover tooltips to an existing line chart."""
|
262
|
+
|
263
|
+
import altair as alt
|
264
|
+
|
265
|
+
# Create a selection that chooses the nearest point & selects based on x-value
|
266
|
+
nearest = alt.selection_point(
|
267
|
+
nearest=True,
|
268
|
+
on="pointerover",
|
269
|
+
fields=[x_column],
|
270
|
+
empty=False,
|
271
|
+
clear="pointerout",
|
272
|
+
)
|
273
|
+
|
274
|
+
# Draw points on the line, and highlight based on selection
|
275
|
+
points = (
|
276
|
+
chart.mark_point(filled=True, size=65)
|
277
|
+
.encode(opacity=alt.condition(nearest, alt.value(1), alt.value(0)))
|
278
|
+
.add_params(nearest)
|
279
|
+
)
|
280
|
+
|
281
|
+
layer_chart = (
|
282
|
+
alt.layer(chart, points)
|
283
|
+
.configure_legend(symbolType="stroke")
|
284
|
+
.properties(
|
285
|
+
width=width or 0,
|
286
|
+
height=height or 0,
|
287
|
+
)
|
288
|
+
)
|
289
|
+
|
290
|
+
return cast(alt.LayerChart, layer_chart)
|
291
|
+
|
292
|
+
|
293
|
+
def prep_chart_data_for_add_rows(
|
294
|
+
data: Data,
|
295
|
+
add_rows_metadata: AddRowsMetadata,
|
296
|
+
) -> tuple[Data, AddRowsMetadata]:
|
297
|
+
"""Prepares the data for add_rows on our built-in charts.
|
298
|
+
|
299
|
+
This includes aspects like conversion of the data to Pandas DataFrame,
|
300
|
+
changes to the index, and melting the data if needed.
|
301
|
+
"""
|
302
|
+
import pandas as pd
|
303
|
+
|
304
|
+
df = cast(pd.DataFrame, dataframe_util.convert_anything_to_pandas_df(data))
|
305
|
+
|
306
|
+
# Make range indices start at last_index.
|
307
|
+
if isinstance(df.index, pd.RangeIndex):
|
308
|
+
old_step = _get_pandas_index_attr(df, "step")
|
309
|
+
|
310
|
+
# We have to drop the predefined index
|
311
|
+
df = df.reset_index(drop=True)
|
312
|
+
|
313
|
+
old_stop = _get_pandas_index_attr(df, "stop")
|
314
|
+
|
315
|
+
if old_step is None or old_stop is None:
|
316
|
+
raise StreamlitAPIException("'RangeIndex' object has no attribute 'step'")
|
317
|
+
|
318
|
+
start = add_rows_metadata.last_index + old_step
|
319
|
+
stop = add_rows_metadata.last_index + old_step + old_stop
|
320
|
+
|
321
|
+
df.index = pd.RangeIndex(start=start, stop=stop, step=old_step)
|
322
|
+
add_rows_metadata.last_index = stop - 1
|
323
|
+
|
324
|
+
out_data, *_ = _prep_data(df, **add_rows_metadata.columns)
|
325
|
+
|
326
|
+
return out_data, add_rows_metadata
|
327
|
+
|
328
|
+
|
329
|
+
def _infer_vegalite_type(
|
330
|
+
data: pd.Series[Any],
|
331
|
+
) -> VegaLiteType:
|
332
|
+
"""
|
333
|
+
From an array-like input, infer the correct vega typecode
|
334
|
+
('ordinal', 'nominal', 'quantitative', or 'temporal')
|
335
|
+
|
336
|
+
Parameters
|
337
|
+
----------
|
338
|
+
data: Numpy array or Pandas Series
|
339
|
+
"""
|
340
|
+
# The code below is copied from Altair, and slightly modified.
|
341
|
+
# We copy this code here so we don't depend on private Altair functions.
|
342
|
+
# Source: https://github.com/altair-viz/altair/blob/62ca5e37776f5cecb27e83c1fbd5d685a173095d/altair/utils/core.py#L193
|
343
|
+
|
344
|
+
from pandas.api.types import infer_dtype
|
345
|
+
|
346
|
+
# STREAMLIT MOD: I'm using infer_dtype directly here, rather than using Altair's wrapper. Their
|
347
|
+
# wrapper is only there to support Pandas < 0.20, but Streamlit requires Pandas 1.3.
|
348
|
+
typ = infer_dtype(data)
|
349
|
+
|
350
|
+
if typ in [
|
351
|
+
"floating",
|
352
|
+
"mixed-integer-float",
|
353
|
+
"integer",
|
354
|
+
"mixed-integer",
|
355
|
+
"complex",
|
356
|
+
]:
|
357
|
+
return "quantitative"
|
358
|
+
|
359
|
+
elif typ == "categorical" and data.cat.ordered:
|
360
|
+
# STREAMLIT MOD: The original code returns a tuple here:
|
361
|
+
# return ("ordinal", data.cat.categories.tolist())
|
362
|
+
# But returning the tuple here isn't compatible with our
|
363
|
+
# built-in chart implementation. And it also doesn't seem to be necessary.
|
364
|
+
# Altair already extracts the correct sort order somewhere else.
|
365
|
+
# More info about the issue here: https://github.com/streamlit/streamlit/issues/7776
|
366
|
+
return "ordinal"
|
367
|
+
elif typ in ["string", "bytes", "categorical", "boolean", "mixed", "unicode"]:
|
368
|
+
return "nominal"
|
369
|
+
elif typ in [
|
370
|
+
"datetime",
|
371
|
+
"datetime64",
|
372
|
+
"timedelta",
|
373
|
+
"timedelta64",
|
374
|
+
"date",
|
375
|
+
"time",
|
376
|
+
"period",
|
377
|
+
]:
|
378
|
+
return "temporal"
|
379
|
+
else:
|
380
|
+
# STREAMLIT MOD: I commented this out since Streamlit doesn't have a warnings object.
|
381
|
+
# warnings.warn(
|
382
|
+
# "I don't know how to infer vegalite type from '{}'. "
|
383
|
+
# "Defaulting to nominal.".format(typ),
|
384
|
+
# stacklevel=1,
|
385
|
+
# )
|
386
|
+
return "nominal"
|
387
|
+
|
388
|
+
|
389
|
+
def _get_pandas_index_attr(
|
390
|
+
data: pd.DataFrame | pd.Series,
|
391
|
+
attr: str,
|
392
|
+
) -> Any | None:
|
393
|
+
return getattr(data.index, attr, None)
|
394
|
+
|
395
|
+
|
396
|
+
def _prep_data(
|
397
|
+
df: pd.DataFrame,
|
398
|
+
x_column: str | None,
|
399
|
+
y_column_list: list[str],
|
400
|
+
color_column: str | None,
|
401
|
+
size_column: str | None,
|
402
|
+
) -> tuple[pd.DataFrame, str | None, str | None, str | None, str | None]:
|
403
|
+
"""Prepares the data for charting. This is also used in add_rows.
|
404
|
+
|
405
|
+
Returns the prepared dataframe and the new names of the x column (taking the index reset into
|
406
|
+
consideration) and y, color, and size columns.
|
407
|
+
"""
|
408
|
+
|
409
|
+
# If y is provided, but x is not, we'll use the index as x.
|
410
|
+
# So we need to pull the index into its own column.
|
411
|
+
x_column = _maybe_reset_index_in_place(df, x_column, y_column_list)
|
412
|
+
|
413
|
+
# Drop columns we're not using.
|
414
|
+
selected_data = _drop_unused_columns(
|
415
|
+
df, x_column, color_column, size_column, *y_column_list
|
416
|
+
)
|
417
|
+
|
418
|
+
# Maybe convert color to Vega colors.
|
419
|
+
_maybe_convert_color_column_in_place(selected_data, color_column)
|
420
|
+
|
421
|
+
# Make sure all columns have string names.
|
422
|
+
(
|
423
|
+
x_column,
|
424
|
+
y_column_list,
|
425
|
+
color_column,
|
426
|
+
size_column,
|
427
|
+
) = _convert_col_names_to_str_in_place(
|
428
|
+
selected_data, x_column, y_column_list, color_column, size_column
|
429
|
+
)
|
430
|
+
|
431
|
+
# Maybe melt data from wide format into long format.
|
432
|
+
melted_data, y_column, color_column = _maybe_melt(
|
433
|
+
selected_data, x_column, y_column_list, color_column, size_column
|
434
|
+
)
|
435
|
+
|
436
|
+
# Return the data, but also the new names to use for x, y, and color.
|
437
|
+
return melted_data, x_column, y_column, color_column, size_column
|
438
|
+
|
439
|
+
|
440
|
+
def _last_index_for_melted_dataframes(
|
441
|
+
data: pd.DataFrame,
|
442
|
+
) -> Hashable | None:
|
443
|
+
return cast(Hashable, data.index[-1]) if data.index.size > 0 else None
|
444
|
+
|
445
|
+
|
446
|
+
def _is_date_column(df: pd.DataFrame, name: str | None) -> bool:
|
447
|
+
"""True if the column with the given name stores datetime.date values.
|
448
|
+
|
449
|
+
This function just checks the first value in the given column, so
|
450
|
+
it's meaningful only for columns whose values all share the same type.
|
451
|
+
|
452
|
+
Parameters
|
453
|
+
----------
|
454
|
+
df : pd.DataFrame
|
455
|
+
name : str
|
456
|
+
The column name
|
457
|
+
|
458
|
+
Returns
|
459
|
+
-------
|
460
|
+
bool
|
461
|
+
|
462
|
+
"""
|
463
|
+
if name is None:
|
464
|
+
return False
|
465
|
+
|
466
|
+
column = df[name]
|
467
|
+
if column.size == 0:
|
468
|
+
return False
|
469
|
+
|
470
|
+
return isinstance(column.iloc[0], date)
|
471
|
+
|
472
|
+
|
473
|
+
def _melt_data(
|
474
|
+
df: pd.DataFrame,
|
475
|
+
columns_to_leave_alone: list[str],
|
476
|
+
columns_to_melt: list[str] | None,
|
477
|
+
new_y_column_name: str,
|
478
|
+
new_color_column_name: str,
|
479
|
+
) -> pd.DataFrame:
|
480
|
+
"""Converts a wide-format dataframe to a long-format dataframe.
|
481
|
+
|
482
|
+
You can find more info about melting on the Pandas documentation:
|
483
|
+
https://pandas.pydata.org/docs/reference/api/pandas.melt.html
|
484
|
+
|
485
|
+
Parameters
|
486
|
+
----------
|
487
|
+
df : pd.DataFrame
|
488
|
+
The dataframe to melt.
|
489
|
+
columns_to_leave_alone : list[str]
|
490
|
+
The columns to leave as they are.
|
491
|
+
columns_to_melt : list[str]
|
492
|
+
The columns to melt.
|
493
|
+
new_y_column_name : str
|
494
|
+
The name of the new column that will store the values of the melted columns.
|
495
|
+
new_color_column_name : str
|
496
|
+
The name of column that will store the original column names.
|
497
|
+
|
498
|
+
Returns
|
499
|
+
-------
|
500
|
+
pd.DataFrame
|
501
|
+
The melted dataframe.
|
502
|
+
|
503
|
+
|
504
|
+
Examples
|
505
|
+
--------
|
506
|
+
|
507
|
+
>>> import pandas as pd
|
508
|
+
>>> df = pd.DataFrame(
|
509
|
+
... {
|
510
|
+
... "a": [1, 2, 3],
|
511
|
+
... "b": [4, 5, 6],
|
512
|
+
... "c": [7, 8, 9],
|
513
|
+
... }
|
514
|
+
... )
|
515
|
+
>>> _melt_data(df, ["a"], ["b", "c"], "value", "color")
|
516
|
+
>>> a color value
|
517
|
+
>>> 0 1 b 4
|
518
|
+
>>> 1 2 b 5
|
519
|
+
>>> 2 3 b 6
|
520
|
+
>>> ...
|
521
|
+
|
522
|
+
"""
|
523
|
+
import pandas as pd
|
524
|
+
from pandas.api.types import infer_dtype
|
525
|
+
|
526
|
+
melted_df = pd.melt(
|
527
|
+
df,
|
528
|
+
id_vars=columns_to_leave_alone,
|
529
|
+
value_vars=columns_to_melt,
|
530
|
+
var_name=new_color_column_name,
|
531
|
+
value_name=new_y_column_name,
|
532
|
+
)
|
533
|
+
|
534
|
+
y_series = melted_df[new_y_column_name]
|
535
|
+
if (
|
536
|
+
y_series.dtype == "object"
|
537
|
+
and "mixed" in infer_dtype(y_series)
|
538
|
+
and len(y_series.unique()) > 100
|
539
|
+
):
|
540
|
+
raise StreamlitAPIException(
|
541
|
+
"The columns used for rendering the chart contain too many values with mixed types. Please select the columns manually via the y parameter."
|
542
|
+
)
|
543
|
+
|
544
|
+
# Arrow has problems with object types after melting two different dtypes
|
545
|
+
# pyarrow.lib.ArrowTypeError: "Expected a <TYPE> object, got a object"
|
546
|
+
fixed_df = dataframe_util.fix_arrow_incompatible_column_types(
|
547
|
+
melted_df,
|
548
|
+
selected_columns=[
|
549
|
+
*columns_to_leave_alone,
|
550
|
+
new_color_column_name,
|
551
|
+
new_y_column_name,
|
552
|
+
],
|
553
|
+
)
|
554
|
+
|
555
|
+
return fixed_df
|
556
|
+
|
557
|
+
|
558
|
+
def _maybe_reset_index_in_place(
|
559
|
+
df: pd.DataFrame, x_column: str | None, y_column_list: list[str]
|
560
|
+
) -> str | None:
|
561
|
+
if x_column is None and len(y_column_list) > 0:
|
562
|
+
if df.index.name is None:
|
563
|
+
# Pick column name that is unlikely to collide with user-given names.
|
564
|
+
x_column = _SEPARATED_INDEX_COLUMN_NAME
|
565
|
+
else:
|
566
|
+
# Reuse index's name for the new column.
|
567
|
+
x_column = df.index.name
|
568
|
+
|
569
|
+
df.index.name = x_column
|
570
|
+
df.reset_index(inplace=True) # noqa: PD002
|
571
|
+
|
572
|
+
return x_column
|
573
|
+
|
574
|
+
|
575
|
+
def _drop_unused_columns(df: pd.DataFrame, *column_names: str | None) -> pd.DataFrame:
|
576
|
+
"""Returns a subset of df, selecting only column_names that aren't None."""
|
577
|
+
|
578
|
+
# We can't just call set(col_names) because sets don't have stable ordering,
|
579
|
+
# which means tests that depend on ordering will fail.
|
580
|
+
# Performance-wise, it's not a problem, though, since this function is only ever
|
581
|
+
# used on very small lists.
|
582
|
+
seen = set()
|
583
|
+
keep = []
|
584
|
+
|
585
|
+
for x in column_names:
|
586
|
+
if x is None:
|
587
|
+
continue
|
588
|
+
if x in seen:
|
589
|
+
continue
|
590
|
+
seen.add(x)
|
591
|
+
keep.append(x)
|
592
|
+
|
593
|
+
return df[keep]
|
594
|
+
|
595
|
+
|
596
|
+
def _maybe_convert_color_column_in_place(df: pd.DataFrame, color_column: str | None):
|
597
|
+
"""If needed, convert color column to a format Vega understands."""
|
598
|
+
if color_column is None or len(df[color_column]) == 0:
|
599
|
+
return
|
600
|
+
|
601
|
+
first_color_datum = df[color_column].iloc[0]
|
602
|
+
|
603
|
+
if is_hex_color_like(first_color_datum):
|
604
|
+
# Hex is already CSS-valid.
|
605
|
+
pass
|
606
|
+
elif is_color_tuple_like(first_color_datum):
|
607
|
+
# Tuples need to be converted to CSS-valid.
|
608
|
+
df.loc[:, color_column] = df[color_column].map(to_css_color)
|
609
|
+
else:
|
610
|
+
# Other kinds of colors columns (i.e. pure numbers or nominal strings) shouldn't
|
611
|
+
# be converted since they are treated by Vega-Lite as sequential or categorical colors.
|
612
|
+
pass
|
613
|
+
|
614
|
+
|
615
|
+
def _convert_col_names_to_str_in_place(
|
616
|
+
df: pd.DataFrame,
|
617
|
+
x_column: str | None,
|
618
|
+
y_column_list: list[str],
|
619
|
+
color_column: str | None,
|
620
|
+
size_column: str | None,
|
621
|
+
) -> tuple[str | None, list[str], str | None, str | None]:
|
622
|
+
"""Converts column names to strings, since Vega-Lite does not accept ints, etc."""
|
623
|
+
import pandas as pd
|
624
|
+
|
625
|
+
column_names = list(df.columns) # list() converts RangeIndex, etc, to regular list.
|
626
|
+
str_column_names = [str(c) for c in column_names]
|
627
|
+
df.columns = pd.Index(str_column_names)
|
628
|
+
|
629
|
+
return (
|
630
|
+
None if x_column is None else str(x_column),
|
631
|
+
[str(c) for c in y_column_list],
|
632
|
+
None if color_column is None else str(color_column),
|
633
|
+
None if size_column is None else str(size_column),
|
634
|
+
)
|
635
|
+
|
636
|
+
|
637
|
+
def _parse_generic_column(
|
638
|
+
df: pd.DataFrame, column_or_value: Any
|
639
|
+
) -> tuple[str | None, Any]:
|
640
|
+
if isinstance(column_or_value, str) and column_or_value in df.columns:
|
641
|
+
column_name = column_or_value
|
642
|
+
value = None
|
643
|
+
else:
|
644
|
+
column_name = None
|
645
|
+
value = column_or_value
|
646
|
+
|
647
|
+
return column_name, value
|
648
|
+
|
649
|
+
|
650
|
+
def _parse_x_column(df: pd.DataFrame, x_from_user: str | None) -> str | None:
|
651
|
+
if x_from_user is None:
|
652
|
+
return None
|
653
|
+
|
654
|
+
elif isinstance(x_from_user, str):
|
655
|
+
if x_from_user not in df.columns:
|
656
|
+
raise StreamlitColumnNotFoundError(df, x_from_user)
|
657
|
+
|
658
|
+
return x_from_user
|
659
|
+
|
660
|
+
else:
|
661
|
+
raise StreamlitAPIException(
|
662
|
+
"x parameter should be a column name (str) or None to use the "
|
663
|
+
f" dataframe's index. Value given: {x_from_user} "
|
664
|
+
f"(type {type(x_from_user)})"
|
665
|
+
)
|
666
|
+
|
667
|
+
|
668
|
+
def _parse_y_columns(
|
669
|
+
df: pd.DataFrame,
|
670
|
+
y_from_user: str | Sequence[str] | None,
|
671
|
+
x_column: str | None,
|
672
|
+
) -> list[str]:
|
673
|
+
y_column_list: list[str] = []
|
674
|
+
|
675
|
+
if y_from_user is None:
|
676
|
+
y_column_list = list(df.columns)
|
677
|
+
|
678
|
+
elif isinstance(y_from_user, str):
|
679
|
+
y_column_list = [y_from_user]
|
680
|
+
|
681
|
+
else:
|
682
|
+
y_column_list = [
|
683
|
+
str(col) for col in dataframe_util.convert_anything_to_list(y_from_user)
|
684
|
+
]
|
685
|
+
|
686
|
+
for col in y_column_list:
|
687
|
+
if col not in df.columns:
|
688
|
+
raise StreamlitColumnNotFoundError(df, col)
|
689
|
+
|
690
|
+
# y_column_list should only include x_column when user explicitly asked for it.
|
691
|
+
if x_column in y_column_list and (not y_from_user or x_column not in y_from_user):
|
692
|
+
y_column_list.remove(x_column)
|
693
|
+
|
694
|
+
return y_column_list
|
695
|
+
|
696
|
+
|
697
|
+
def _get_offset_encoding(
|
698
|
+
chart_type: ChartType,
|
699
|
+
color_column: str | None,
|
700
|
+
) -> tuple[alt.XOffset, alt.YOffset]:
|
701
|
+
# Vega's Offset encoding channel is used to create grouped/non-stacked bar charts
|
702
|
+
import altair as alt
|
703
|
+
|
704
|
+
x_offset = alt.XOffset()
|
705
|
+
y_offset = alt.YOffset()
|
706
|
+
|
707
|
+
_color_column: str | alt.UndefinedType = (
|
708
|
+
color_column if color_column is not None else alt.utils.Undefined
|
709
|
+
)
|
710
|
+
|
711
|
+
if chart_type is ChartType.VERTICAL_BAR:
|
712
|
+
x_offset = alt.XOffset(field=_color_column)
|
713
|
+
elif chart_type is ChartType.HORIZONTAL_BAR:
|
714
|
+
y_offset = alt.YOffset(field=_color_column)
|
715
|
+
|
716
|
+
return x_offset, y_offset
|
717
|
+
|
718
|
+
|
719
|
+
def _get_opacity_encoding(
|
720
|
+
chart_type: ChartType,
|
721
|
+
stack: bool | ChartStackType | None,
|
722
|
+
color_column: str | None,
|
723
|
+
) -> alt.OpacityValue | None:
|
724
|
+
import altair as alt
|
725
|
+
|
726
|
+
# Opacity set to 0.7 for all area charts
|
727
|
+
if color_column and chart_type == ChartType.AREA:
|
728
|
+
return alt.OpacityValue(0.7)
|
729
|
+
|
730
|
+
# Layered bar chart
|
731
|
+
if color_column and stack == "layered":
|
732
|
+
return alt.OpacityValue(0.7)
|
733
|
+
|
734
|
+
return None
|
735
|
+
|
736
|
+
|
737
|
+
def _get_axis_config(df: pd.DataFrame, column_name: str | None, grid: bool) -> alt.Axis:
|
738
|
+
import altair as alt
|
739
|
+
from pandas.api.types import is_integer_dtype
|
740
|
+
|
741
|
+
if column_name is not None and is_integer_dtype(df[column_name]):
|
742
|
+
# Use a max tick size of 1 for integer columns (prevents zoom into float numbers)
|
743
|
+
# and deactivate grid lines for x-axis
|
744
|
+
return alt.Axis(tickMinStep=1, grid=grid)
|
745
|
+
|
746
|
+
return alt.Axis(grid=grid)
|
747
|
+
|
748
|
+
|
749
|
+
def _maybe_melt(
|
750
|
+
df: pd.DataFrame,
|
751
|
+
x_column: str | None,
|
752
|
+
y_column_list: list[str],
|
753
|
+
color_column: str | None,
|
754
|
+
size_column: str | None,
|
755
|
+
) -> tuple[pd.DataFrame, str | None, str | None]:
|
756
|
+
"""If multiple columns are set for y, melt the dataframe into long format."""
|
757
|
+
y_column: str | None
|
758
|
+
|
759
|
+
if len(y_column_list) == 0:
|
760
|
+
y_column = None
|
761
|
+
elif len(y_column_list) == 1:
|
762
|
+
y_column = y_column_list[0]
|
763
|
+
elif x_column is not None:
|
764
|
+
# Pick column names that are unlikely to collide with user-given names.
|
765
|
+
y_column = _MELTED_Y_COLUMN_NAME
|
766
|
+
color_column = _MELTED_COLOR_COLUMN_NAME
|
767
|
+
|
768
|
+
columns_to_leave_alone = [x_column]
|
769
|
+
if size_column:
|
770
|
+
columns_to_leave_alone.append(size_column)
|
771
|
+
|
772
|
+
df = _melt_data(
|
773
|
+
df=df,
|
774
|
+
columns_to_leave_alone=columns_to_leave_alone,
|
775
|
+
columns_to_melt=y_column_list,
|
776
|
+
new_y_column_name=y_column,
|
777
|
+
new_color_column_name=color_column,
|
778
|
+
)
|
779
|
+
|
780
|
+
return df, y_column, color_column
|
781
|
+
|
782
|
+
|
783
|
+
def _get_axis_encodings(
|
784
|
+
df: pd.DataFrame,
|
785
|
+
chart_type: ChartType,
|
786
|
+
x_column: str | None,
|
787
|
+
y_column: str | None,
|
788
|
+
x_from_user: str | None,
|
789
|
+
y_from_user: str | Sequence[str] | None,
|
790
|
+
x_axis_label: str | None,
|
791
|
+
y_axis_label: str | None,
|
792
|
+
stack: bool | ChartStackType | None,
|
793
|
+
) -> tuple[alt.X, alt.Y]:
|
794
|
+
stack_encoding: alt.X | alt.Y
|
795
|
+
if chart_type == ChartType.HORIZONTAL_BAR:
|
796
|
+
# Handle horizontal bar chart - switches x and y data:
|
797
|
+
x_encoding = _get_x_encoding(
|
798
|
+
df, y_column, y_from_user, x_axis_label, chart_type
|
799
|
+
)
|
800
|
+
y_encoding = _get_y_encoding(
|
801
|
+
df, x_column, x_from_user, y_axis_label, chart_type
|
802
|
+
)
|
803
|
+
stack_encoding = x_encoding
|
804
|
+
else:
|
805
|
+
x_encoding = _get_x_encoding(
|
806
|
+
df, x_column, x_from_user, x_axis_label, chart_type
|
807
|
+
)
|
808
|
+
y_encoding = _get_y_encoding(
|
809
|
+
df, y_column, y_from_user, y_axis_label, chart_type
|
810
|
+
)
|
811
|
+
stack_encoding = y_encoding
|
812
|
+
|
813
|
+
# Handle stacking - only relevant for bar & area charts
|
814
|
+
_update_encoding_with_stack(stack, stack_encoding)
|
815
|
+
|
816
|
+
return x_encoding, y_encoding
|
817
|
+
|
818
|
+
|
819
|
+
def _get_x_encoding(
|
820
|
+
df: pd.DataFrame,
|
821
|
+
x_column: str | None,
|
822
|
+
x_from_user: str | Sequence[str] | None,
|
823
|
+
x_axis_label: str | None,
|
824
|
+
chart_type: ChartType,
|
825
|
+
) -> alt.X:
|
826
|
+
import altair as alt
|
827
|
+
|
828
|
+
if x_column is None:
|
829
|
+
# If no field is specified, the full axis disappears when no data is present.
|
830
|
+
# Maybe a bug in vega-lite? So we pass a field that doesn't exist.
|
831
|
+
x_field = _NON_EXISTENT_COLUMN_NAME
|
832
|
+
x_title = ""
|
833
|
+
elif x_column == _SEPARATED_INDEX_COLUMN_NAME:
|
834
|
+
# If the x column name is the crazy anti-collision name we gave it, then need to set
|
835
|
+
# up a title so we never show the crazy name to the user.
|
836
|
+
x_field = x_column
|
837
|
+
# Don't show a label in the x axis (not even a nice label like
|
838
|
+
# SEPARATED_INDEX_COLUMN_TITLE) when we pull the x axis from the index.
|
839
|
+
x_title = ""
|
840
|
+
else:
|
841
|
+
x_field = x_column
|
842
|
+
|
843
|
+
# Only show a label in the x axis if the user passed a column explicitly. We
|
844
|
+
# could go either way here, but I'm keeping this to avoid breaking the existing
|
845
|
+
# behavior.
|
846
|
+
if x_from_user is None:
|
847
|
+
x_title = ""
|
848
|
+
else:
|
849
|
+
x_title = x_column
|
850
|
+
|
851
|
+
# User specified x-axis label takes precedence
|
852
|
+
if x_axis_label is not None:
|
853
|
+
x_title = x_axis_label
|
854
|
+
|
855
|
+
# grid lines on x axis for horizontal bar charts only
|
856
|
+
grid = True if chart_type == ChartType.HORIZONTAL_BAR else False
|
857
|
+
|
858
|
+
return alt.X(
|
859
|
+
x_field,
|
860
|
+
title=x_title,
|
861
|
+
type=_get_x_encoding_type(df, chart_type, x_column),
|
862
|
+
scale=alt.Scale(),
|
863
|
+
axis=_get_axis_config(df, x_column, grid=grid),
|
864
|
+
)
|
865
|
+
|
866
|
+
|
867
|
+
def _get_y_encoding(
|
868
|
+
df: pd.DataFrame,
|
869
|
+
y_column: str | None,
|
870
|
+
y_from_user: str | Sequence[str] | None,
|
871
|
+
y_axis_label: str | None,
|
872
|
+
chart_type: ChartType,
|
873
|
+
) -> alt.Y:
|
874
|
+
import altair as alt
|
875
|
+
|
876
|
+
if y_column is None:
|
877
|
+
# If no field is specified, the full axis disappears when no data is present.
|
878
|
+
# Maybe a bug in vega-lite? So we pass a field that doesn't exist.
|
879
|
+
y_field = _NON_EXISTENT_COLUMN_NAME
|
880
|
+
y_title = ""
|
881
|
+
elif y_column == _MELTED_Y_COLUMN_NAME:
|
882
|
+
# If the y column name is the crazy anti-collision name we gave it, then need to set
|
883
|
+
# up a title so we never show the crazy name to the user.
|
884
|
+
y_field = y_column
|
885
|
+
# Don't show a label in the y axis (not even a nice label like
|
886
|
+
# MELTED_Y_COLUMN_TITLE) when we pull the x axis from the index.
|
887
|
+
y_title = ""
|
888
|
+
else:
|
889
|
+
y_field = y_column
|
890
|
+
|
891
|
+
# Only show a label in the y axis if the user passed a column explicitly. We
|
892
|
+
# could go either way here, but I'm keeping this to avoid breaking the existing
|
893
|
+
# behavior.
|
894
|
+
if y_from_user is None:
|
895
|
+
y_title = ""
|
896
|
+
else:
|
897
|
+
y_title = y_column
|
898
|
+
|
899
|
+
# User specified y-axis label takes precedence
|
900
|
+
if y_axis_label is not None:
|
901
|
+
y_title = y_axis_label
|
902
|
+
|
903
|
+
# grid lines on y axis for all charts except horizontal bar charts
|
904
|
+
grid = False if chart_type == ChartType.HORIZONTAL_BAR else True
|
905
|
+
|
906
|
+
return alt.Y(
|
907
|
+
field=y_field,
|
908
|
+
title=y_title,
|
909
|
+
type=_get_y_encoding_type(df, chart_type, y_column),
|
910
|
+
scale=alt.Scale(),
|
911
|
+
axis=_get_axis_config(df, y_column, grid=grid),
|
912
|
+
)
|
913
|
+
|
914
|
+
|
915
|
+
def _update_encoding_with_stack(
|
916
|
+
stack: bool | ChartStackType | None,
|
917
|
+
encoding: alt.X | alt.Y,
|
918
|
+
) -> None:
|
919
|
+
if stack is None:
|
920
|
+
return None
|
921
|
+
# Our layered option maps to vega's stack=False option
|
922
|
+
elif stack == "layered":
|
923
|
+
stack = False
|
924
|
+
|
925
|
+
encoding["stack"] = stack
|
926
|
+
|
927
|
+
|
928
|
+
def _get_color_encoding(
|
929
|
+
df: pd.DataFrame,
|
930
|
+
color_value: Color | None,
|
931
|
+
color_column: str | None,
|
932
|
+
y_column_list: list[str],
|
933
|
+
color_from_user: str | Color | list[Color] | None,
|
934
|
+
) -> alt.Color | alt.ColorValue | None:
|
935
|
+
import altair as alt
|
936
|
+
|
937
|
+
has_color_value = color_value not in [None, [], ()] # type: ignore[comparison-overlap]
|
938
|
+
|
939
|
+
# If user passed a color value, that should win over colors coming from the
|
940
|
+
# color column (be they manual or auto-assigned due to melting)
|
941
|
+
if has_color_value:
|
942
|
+
# If the color value is color-like, return that.
|
943
|
+
if is_color_like(cast(Any, color_value)):
|
944
|
+
if len(y_column_list) != 1:
|
945
|
+
raise StreamlitColorLengthError([color_value], y_column_list)
|
946
|
+
|
947
|
+
return alt.ColorValue(to_css_color(cast(Any, color_value)))
|
948
|
+
|
949
|
+
# If the color value is a list of colors of approriate length, return that.
|
950
|
+
elif isinstance(color_value, (list, tuple)):
|
951
|
+
color_values = cast(Collection[Color], color_value)
|
952
|
+
|
953
|
+
if len(color_values) != len(y_column_list):
|
954
|
+
raise StreamlitColorLengthError(color_values, y_column_list)
|
955
|
+
|
956
|
+
if len(color_values) == 1:
|
957
|
+
return alt.ColorValue(to_css_color(cast(Any, color_value[0])))
|
958
|
+
else:
|
959
|
+
return alt.Color(
|
960
|
+
field=color_column
|
961
|
+
if color_column is not None
|
962
|
+
else alt.utils.Undefined,
|
963
|
+
scale=alt.Scale(range=[to_css_color(c) for c in color_values]),
|
964
|
+
legend=_COLOR_LEGEND_SETTINGS,
|
965
|
+
type="nominal",
|
966
|
+
title=" ",
|
967
|
+
)
|
968
|
+
|
969
|
+
raise StreamlitInvalidColorError(df, color_from_user)
|
970
|
+
|
971
|
+
elif color_column is not None:
|
972
|
+
column_type: VegaLiteType
|
973
|
+
|
974
|
+
if color_column == _MELTED_COLOR_COLUMN_NAME:
|
975
|
+
column_type = "nominal"
|
976
|
+
else:
|
977
|
+
column_type = _infer_vegalite_type(df[color_column])
|
978
|
+
|
979
|
+
color_enc = alt.Color(
|
980
|
+
field=color_column, legend=_COLOR_LEGEND_SETTINGS, type=column_type
|
981
|
+
)
|
982
|
+
|
983
|
+
# Fix title if DF was melted
|
984
|
+
if color_column == _MELTED_COLOR_COLUMN_NAME:
|
985
|
+
# This has to contain an empty space, otherwise the
|
986
|
+
# full y-axis disappears (maybe a bug in vega-lite)?
|
987
|
+
color_enc["title"] = " "
|
988
|
+
|
989
|
+
# If the 0th element in the color column looks like a color, we'll use the color column's
|
990
|
+
# values as the colors in our chart.
|
991
|
+
elif len(df[color_column]) and is_color_like(df[color_column].iloc[0]):
|
992
|
+
color_range = [to_css_color(c) for c in df[color_column].unique()]
|
993
|
+
color_enc["scale"] = alt.Scale(range=color_range)
|
994
|
+
# Don't show the color legend, because it will just show text with the color values,
|
995
|
+
# like #f00, #00f, etc, which are not user-readable.
|
996
|
+
color_enc["legend"] = None
|
997
|
+
|
998
|
+
# Otherwise, let Vega-Lite auto-assign colors.
|
999
|
+
# This codepath is typically reached when the color column contains numbers (in which case
|
1000
|
+
# Vega-Lite uses a color gradient to represent them) or strings (in which case Vega-Lite
|
1001
|
+
# assigns one color for each unique value).
|
1002
|
+
else:
|
1003
|
+
pass
|
1004
|
+
|
1005
|
+
return color_enc
|
1006
|
+
|
1007
|
+
return None
|
1008
|
+
|
1009
|
+
|
1010
|
+
def _get_size_encoding(
|
1011
|
+
chart_type: ChartType,
|
1012
|
+
size_column: str | None,
|
1013
|
+
size_value: str | float | None,
|
1014
|
+
) -> alt.Size | alt.SizeValue | None:
|
1015
|
+
import altair as alt
|
1016
|
+
|
1017
|
+
if chart_type == ChartType.SCATTER:
|
1018
|
+
if size_column is not None:
|
1019
|
+
return alt.Size(
|
1020
|
+
size_column,
|
1021
|
+
legend=_SIZE_LEGEND_SETTINGS,
|
1022
|
+
)
|
1023
|
+
|
1024
|
+
elif isinstance(size_value, (float, int)):
|
1025
|
+
return alt.SizeValue(size_value)
|
1026
|
+
elif size_value is None:
|
1027
|
+
return alt.SizeValue(100)
|
1028
|
+
else:
|
1029
|
+
raise StreamlitAPIException(
|
1030
|
+
f"This does not look like a valid size: {repr(size_value)}"
|
1031
|
+
)
|
1032
|
+
|
1033
|
+
elif size_column is not None or size_value is not None:
|
1034
|
+
raise Error(
|
1035
|
+
f"Chart type {chart_type.name} does not support size argument. "
|
1036
|
+
"This should never happen!"
|
1037
|
+
)
|
1038
|
+
|
1039
|
+
return None
|
1040
|
+
|
1041
|
+
|
1042
|
+
def _get_tooltip_encoding(
|
1043
|
+
x_column: str,
|
1044
|
+
y_column: str,
|
1045
|
+
size_column: str | None,
|
1046
|
+
color_column: str | None,
|
1047
|
+
color_enc: alt.Color | alt.ColorValue | None,
|
1048
|
+
) -> list[alt.Tooltip]:
|
1049
|
+
import altair as alt
|
1050
|
+
|
1051
|
+
tooltip = []
|
1052
|
+
|
1053
|
+
# If the x column name is the crazy anti-collision name we gave it, then need to set
|
1054
|
+
# up a tooltip title so we never show the crazy name to the user.
|
1055
|
+
if x_column == _SEPARATED_INDEX_COLUMN_NAME:
|
1056
|
+
tooltip.append(alt.Tooltip(x_column, title=_SEPARATED_INDEX_COLUMN_TITLE))
|
1057
|
+
else:
|
1058
|
+
tooltip.append(alt.Tooltip(x_column))
|
1059
|
+
|
1060
|
+
# If the y column name is the crazy anti-collision name we gave it, then need to set
|
1061
|
+
# up a tooltip title so we never show the crazy name to the user.
|
1062
|
+
if y_column == _MELTED_Y_COLUMN_NAME:
|
1063
|
+
tooltip.append(
|
1064
|
+
alt.Tooltip(
|
1065
|
+
y_column,
|
1066
|
+
title=_MELTED_Y_COLUMN_TITLE,
|
1067
|
+
type="quantitative", # Just picked something random. Doesn't really matter!
|
1068
|
+
)
|
1069
|
+
)
|
1070
|
+
else:
|
1071
|
+
tooltip.append(alt.Tooltip(y_column))
|
1072
|
+
|
1073
|
+
# If we earlier decided that there should be no color legend, that's because the
|
1074
|
+
# user passed a color column with actual color values (like "#ff0"), so we should
|
1075
|
+
# not show the color values in the tooltip.
|
1076
|
+
if color_column and getattr(color_enc, "legend", True) is not None:
|
1077
|
+
# Use a human-readable title for the color.
|
1078
|
+
if color_column == _MELTED_COLOR_COLUMN_NAME:
|
1079
|
+
tooltip.append(
|
1080
|
+
alt.Tooltip(
|
1081
|
+
color_column,
|
1082
|
+
title=_MELTED_COLOR_COLUMN_TITLE,
|
1083
|
+
type="nominal",
|
1084
|
+
)
|
1085
|
+
)
|
1086
|
+
else:
|
1087
|
+
tooltip.append(alt.Tooltip(color_column))
|
1088
|
+
|
1089
|
+
if size_column:
|
1090
|
+
tooltip.append(alt.Tooltip(size_column))
|
1091
|
+
|
1092
|
+
return tooltip
|
1093
|
+
|
1094
|
+
|
1095
|
+
def _get_x_encoding_type(
|
1096
|
+
df: pd.DataFrame, chart_type: ChartType, x_column: str | None
|
1097
|
+
) -> VegaLiteType:
|
1098
|
+
if x_column is None:
|
1099
|
+
return "quantitative" # Anything. If None, Vega-Lite may hide the axis.
|
1100
|
+
|
1101
|
+
# Vertical bar charts should have a discrete (ordinal) x-axis, UNLESS type is date/time
|
1102
|
+
# https://github.com/streamlit/streamlit/pull/2097#issuecomment-714802475
|
1103
|
+
if chart_type == ChartType.VERTICAL_BAR and not _is_date_column(df, x_column):
|
1104
|
+
return "ordinal"
|
1105
|
+
|
1106
|
+
return _infer_vegalite_type(df[x_column])
|
1107
|
+
|
1108
|
+
|
1109
|
+
def _get_y_encoding_type(
|
1110
|
+
df: pd.DataFrame, chart_type: ChartType, y_column: str | None
|
1111
|
+
) -> VegaLiteType:
|
1112
|
+
# Horizontal bar charts should have a discrete (ordinal) y-axis, UNLESS type is date/time
|
1113
|
+
if chart_type == ChartType.HORIZONTAL_BAR and not _is_date_column(df, y_column):
|
1114
|
+
return "ordinal"
|
1115
|
+
|
1116
|
+
if y_column:
|
1117
|
+
return _infer_vegalite_type(df[y_column])
|
1118
|
+
|
1119
|
+
return "quantitative" # Pick anything. If undefined, Vega-Lite may hide the axis.
|
1120
|
+
|
1121
|
+
|
1122
|
+
class StreamlitColumnNotFoundError(StreamlitAPIException):
|
1123
|
+
def __init__(self, df, col_name, *args):
|
1124
|
+
available_columns = ", ".join(str(c) for c in list(df.columns))
|
1125
|
+
message = (
|
1126
|
+
f'Data does not have a column named `"{col_name}"`. '
|
1127
|
+
f"Available columns are `{available_columns}`"
|
1128
|
+
)
|
1129
|
+
super().__init__(message, *args)
|
1130
|
+
|
1131
|
+
|
1132
|
+
class StreamlitInvalidColorError(StreamlitAPIException):
|
1133
|
+
def __init__(self, df, color_from_user, *args):
|
1134
|
+
", ".join(str(c) for c in list(df.columns))
|
1135
|
+
message = f"""
|
1136
|
+
This does not look like a valid color argument: `{color_from_user}`.
|
1137
|
+
|
1138
|
+
The color argument can be:
|
1139
|
+
|
1140
|
+
* A hex string like "#ffaa00" or "#ffaa0088".
|
1141
|
+
* An RGB or RGBA tuple with the red, green, blue, and alpha
|
1142
|
+
components specified as ints from 0 to 255 or floats from 0.0 to
|
1143
|
+
1.0.
|
1144
|
+
* The name of a column.
|
1145
|
+
* Or a list of colors, matching the number of y columns to draw.
|
1146
|
+
"""
|
1147
|
+
super().__init__(message, *args)
|
1148
|
+
|
1149
|
+
|
1150
|
+
class StreamlitColorLengthError(StreamlitAPIException):
|
1151
|
+
def __init__(self, color_values, y_column_list, *args):
|
1152
|
+
message = (
|
1153
|
+
f"The list of colors `{color_values}` must have the same "
|
1154
|
+
"length as the list of columns to be colored "
|
1155
|
+
f"`{y_column_list}`."
|
1156
|
+
)
|
1157
|
+
super().__init__(message, *args)
|