Intro to custom components

开发Streamlit组件的第一步是决定是创建一个静态组件(即一次性渲染,由Python控制)还是创建一个可以在Python和JavaScript之间双向通信的组件。

如果您创建Streamlit组件的目标仅仅是显示HTML代码或从Python可视化库渲染图表,Streamlit提供了两种方法,可以大大简化这个过程:components.html()components.iframe()

如果您不确定是否需要双向通信,请首先从这里开始

虽然st.textst.markdownst.write使得向Streamlit应用程序写入文本变得容易,但有时你可能更愿意实现一个自定义的HTML片段。同样,虽然Streamlit原生支持许多图表库,但你可能希望为新的图表库实现特定的HTML/JavaScript模板。components.html通过让你能够在Streamlit应用程序中嵌入一个包含你所需输出的iframe来实现这一功能。

示例

import streamlit as st import streamlit.components.v1 as components # bootstrap 4 collapse example components.html( """ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> <div id="accordion"> <div class="card"> <div class="card-header" id="headingOne"> <h5 class="mb-0"> <button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> Collapsible Group Item #1 </button> </h5> </div> <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"> <div class="card-body"> Collapsible Group Item #1 content </div> </div> </div> <div class="card"> <div class="card-header" id="headingTwo"> <h5 class="mb-0"> <button class="btn btn-link collapsed" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> Collapsible Group Item #2 </button> </h5> </div> <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordion"> <div class="card-body"> Collapsible Group Item #2 content </div> </div> </div> </div> """, height=600, )

components.iframe 在功能上与 components.html 类似,不同之处在于 components.iframe 接受一个 URL 作为输入。这适用于您希望在 Streamlit 应用程序中包含整个页面的情况。

示例

import streamlit as st import streamlit.components.v1 as components # embed streamlit docs in a streamlit app components.iframe("https://example.com", height=500)

一个双向的Streamlit组件有两个部分:

  1. 一个前端,由HTML和您喜欢的任何其他网络技术(JavaScript、React、Vue等)构建,并通过iframe标签在Streamlit应用程序中呈现。
  2. 一个Python API,Streamlit 应用程序使用它来实例化并与前端进行通信

为了使创建双向Streamlit组件的过程更加容易,我们在Streamlit组件模板GitHub仓库中创建了一个React模板和一个仅TypeScript的模板。我们还在同一个仓库中提供了一些示例组件

要构建一个Streamlit组件,您需要在开发环境中安装以下内容:

克隆component-template GitHub 仓库,然后决定是否要使用 React.js ("template") 或纯 TypeScript ("template-reactless") 模板。

  1. 从终端初始化和构建组件模板前端:

    # React 模板 template/my_component/frontend npm install # 初始化项目并安装 npm 依赖 npm run start # 启动 Webpack 开发服务器 # 或者 # 仅 TypeScript 模板 template-reactless/my_component/frontend npm install # 初始化项目并安装 npm 依赖 npm run start # 启动 Webpack 开发服务器
  2. 从另一个终端,运行声明并使用组件的Streamlit应用程序(Python):

    # React模板 cd template . venv/bin/activate # 或类似命令以激活安装了Streamlit的venv/conda环境 pip install -e . # 将模板安装为可编辑包 streamlit run my_component/example.py # 运行示例 # 或者 # 仅TypeScript模板 cd template-reactless . venv/bin/activate # 或类似命令以激活安装了Streamlit的venv/conda环境 pip install -e . # 将模板安装为可编辑包 streamlit run my_component/example.py # 运行示例

运行上述步骤后,您应该在浏览器中看到一个如下所示的Streamlit应用程序:

Streamlit Component Example App

模板中的示例应用程序展示了如何实现双向通信。Streamlit 组件显示一个按钮(Python → JavaScript),最终用户可以点击该按钮。每次点击按钮时,JavaScript 前端会增加计数器值并将其传递回 Python(JavaScript → Python),然后由 Streamlit 显示(Python → JavaScript)。

因为每个Streamlit组件都是自己的网页,会被渲染到iframe中,所以你可以使用几乎任何你喜欢的网页技术来创建那个网页。我们在Streamlit的Components-template GitHub仓库中提供了两个模板来帮助你开始;其中一个模板使用了React,而另一个则没有。

push_pin

注意

即使您还不熟悉React,您可能仍然想查看基于React的模板。它处理了从Streamlit发送和接收数据所需的大部分样板代码,您可以在使用过程中学习所需的React知识。

如果您不想使用React,也请阅读本节!它解释了Streamlit ↔ 组件通信的基础知识。

React

基于React的模板位于template/my_component/frontend/src/MyComponent.tsx

  • MyComponent.render() 当组件需要重新渲染时自动调用(就像在任何 React 应用中一样)
  • 从Python脚本传递的参数可以通过this.props.args字典获取:
# Send arguments in Python: result = my_component(greeting="Hello", name="Streamlit")
// Receive arguments in frontend: let greeting = this.props.args["greeting"]; // greeting = "Hello" let name = this.props.args["name"]; // name = "Streamlit"
  • 使用 Streamlit.setComponentValue() 将数据从组件返回到 Python 脚本:
// Set value in frontend: Streamlit.setComponentValue(3.14);
# Access value in Python: result = my_component(greeting="Hello", name="Streamlit") st.write("result = ", result) # result = 3.14

当你调用Streamlit.setComponentValue(new_value)时,这个新值会被发送到Streamlit,然后重新从头到尾执行Python脚本。当脚本重新执行时,调用my_component(...)将返回新值。

代码流程的角度来看,似乎你正在与前端同步传输数据:Python将参数发送给JavaScript,JavaScript将值返回给Python,所有这些都在一个函数调用中完成!但实际上,这一切都是异步发生的,而Python脚本的重新执行才是实现这一技巧的关键。

  • 使用 Streamlit.setFrameHeight() 来控制组件的高度。默认情况下,React 模板会自动调用此方法(参见 StreamlitComponentBase.componentDidUpdate())。如果需要更多控制,可以覆盖此行为。
  • 文件的最后一行有一点小魔法:export default withStreamlitConnection(MyComponent) - 这与Streamlit进行了一些握手,并设置了双向数据通信的机制。

仅限TypeScript

仅限TypeScript的模板位于template-reactless/my_component/frontend/src/MyComponent.tsx

这个模板比它的React版本有更多的代码,因为所有握手、设置事件监听器和更新组件框架高度的机制都是手动完成的。React版本的模板自动处理了大部分这些细节。

  • 在源文件的底部,模板调用Streamlit.setComponentReady()来告诉Streamlit它已准备好开始接收数据。(通常,您会在创建并加载组件所依赖的所有内容后执行此操作。)
  • 它订阅了Streamlit.RENDER_EVENT以在需要重新绘制时收到通知。(此事件在调用setComponentReady之前不会触发)
  • 在其onRender事件处理程序中,它通过event.detail.args访问Python脚本中传递的参数
  • 它以与React模板相同的方式将数据发送回Python脚本——点击“Click Me!”按钮会调用Streamlit.setComponentValue()
  • 它通过Streamlit.setFrameHeight()通知Streamlit其高度可能已更改

使用主题

push_pin

注意

自定义组件主题支持需要 streamlit-component-lib 版本 1.2.0 或更高。

除了向您的组件发送一个args对象外,Streamlit 还会发送一个theme对象,定义当前主题,以便您的组件可以以兼容的方式调整其样式。此对象与args在同一消息中发送,因此可以通过this.props.theme(使用 React 模板时)或event.detail.theme(使用纯 TypeScript 模板时)访问。

theme 对象具有以下形状:

{ "base": "lightORdark", "primaryColor": "someColor1", "backgroundColor": "someColor2", "secondaryBackgroundColor": "someColor3", "textColor": "someColor4", "font": "someFont" }

base 选项允许您指定一个预设的 Streamlit 主题,您的自定义主题将继承该主题。任何未在您的主题设置中定义的主题配置选项都将使用基础主题的值。base 的有效值为 "light""dark"

请注意,主题对象具有与通过命令streamlit config show打印的配置选项中的“theme”部分相同的名称和语义的字段。

使用 React 模板时,以下 CSS 变量也会自动设置。

--base --primary-color --background-color --secondary-background-color --text-color --font

如果你不熟悉 CSS变量, 简而言之,你可以这样使用它们:

.mySelector { color: var(--text-color); }

这些变量与上面定义的theme对象中的字段匹配,是否在组件中使用CSS变量或主题对象是个人偏好的问题。

其他前端细节

  • 因为你是通过开发服务器(通过npm run start)托管你的组件,所以当你保存时,你所做的任何更改都应该自动反映在Streamlit应用中。
  • 如果你想为你的组件添加更多的包,可以在组件的frontend/目录中运行npm add来添加它们。
npm add baseui
  • 要构建组件的静态版本,请运行 npm run export。有关更多信息,请参阅 准备您的组件

components.declare_component() 是创建你的组件的 Python API 所需的全部内容:

import streamlit.components.v1 as components my_component = components.declare_component( "my_component", url="http://localhost:3001" )

然后,您可以使用返回的my_component函数与您的前端代码发送和接收数据:

# Send data to the frontend using named arguments. return_value = my_component(name="Blackbeard", ship="Queen Anne's Revenge") # `my_component`'s return value is the data returned from the frontend. st.write("Value = ", return_value)

虽然上述内容是从Python端定义工作组件所需的全部内容,但我们建议创建一个带有命名参数和默认值、输入验证等的“包装”函数。这将使最终用户更容易理解您的函数接受哪些数据值,并允许定义有用的文档字符串。

请参阅此示例来自组件模板,以了解创建包装函数的示例。

Python → 前端

你通过将关键字参数传递给你的组件的invoke函数(即从declare_component返回的函数)来将数据从Python发送到前端。你可以从Python发送以下类型的数据到前端:

  • 任何可JSON序列化的数据
  • numpy.array
  • pandas.DataFrame

任何可JSON序列化的数据都会被序列化为JSON字符串,并反序列化为其JavaScript等效对象。numpy.arraypandas.DataFrame 使用 Apache Arrow 进行序列化,并反序列化为 ArrowTable 的实例,这是一个自定义类型,封装了Arrow结构并在其上提供了方便的API。

查看CustomDataframeSelectableDataTable组件的示例代码,了解更多关于如何使用ArrowTable的上下文。

前端 → Python

你通过Streamlit.setComponentValue() API(这是模板代码的一部分)将数据从前端发送到Python。与从Python → 前端传递参数不同,这个API只接受一个值。如果你想返回多个值,你需要将它们包装在ArrayObject中。

自定义组件可以从前端发送可JSON序列化的数据到Python,以及Apache Arrow ArrowTables来表示数据框。

forum

还有问题吗?

我们的 论坛 充满了有用的信息和Streamlit专家。