# ex4nicegui
<div align="center">
简体中文| [English](./README.en.md)
</div>
对 [nicegui](https://github.com/zauberzeug/nicegui) 做的扩展库。内置响应式组件,完全实现数据响应式界面编程。
![todo-app](https://gitee.com/carson_add/ex4nicegui-examples/raw/main/asset/todo-app.01.gif)
![todo-app](https://gitee.com/carson_add/ex4nicegui-examples/raw/main/asset/todo-app.02.gif)
[查看更多示例](https://gitee.com/carson_add/ex4nicegui-examples)
---
## 教程
[头条文章-秒杀官方实现,python界面库,去掉90%事件代码的nicegui](https://www.toutiao.com/item/7253786340574265860/)
[微信公众号-秒杀官方实现,python界面库,去掉90%事件代码的nicegui](https://mp.weixin.qq.com/s?__biz=MzUzNDk1MTc5Mw==&mid=2247486796&idx=1&sn=457ed6fb9d6a25145f7704d5197d670d&chksm=fa8daf52cdfa2644bede50ae7f2551162ecaedecafec231ee4ce8f28775a599f8669ecf06af1#rd)
## 📦 安装
```
pip install ex4nicegui -U
```
---
## 入门
我们从一个简单的计数器应用开始,用户可以通过点击按钮让计数增加或减少。
![counter](https://gitee.com/carson_add/ex4nicegui-examples/raw/main/asset/counter.gif)
下面是完整代码:
```python
from nicegui import ui
from ex4nicegui import rxui
# 数据状态代码
class Counter(rxui.ViewModel):
count: int = 0
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
# 界面代码
counter = Counter()
with ui.row(align_items="center"):
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count)
ui.button(icon="add", on_click=counter.increment)
ui.run()
```
---
现在看更多细节。`ex4nicegui` 遵从数据驱动方式定义界面。状态数据定义应用程序中所有可以变化的数据。
下面是 `Counter` 状态数据定义:
```python
class Counter(rxui.ViewModel):
count: int = 0
```
- 自定义类需要继承 `rxui.ViewModel`
- 这里定义了一个变量 `count`,表示计数器的当前值,初始值为 0
接着,在类中定义一系列操作数据的方法:
```python
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
```
- 这些都是实例方法,可以修改 `count` 变量的值
然后,在界面代码中,实例化 `Counter` 的对象。
```python
counter = Counter()
```
我们通过 `rxui.label` 组件绑定 `count` 变量。把操作数据的方法绑定到按钮点击事件上。
```python
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count)
ui.button(icon="add", on_click=counter.increment)
```
- 我们需要使用 `rxui` 命名空间下的 `label` 组件,而不是 `nicegui` 命名空间下的 `label` 组件。
- `rxui.label` 组件绑定 `counter.count` 变量,当 `counter.count` 变化时,`rxui.label` 组件自动更新。
- `ui.button` 组件绑定 `counter.decrement` 和 `counter.increment` 方法,点击按钮时调用相应方法。
> 在复杂项目中,`Counter` 定义的代码可以放到单独的模块中,然后在界面代码中导入。
注意,当类变量名前面带有下划线时,数据状态不会自动更新。
```python
class Counter(rxui.ViewModel):
count: int = 0 # 响应式数据,能自动同步界面
_count: int = 0 # 这里的下划线表示私有变量,不会自动同步界面
```
---
### 二次计算
接着前面的例子,我们再添加一个功能。当计数器的值小于 0 时,字体显示为红色,大于 0 时显示为绿色,否则显示为黑色。
```python
# 数据状态代码
class Counter(rxui.ViewModel):
count: int = 0
def text_color(self):
if self.count > 0:
return "green"
elif self.count < 0:
return "red"
else:
return "black"
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
# 界面代码
counter = Counter()
with ui.row(align_items="center"):
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count).bind_color(counter.text_color)
ui.button(icon="add", on_click=counter.increment)
```
颜色值是依据计数器当前值计算得到的。属于二次计算。通过定义普通的实例函数即可。
```python
def text_color(self):
if self.count > 0:
return "green"
elif self.count < 0:
return "red"
else:
return "black"
```
然后,通过 `rxui.label` 组件的 `bind_color` 方法绑定 `text_color` 方法,使得颜色值自动更新。
```python
rxui.label(counter.count).bind_color(counter.text_color)
```
### 二次计算缓存
现在,我们在计数器下方使用文字,显示当前计数器的颜色文本值。
```python
...
# 数据状态代码
class Counter(rxui.ViewModel):
...
# 界面代码
counter = Counter()
with ui.row(align_items="center"):
ui.button(icon="remove", on_click=counter.decrement)
rxui.label(counter.count).bind_color(counter.text_color)
ui.button(icon="add", on_click=counter.increment)
rxui.label(lambda: f"当前计数器值为 {counter.count}, 颜色值为 {counter.text_color()}")
```
- 当二次计算非常简单时,可以直接使用 lambda 表达式
上面的代码中,有两个地方使用了 `counter.text_color` 方法。当 `counter.count` 变化时,`counter.text_color` 会执行两次计算。第二次计算是多余的。
为了避免多余的计算,我们可以把 `counter.text_color` 缓存起来。
```python
# 数据状态代码
class Counter(rxui.ViewModel):
count: int = 0
@rxui.cached_var
def text_color(self):
if self.count > 0:
return "green"
elif self.count < 0:
return "red"
else:
return "black"
```
- `rxui.cached_var` 装饰器可以把函数结果缓存起来,避免多余的计算。
### 列表
下面的示例,展示了如何使用列表。
```python
class AppState(rxui.ViewModel):
nums = []
# nums = [1,2,3] ❌ 如果需要初始化,必须在 __init__ 中设置
def __init__(self):
super().__init__()
self.nums = [1, 2, 3]
def append(self):
new_num = max(self.nums) + 1
self.nums.append(new_num)
def pop(self):
self.nums.pop()
def reverse(self):
self.nums.reverse()
def display_nums(self):
return ", ".join(map(str, self.nums))
# 界面代码
state = AppState()
with ui.row(align_items="center"):
ui.button("append", on_click=state.append)
ui.button("pop", on_click=state.pop)
ui.button("reverse", on_click=state.reverse)
rxui.label(state.display_nums)
```
如果你需要在定义列表时,初始化列表,建议在 `__init__` 中设置。
```python
class AppState(rxui.ViewModel):
nums = []
# nums = [1,2,3] ❌ 如果需要初始化,必须在 __init__ 中设置
def __init__(self):
super().__init__()
self.nums = [1, 2, 3]
...
```
另一种方式是使用 `rxui.list_var`
```python
class AppState(rxui.ViewModel):
# nums = []
# nums = [1,2,3] ❌ 如果需要初始化,必须在 __init__ 中设置
nums = rxui.list_var(lambda: [1, 2, 3])
...
```
- `rxui.list_var` 参数是一个返回列表的函数
### 列表循环
定义列表后,我们可以用 `effect_refreshable.on` 装饰器,在界面中展示列表数据。
下面的例子中,界面会动态展示下拉框选中的图标
```python
from ex4nicegui import rxui, effect_refreshable
class AppState(rxui.ViewModel):
icons = []
_option_icons = ["font_download", "warning", "format_size", "print"]
state = AppState()
# 界面代码
with ui.row(align_items="center"):
@effect_refreshable.on(state.icons)
def _():
for icon in state.icons:
ui.icon(icon, size="2rem")
rxui.select(state._option_icons, value=state.icons, multiple=True)
```
其中,`@effect_refreshable.on(state.icons)` 明确指定了依赖关系。当 `state.icons` 变化时,`_` 函数会重新执行。
```python
@effect_refreshable.on(state.icons)
def _():
# 这里的代码会在 state.icons 变化时重新执行
...
```
> 注意,每次执行,里面的内容都会被清除。这是数据驱动版本的 `ui.refreshable`
原则上,可以不通过 `.on` 指定监控的数据,只要函数中使用到的"响应式数据",都会自动监控
```python
@effect_refreshable # 没有使用 .on(state.icons)
def _():
# 这里读取了 state.icons,因此会自动监控
for icon in state.icons:
ui.icon(icon, size="2rem")
```
> 建议总是通过 `.on` 指定依赖关系,避免预料之外的刷新
---
### 数据持久化
`ViewModel` 使用代理对象创建响应式数据,当需要保存数据时,可以使用 `rxui.ViewModel.to_value` 转换成普通数据.
下面的例子,点击按钮将显示 my_app 的状态数据字典。
```python
from nicegui import ui
from ex4nicegui import rxui
class MyApp(rxui.ViewModel):
a = 0
sign = "+"
b = 0
def show_data(self):
# >> {"a": 0, "sign": '+, "b": 0}
return rxui.ViewModel.to_value(self)
def show_a(self):
# >> 0
return rxui.ViewModel.to_value(self.a)
my_app = MyApp()
rxui.number(value=my_app.a, min=0, max=10)
rxui.radio(["+", "-", "*", "/"], value=my_app.sign)
rxui.number(value=my_app.b, min=0, max=10)
ui.button("show data", on_click=lambda: ui.notify(my_app.show_data()))
```
结合 `rxui.ViewModel.on_refs_changed` ,可以在数据变化时,自动保存数据到本地。
```python
from nicegui import ui
from ex4nicegui import rxui
from pathlib import Path
import json
class MyApp(rxui.ViewModel):
a = 0
sign = "+"
b = 0
_json_path = Path(__file__).parent / "data.json"
def __init__(self):
super().__init__()
@rxui.ViewModel.on_refs_changed(self)
def _():
# a, sign, b 任意一个值变化时,自动保存到本地
self._json_path.write_text(json.dumps(self.show_data()))
def show_data(self):
return rxui.ViewModel.to_value(self)
...
```
---
- [ex4nicegui](#ex4nicegui)
- [教程](#教程)
- [📦 安装](#-安装)
- [入门](#入门)
- [二次计算](#二次计算)
- [二次计算缓存](#二次计算缓存)
- [列表](#列表)
- [列表循环](#列表循环)
- [数据持久化](#数据持久化)
- [apis](#apis)
- [ViewModel](#viewmodel)
- [使用列表](#使用列表)
- [响应式](#响应式)
- [`to_ref`](#to_ref)
- [`deep_ref`](#deep_ref)
- [`effect`](#effect)
- [`ref_computed`](#ref_computed)
- [`async_computed`](#async_computed)
- [`on`](#on)
- [`new_scope`](#new_scope)
- [组件功能](#组件功能)
- [vmodel](#vmodel)
- [vfor](#vfor)
- [bind\_classes](#bind_classes)
- [bind\_style](#bind_style)
- [bind\_prop](#bind_prop)
- [rxui.echarts](#rxuiecharts)
- [echarts 图表鼠标事件](#echarts-图表鼠标事件)
- [rxui.echarts.from\_javascript](#rxuiechartsfrom_javascript)
- [rxui.echarts.register\_map](#rxuiechartsregister_map)
- [tab\_panels](#tab_panels)
- [lazy\_tab\_panels](#lazy_tab_panels)
- [scoped\_style](#scoped_style)
- [BI 模块](#bi-模块)
- [`bi.data_source`](#bidata_source)
- [ui\_select](#ui_select)
- [ui\_table](#ui_table)
- [ui\_aggrid](#ui_aggrid)
---
## apis
### ViewModel
在 `v0.7.0` 版本中,引入 `ViewModel` 类,用于管理一组响应式数据。
下面是一个简单的计算器示例:
1. 当用户修改数值输入框或符号选择框,右侧会自动显示计算结果
2. 当结果小于 0 时,结果显示为红色,否则为黑色
```python
from ex4nicegui import rxui
class Calculator(rxui.ViewModel):
num1 = 0
sign = "+"
num2 = 0
@rxui.cached_var
def result(self):
# 当 num1,sign,num2 任意一个值发生变化时,result 也会重新计算
return eval(f"{self.num1}{self.sign}{self.num2}")
# 每个对象拥有独立的数据
calc = Calculator()
with ui.row(align_items="center"):
rxui.number(value=calc.num1, label="Number 1")
rxui.select(value=calc.sign, options=["+", "-", "*", "/"], label="Sign")
rxui.number(value=calc.num2, label="Number 2")
ui.label("=")
rxui.label(calc.result).bind_color(
lambda: "red" if calc.result() < 0 else "black"
)
```
#### 使用列表
下面的示例,每个 person 使用卡片展示。最上方显示所有人的平均年龄。当个人年龄大于平均年龄,卡片外边框将变为红色。
通过 `number` 组件修改年龄,一切都会自动更新。
```python
from typing import List
from ex4nicegui import rxui
from itertools import count
from nicegui import ui
id_generator = count()
class Person(rxui.ViewModel):
name = ""
age = 0
def __init__(self, name: str, age: int):
super().__init__()
self.name = name
self.age = age
self.id = next(id_generator)
class Home(rxui.ViewModel):
persons: List[Person] = []
deleted_person_index = 0
@rxui.cached_var
def avg_age(self) -> float:
if len(self.persons) == 0:
return 0
return round(sum(p.age for p in self.persons) / len(self.persons), 2)
def avg_name_length(self):
if len(self.persons) == 0:
return 0
return round(sum(len(p.name) for p in self.persons) / len(self.persons), 2)
def delete_person(self):
if self.deleted_person_index < len(self.persons):
del self.persons[int(self.deleted_person_index)]
def sample_data(self):
self.persons = [
Person("alice", 25),
Person("bob", 30),
Person("charlie", 31),
Person("dave", 22),
Person("eve", 26),
Person("frank", 29),
]
home = Home()
home.sample_data()
rxui.label(lambda: f"平均年龄: {home.avg_age()}")
rxui.label(lambda: f"平均名字长度: {home.avg_name_length()}")
rxui.number(
value=home.deleted_person_index, min=0, max=lambda: len(home.persons) - 1, step=1
)
ui.button("删除", on_click=home.delete_person)
with ui.row():
@rxui.vfor(home.persons, key="id")
def _(store: rxui.VforStore[Person]):
person = store.get_item()
with rxui.card().classes("outline").bind_classes(
{
"outline-red-500": lambda: person.age > home.avg_age(),
}
):
rxui.input(value=person.name, placeholder="名字")
rxui.number(value=person.age, min=1, max=100, step=1, placeholder="年龄")
ui.run()
```
如果你觉得 `rxui.vfor` 代码过于复杂,可以使用 `effect_refreshable` 装饰器代替。
```python
from ex4nicegui import rxui, Ref,effect_refreshable
...
# 明确指定监控 home.persons 变化,可以避免意外刷新
@effect_refreshable.on(home.persons)
def _():
for person in home.persons.value:
...
rxui.number(value=person.age, min=1, max=100, step=1, placeholder="年龄")
...
```
需要注意到,每当 `home.persons` 列表变化时(比如新增或删除元素),`effect_refreshable` 装饰的函数都会重新执行。意味着所有元素都会重新创建。
更多复杂的应用,可以查看 [examples](./examples)
---
### 响应式
```python
from ex4nicegui import (
to_ref,
ref_computed,
on,
effect,
effect_refreshable,
batch,
event_batch,
deep_ref,
async_computed
)
```
常用 `to_ref`,`deep_ref`,`effect`,`ref_computed`,`on`,`async_computed`
---
#### `to_ref`
定义响应式对象,通过 `.value` 读写
```python
a = to_ref(1)
b = to_ref("text")
a.value =2
b.value = 'new text'
print(a.value)
```
当值为复杂对象时,默认不会保持嵌套对象的响应性。
```python
a = to_ref([1,2])
@effect
def _():
print('len:',len(a.value))
# 不会触发 effect
a.value.append(10)
# 整个替换则会触发
a.value = [1,2,10]
```
参数 `is_deep` 设置为 `True` 时,能得到深度响应能力
```python
a = to_ref([1,2],is_deep=True)
@effect
def _():
print('len:',len(a.value))
# print 3
a.value.append(10)
```
> `deep_ref` 等价于 `is_deep` 设置为 `True` 时的 `to_ref`
---
#### `deep_ref`
等价于 `is_deep` 设置为 `True` 时的 `to_ref`。
当数据源为列表、字典或自定义类时,特别有用。通过 `.value` 获取的对象为代理对象
```python
data = [1,2,3]
data_ref = deep_ref(data)
assert data_ref.value is not data
```
通过 `to_raw` 可以获取原始对象
```python
from ex4nicegui import to_raw, deep_ref
data = [1, 2, 3]
data_ref = deep_ref(data)
assert data_ref.value is not data
assert to_raw(data_ref.value) is data
```
---
#### `effect`
接受一个函数,自动监控函数中使用到的响应式对象变化,从而自动执行函数
```python
a = to_ref(1)
b = to_ref("text")
@effect
def auto_run_when_ref_value():
print(f"a:{a.value}")
def change_value():
a.value = 2
b.value = "new text"
ui.button("change", on_click=change_value)
```
首次执行 effect ,函数`auto_run_when_ref_value`将被执行一次.之后点击按钮,改变 `a` 的值(通过 `a.value`),函数`auto_run_when_ref_value`再次执行
> 切忌把大量数据处理逻辑分散在多个 `on` 或 `effect` 中,`on` 或 `effect` 中应该大部分为界面操作逻辑,而非响应式数据处理逻辑
---
#### `ref_computed`
与 `effect` 具备一样的功能,`ref_computed` 还能从函数中返回结果。一般用于从 `to_ref` 中进行二次计算
```python
a = to_ref(1)
a_square = ref_computed(lambda: a.value * 2)
@effect
def effect1():
print(f"a_square:{a_square.value}")
def change_value():
a.value = 2
ui.button("change", on_click=change_value)
```
点击按钮后,`a.value` 值被修改,从而触发 `a_square` 重新计算.由于 `effect1` 中读取了 `a_square` 的值,从而触发 `effect1` 执行
> `ref_computed` 是只读的 `to_ref`
从 `v0.7.0` 版本开始,不建议使用 `ref_computed` 应用实例方法。你可以使用 `rxui.ViewModel`,并使用 `rxui.cached_var` 装饰器
```python
class MyState(rxui.ViewModel):
def __init__(self) -> None:
self.r_text = to_ref("")
@rxui.cached_var
def post_text(self):
return self.r_text.value + "post"
state = MyState()
rxui.input(value=state.r_text)
rxui.label(state.post_text)
```
---
#### `async_computed`
二次计算中需要使用异步函数时,使用 `async_computed`
```python
# 模拟长时间执行的异步函数
async def long_time_query(input: str):
await asyncio.sleep(2)
num = random.randint(20, 100)
return f"query result[{input=}]:{num=}"
search = to_ref("")
evaluating = to_ref(False)
@async_computed(search, evaluating=evaluating, init="")
async def search_result():
return await long_time_query(search.value)
rxui.lazy_input(value=search)
rxui.label(
lambda: "查询中" if evaluating.value else "上方输入框输入内容并回车搜索"
)
rxui.label(search_result)
```
- `async_computed` 第一个参数必须明确指定需要监控的响应式数据. 使用列表可以同时指定多个响应式数据
- 参数 `evaluating` 为 bool 类型的响应式数据,当异步函数执行中,此变量值为 `True`,计算结束后为 `False`
- 参数 `init` 指定初始结果
---
#### `on`
类似 `effect` 的功能,但是 `on` 需要明确指定监控的响应式对象
```python
a1 = to_ref(1)
a2 = to_ref(10)
b = to_ref("text")
@on(a1)
def watch_a1_only():
print(f"watch_a1_only ... a1:{a1.value},a2:{a2.value}")
@on([a1, b], onchanges=True)
def watch_a1_and_b():
print(f"watch_a1_and_b ... a1:{a1.value},a2:{a2.value},b:{b.value}")
def change_a1():
a1.value += 1
ui.notify("change_a1")
ui.button("change a1", on_click=change_a1)
def change_a2():
a2.value += 1
ui.notify("change_a2")
ui.button("change a2", on_click=change_a2)
def change_b():
b.value += "x"
ui.notify("change_b")
ui.button("change b", on_click=change_b)
```
- 参数 `onchanges` 为 True 时(默认值为 False),指定的函数不会在绑定时执行
> 切忌把大量数据处理逻辑分散在多个 `on` 或 `effect` 中,`on` 或 `effect` 中应该大部分为界面操作逻辑,而非响应式数据处理逻辑
---
#### `new_scope`
默认情况下,所有检测函数在客户端连接断开时自动销毁。如果需要更细粒度的控制,可以使用 `new_scope`
```python
from nicegui import ui
from ex4nicegui import rxui, to_ref, effect, new_scope
a = to_ref(0.0)
scope1 = new_scope()
@scope1.run
def _():
@effect
def _():
print(f"scope 1:{a.value}")
rxui.number(value=a)
rxui.button("dispose scope 1", on_click=scope1.dispose)
```
---
### 组件功能
#### vmodel
在表单输入元素或组件上创建双向绑定。
简单值类型的 `ref` 默认支持双向绑定
```python
from ex4nicegui import rxui, to_ref, deep_ref
data = to_ref("init")
rxui.label(lambda: f"{data.value=}")
# 默认就是双向绑定
rxui.input(value=data)
```
- 简单值类型一般是 `str`,`int` 等不可变值类型
当使用复杂数据结构时,会使用 `deep_ref` 保持嵌套值的响应性
```python
data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
rxui.label(lambda: f"{data.value=!s}")
# 当前版本没有任何绑定效果.或许未来的版本可以解决
rxui.input(value=data.value["a"])
# 只读绑定.其他途径修改了 `data.value["a"]` ,此输入框会同步,但反过来不行
rxui.input(value=lambda: data.value["a"])
# 要使用 vmodel 才能双向绑定
rxui.input(value=rxui.vmodel(data, "a"))
# 也可以直接使用,但不推荐
rxui.input(value=rxui.vmodel(data.value['a']))
```
- 第一个输入框将完全失去响应性,因为代码等价于 `rxui.input(value=1)`
- 第二个输入框由于使用函数,将得到读取响应性(第三个输入框输入值,将得到同步)
- 第三个输入框,使用 `rxui.vmodel` 包裹,即可实现双向绑定
> 如果使用 `rxui.ViewModel` ,你可能不需要使用 `vmodel`
可参考 [todo list 案例](./examples/todomvc/)
---
#### vfor
基于列表响应式数据,渲染列表组件。每项组件按需更新。数据项支持字典或任意类型对象。
从 `v0.7.0` 版本开始,建议配合 `rxui.ViewModel` 使用。与使用 `effect_refreshable` 装饰器不同,`vfor` 不会重新创建所有的元素,而是更新已存在的元素。
下面是卡片排序例子,卡片总是按年龄排序。当你修改某个卡片中的年龄数据时,卡片会实时调整顺序。但是,光标焦点不会离开输入框。
```python
from typing import List
from nicegui import ui
from ex4nicegui import rxui, deep_ref as ref, Ref
class Person(rxui.ViewModel):
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = ref(age)
class MyApp(rxui.ViewModel):
persons: Ref[List[Person]] = rxui.var(lambda: [])
order = rxui.var("asc")
def sort_by_age(self):
return sorted(
self.persons.value,
key=lambda p: p.age.value,
reverse=self.order.value == "desc",
)
@staticmethod
def create():
persons = [
Person(name="Alice", age=25),
Person(name="Bob", age=30),
Person(name="Charlie", age=20),
Person(name="Dave", age=35),
Person(name="Eve", age=28),
]
app = MyApp()
app.persons.value = persons
return app
# ui
app = MyApp.create()
with rxui.tabs(app.order):
rxui.tab("asc", "Ascending")
rxui.tab("desc", "Descending")
@rxui.vfor(app.sort_by_age, key="name")
def each_person(s: rxui.VforStore[Person]):
person = s.get_item()
with ui.card(), ui.row(align_items="center"):
rxui.label(person.name)
rxui.number(value=person.age, step=1, min=0, max=100)
```
- `rxui.vfor` 装饰器到自定义函数
- 第一个参数传入响应式列表。注意,无须调用 `app.sort_by_age`
- 第二个参数 `key`: 为了可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你可以为每个元素对应的块提供一个唯一的 key 。默认情况使用列表元素索引。例子中假定每个人的名字唯一。
- 自定义函数带有一个参数。通过 `store.get_item` 可以获取当前行的对象。由于 Person 本身继承自 `rxui.ViewModel`,所以它的各项属性可以直接绑定到组件。
---
#### bind_classes
所有的组件类提供 `bind_classes` 用于绑定 `class`,支持三种不同的数据结构。
绑定字典
```python
bg_color = to_ref(False)
has_error = to_ref(False)
rxui.label("test").bind_classes({"bg-blue": bg_color, "text-red": has_error})
rxui.switch("bg_color", value=bg_color)
rxui.switch("has_error", value=has_error)
```
字典键值为类名,对应值为 bool 的响应式变量。当响应式值为 `True`,类名应用到组件 class
---
绑定返回值为字典的响应式变量
```python
bg_color = to_ref(False)
has_error = to_ref(False)
class_obj = ref_computed(
lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
)
rxui.switch("bg_color", value=bg_color)
rxui.switch("has_error", value=has_error)
rxui.label("bind to ref_computed").bind_classes(class_obj)
# or direct function passing
rxui.label("bind to ref_computed").bind_classes(
lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
)
```
---
绑定为列表或单个字符串的响应式变量
```python
bg_color = to_ref("red")
bg_color_class = ref_computed(lambda: f"bg-{bg_color.value}")
text_color = to_ref("green")
text_color_class = ref_computed(lambda: f"text-{text_color.value}")
rxui.select(["red", "green", "yellow"], label="bg color", value=bg_color)
rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
rxui.label("binding to arrays").bind_classes([bg_color_class, text_color_class])
rxui.label("binding to single string").bind_classes(bg_color_class)
```
- 列表中每个元素为返回类名的响应式变量
---
#### bind_style
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
from ex4nicegui.utils.signals import to_ref
bg_color = to_ref("blue")
text_color = to_ref("red")
rxui.label("test").bind_style(
{
"background-color": bg_color,
"color": text_color,
}
)
rxui.select(["blue", "green", "yellow"], label="bg color", value=bg_color)
rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
```
`bind_style` 传入字典,`key` 为样式名字,`value` 为样式值,响应式字符串
---
#### bind_prop
绑定单个属性
```python
label = to_ref("hello")
rxui.button("").bind_prop("label", label)
# 允许使用函数
rxui.button("").bind_prop(
"label", lambda: f"{label.value} world"
)
rxui.input(value=label)
```
---
#### rxui.echarts
使用 echarts 制作图表
```python
from nicegui import ui
from ex4nicegui import ref_computed, effect, to_ref
from ex4nicegui.reactive import rxui
r_input = to_ref("")
# ref_computed 创建只读响应式变量
# 函数中使用任意其他响应式变量,会自动关联
@ref_computed
def cp_echarts_opts():
return {
"title": {"text": r_input.value}, #字典中使用任意响应式变量,通过 .value 获取值
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
"yAxis": {"type": "value"},
"series": [
{
"data": [120, 200, 150, 80, 70, 110, 130],
"type": "bar",
"showBackground": True,
"backgroundStyle": {"color": "rgba(180, 180, 180, 0.2)"},
}
],
}
input = rxui.input("输入内容,图表标题会同步", value=r_input)
# 通过响应式组件对象的 element 属性,获取原生 nicegui 组件对象
input.element.classes("w-full")
rxui.echarts(cp_echarts_opts)
ui.run()
```
![](./asset/asyc_echarts_title.gif)
##### echarts 图表鼠标事件
`on` 函数参数 `event_name` 以及 `query` 使用,查看[echarts 事件中文文档](https://echarts.apache.org/handbook/zh/concepts/event/)
以下例子绑定鼠标单击事件
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
opts = {
"xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
"yAxis": {
"type": "category",
"data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
},
"series": [
{
"name": "first",
"type": "bar",
"data": [18203, 23489, 29034, 104970, 131744, 630230],
},
{
"name": "second",
"type": "bar",
"data": [19325, 23438, 31000, 121594, 134141, 681807],
},
],
}
bar = rxui.echarts(opts)
def on_click(e: rxui.echarts.EChartsMouseEventArguments):
ui.notify(f"on_click:{e.seriesName}:{e.name}:{e.value}")
bar.on("click", on_click)
```
以下例子只针对指定系列触发鼠标划过事件
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
opts = {
"xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
"yAxis": {
"type": "category",
"data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
},
"series": [
{
"name": "first",
"type": "bar",
"data": [18203, 23489, 29034, 104970, 131744, 630230],
},
{
"name": "second",
"type": "bar",
"data": [19325, 23438, 31000, 121594, 134141, 681807],
},
],
}
bar = rxui.echarts(opts)
def on_first_series_mouseover(e: rxui.echarts.EChartsMouseEventArguments):
ui.notify(f"on_first_series_mouseover:{e.seriesName}:{e.name}:{e.value}")
bar.on("mouseover", on_first_series_mouseover, query={"seriesName": "first"})
ui.run()
```
---
---
##### rxui.echarts.from_javascript
从 javascript 代码创建 echart
```python
from pathlib import Path
rxui.echarts.from_javascript(Path("code.js"))
# or
rxui.echarts.from_javascript(
"""
(myChart) => {
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}
]
};
myChart.setOption(option);
}
"""
)
```
- 函数第一个参数为 echart 实例对象.你需要在函数中通过 `setOption` 完成图表配置
函数也有第二个参数,为 `echarts` 全局对象,你可以通过 `echarts.registerMap` 注册地图。
```python
rxui.echarts.from_javascript(
"""
(chart,echarts) =>{
fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
.then(response => response.json())
.then(data => {
echarts.registerMap('test_map', data);
chart.setOption({
geo: {
map: 'test_map',
roam: true,
},
tooltip: {},
legend: {},
series: [],
});
});
}
"""
)
```
---
##### rxui.echarts.register_map
注册地图.
```python
rxui.echarts.register_map(
"china", "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
)
rxui.echarts(
{
"geo": {
"map": "china",
"roam": True,
},
"tooltip": {},
"legend": {},
"series": [],
}
)
```
- 参数 `map_name` 为自定义的地图名字。注意在图表配置中 `map` 必需对应注册的名字
- 参数 `src` 为有效的地图数据网络链接。
如果是 svg 数据,需要设置参数 `type="svg"`
```python
rxui.echarts.register_map("svg-rect", "/test/svg", type="svg")
```
你也可以直接提供本地地图数据的json文件路径对象(Path)
```python
from pathlib import Path
rxui.echarts.register_map(
"china", Path("map-data.json")
)
```
---
#### tab_panels
相比较于 `nicegui.ui.tab_panels` , `rxui.tab_panels` 没有参数 `tabs`。在数据响应式机制下,`tabs` 与 `tab_panels` 联动只需要通过参数 `value` 即可。
```python
from nicegui import ui
from ex4nicegui import rxui, to_ref
names = ["Tab 1", "Tab 2", "Tab 3"]
current_tab = to_ref(names[0])
with rxui.tabs(current_tab):
for name in names:
rxui.tab(name)
with rxui.tab_panels(current_tab):
for name in names:
with rxui.tab_panel(name):
ui.label(f"Content of {name}")
```
这是因为,数据响应机制下,组件联动是通过中间数据层(`to_ref`)实现的。因此,`tab_panels` 可以与其他组件联动(只需要保证使用同样的 `ref` 对象即可)
```python
names = ["Tab 1", "Tab 2", "Tab 3"]
current_tab = to_ref(names[0])
with rxui.tab_panels(current_tab):
for name in names:
with rxui.tab_panel(name):
ui.label(f"Content of {name}")
# tabs 不必在 panels 前面
with rxui.tabs(current_tab):
for name in names:
rxui.tab(name)
rxui.select(names, value=current_tab)
rxui.radio(names, value=current_tab).props("inline")
rxui.label(lambda: f"当前 tab 为:{current_tab.value}")
```
---
#### lazy_tab_panels
懒加载模式下,只有当前激活的 tab 才会渲染。
```python
from ex4nicegui import to_ref, rxui, on, deep_ref
current_tab = to_ref("t1")
with rxui.tabs(current_tab):
ui.tab("t1")
ui.tab("t2")
with rxui.lazy_tab_panels(current_tab) as panels:
@panels.add_tab_panel("t1")
def _():
# 通过 `panels.get_panel` 获取当前激活的 panel 组件
panels.get_panel("t1").classes("bg-green")
ui.notify("Hello from t1")
ui.label("This is t1")
@panels.add_tab_panel("t2")
def _():
panels.get_panel("t2").style("background-color : red")
ui.notify("Hello from t2")
ui.label("This is t2")
```
页面加载后,立刻显示 "Hello from t1"。当切换到 "t2" 页签,才会显示 "Hello from t2"。
---
#### scoped_style
`scoped_style` 方法允许你创建限定在组件内部的样式。
```python
# 所有子元素都会有红色轮廓,但排除自身
with rxui.row().scoped_style("*", "outline: 1px solid red;") as row:
ui.label("Hello")
ui.label("World")
# 所有子元素都会有红色轮廓,包括自身
with rxui.row().scoped_style(":self *", "outline: 1px solid red;") as row:
ui.label("Hello")
ui.label("World")
# 当鼠标悬停在 row 组件时,所有子元素都会有红色轮廓,但排除自身
with rxui.row().scoped_style(":hover *", "outline: 1px solid red;") as row:
ui.label("Hello")
ui.label("World")
# 当鼠标悬停在 row 组件时,所有子元素都会有红色轮廓,包括自身
with rxui.row().scoped_style(":self:hover *", "outline: 1px solid red;") as row:
ui.label("Hello")
ui.label("World")
```
---
### BI 模块
以最精简的 apis 创建可交互的数据可视化报表
![](./asset/bi_examples1.gif)
```python
from nicegui import ui
import pandas as pd
import numpy as np
from ex4nicegui import bi
from ex4nicegui.reactive import rxui
from ex4nicegui import effect, effect_refreshable
from pyecharts.charts import Bar
# data ready
def gen_data():
np.random.seed(265)
field1 = ["a1", "a2", "a3", "a4"]
field2 = [f"name{i}" for i in range(1, 11)]
df = (
pd.MultiIndex.from_product([field1, field2], names=["cat", "name"])
.to_frame()
.reset_index(drop=True)
)
df[["idc1", "idc2"]] = np.random.randint(50, 1000, size=(len(df), 2))
return df
df = gen_data()
# 创建数据源
ds = bi.data_source(df)
# ui
ui.query(".nicegui-content").classes("items-stretch no-wrap")
with ui.row().classes("justify-evenly"):
# 基于数据源 `ds` 创建界面组件
ds.ui_select("cat").classes("min-w-[10rem]")
ds.ui_select("name").classes("min-w-[10rem]")
with ui.grid(columns=2):
# 使用字典配置图表
@ds.ui_echarts
def bar1(data: pd.DataFrame):
data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
return {
"xAxis": {"type": "value"},
"yAxis": {
"type": "category",
"data": data["name"].tolist(),
"inverse": True,
},
"legend": {"textStyle": {"color": "gray"}},
"series": [
{"type": "bar", "name": "idc1", "data": data["idc1"].tolist()},
{"type": "bar", "name": "idc2", "data": data["idc2"].tolist()},
],
}
bar1.classes("h-[20rem]")
# 使用pyecharts配置图表
@ds.ui_echarts
def bar2(data: pd.DataFrame):
data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
return (
Bar()
.add_xaxis(data["name"].tolist())
.add_yaxis("idc1", data["idc1"].tolist())
.add_yaxis("idc2", data["idc2"].tolist())
)
bar2.classes("h-[20rem]")
# 绑定点击事件,即可实现跳转
@bar2.on_chart_click
def _(e: rxui.echarts.EChartsMouseEventArguments):
ui.open(f"/details/{e.name}", new_tab=True)
# 利用响应式机制,你可以随意组合原生 nicegui 组件
label_a1_total = ui.label("")
# 当 ds 有变化,都会触发此函数
@effect
def _():
# filtered_data 为过滤后的 DataFrame
df = ds.filtered_data
total = df[df["cat"] == "a1"]["idc1"].sum()
label_a1_total.text = f"idc1 total(cat==a1):{total}"
# 你也可以使用 `effect_refreshable`,但需要注意函数中的组件每次都被重建
@effect_refreshable
def _():
df = ds.filtered_data
total = df[df["cat"] == "a2"]["idc1"].sum()
ui.label(f"idc1 total(cat==a2):{total}")
# 当点击图表系列时,跳转的页面
@ui.page("/details/{name}")
def details_page(name: str):
ui.label("This table data will not change")
ui.aggrid.from_pandas(ds.data.query(f'name=="{name}"'))
ui.label("This table will change when the homepage data changes. ")
@bi.data_source
def new_ds():
return ds.filtered_data[["name", "idc1", "idc2"]]
new_ds.ui_aggrid()
ui.run()
```
---
#### `bi.data_source`
数据源是 BI 模块的核心概念,所有数据的联动基于此展开。当前版本(0.4.3)中,有两种创建数据源的方式
接收 `pandas` 的 `DataFrame`:
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
ds = bi.data_source(df)
```
---
有时候,我们希望基于另一个数据源创建新的数据源,此时可以使用装饰器创建联动数据源:
```python
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
ds = bi.data_source(df)
@bi.data_source
def new_ds():
# df is pd.DataFrame
df = ds.filtered_data
df=df.copy()
df['value'] = df['value'] * 100
return df
ds.ui_select('name')
new_ds.ui_aggrid()
```
注意,由于 `new_ds` 中使用了 `ds.filtered_data` ,因此 `ds` 的变动会触发 `new_ds` 的联动变化,从而导致 `new_ds` 创建的表格组件产生变化
---
通过 `ds.remove_filters` 方法,移除所有筛选状态:
```python
ds = bi.data_source(df)
def on_remove_filters():
ds.remove_filters()
ui.button("remove all filters", on_click=on_remove_filters)
ds.ui_select("name")
ds.ui_aggrid()
```
---
通过 `ds.reload` 方法,重设数据源:
```python
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
new_df = pd.DataFrame(
{
"name": list("xxyyds"),
"cls": ["cla1", "cla2", "cla3", "cla3", "cla3", None],
"value": range(100, 106),
}
)
ds = bi.data_source(df)
def on_remove_filters():
ds.reload(new_df)
ui.button("reload data", on_click=on_remove_filters)
ds.ui_select("name")
ds.ui_aggrid()
```
---
#### ui_select
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
ds = bi.data_source(df)
ds.ui_select("name")
```
第一个参数 column 指定数据源的列名
---
通过参数 `sort_options` 设置选项顺序:
```python
ds.ui_select("name", sort_options={"value": "desc", "name": "asc"})
```
---
参数 `exclude_null_value` 设置是否排除空值:
```python
df = pd.DataFrame(
{
"cls": ["c1", "c2", "c1", "c1", "c3", None],
}
)
ds = bi.data_source(df)
ds.ui_select("cls", exclude_null_value=True)
```
---
你可以通过关键字参数,设置原生 nicegui select 组件的参数.
通过 value 属性,设置默认值:
```python
ds.ui_select("cls",value=['c1','c2'])
ds.ui_select("cls",multiple=False,value='c1')
```
多选时(参数 `multiple` 默认为 True),`value` 需要指定为 list
单选时,`value` 设置为非 list
---
#### ui_table
表格
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
data = pd.DataFrame({"name": ["f", "a", "c", "b"], "age": [1, 2, 3, 1]})
ds = bi.data_source(data)
ds.ui_table(
columns=[
{"label": "new colA", "field": "colA", "sortable": True},
]
)
```
- columns 与 nicegui `ui.table` 一致。其中 键值 `field` 对应数据源的列名,如果不存在,则该配置不会生效
- rows 参数不会生效。因为表格的数据源始终由 data source 控制
---
#### ui_aggrid
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
data = pd.DataFrame(
{
"colA": list("abcde"),
"colB": [f"n{idx}" for idx in range(5)],
"colC": list(range(5)),
}
)
df = pd.DataFrame(data)
source = bi.data_source(df)
source.ui_aggrid(
options={
"columnDefs": [
{"headerName": "xx", "field": "no exists"},
{"headerName": "new colA", "field": "colA"},
{
"field": "colC",
"cellClassRules": {
"bg-red-300": "x < 3",
"bg-green-300": "x >= 3",
},
},
],
"rowData": [{"colX": [1, 2, 3, 4, 5]}],
}
)
```
- 参数 options 与 nicegui `ui.aggrid` 一致。其中 `columnDefs` 中的键值 `field` 对应数据源的列名,如果不存在,则该配置不会生效
- `rowData` 键值不会生效。因为表格的数据源始终由 data source 控制
Raw data
{
"_id": null,
"home_page": "https://github.com/CrystalWindSnake/ex4nicegui",
"name": "ex4nicegui",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": "nicegui, ex4nicegui, webui",
"author": "CrystalWindSnake",
"author_email": "568166495@qq.com",
"download_url": "https://files.pythonhosted.org/packages/36/1b/bfe44fa85eadaf632248c42ba2e3b2980c478cba351913b4919ec58a36e2/ex4nicegui-0.8.4.tar.gz",
"platform": null,
"description": "# ex4nicegui\n\n<div align=\"center\">\n\n\u7b80\u4f53\u4e2d\u6587| [English](./README.en.md)\n\n</div>\n\n\u5bf9 [nicegui](https://github.com/zauberzeug/nicegui) \u505a\u7684\u6269\u5c55\u5e93\u3002\u5185\u7f6e\u54cd\u5e94\u5f0f\u7ec4\u4ef6\uff0c\u5b8c\u5168\u5b9e\u73b0\u6570\u636e\u54cd\u5e94\u5f0f\u754c\u9762\u7f16\u7a0b\u3002\n\n\n![todo-app](https://gitee.com/carson_add/ex4nicegui-examples/raw/main/asset/todo-app.01.gif)\n\n![todo-app](https://gitee.com/carson_add/ex4nicegui-examples/raw/main/asset/todo-app.02.gif)\n\n[\u67e5\u770b\u66f4\u591a\u793a\u4f8b](https://gitee.com/carson_add/ex4nicegui-examples)\n\n---\n\n\n## \u6559\u7a0b\n[\u5934\u6761\u6587\u7ae0-\u79d2\u6740\u5b98\u65b9\u5b9e\u73b0\uff0cpython\u754c\u9762\u5e93\uff0c\u53bb\u638990%\u4e8b\u4ef6\u4ee3\u7801\u7684nicegui](https://www.toutiao.com/item/7253786340574265860/)\n\n[\u5fae\u4fe1\u516c\u4f17\u53f7-\u79d2\u6740\u5b98\u65b9\u5b9e\u73b0\uff0cpython\u754c\u9762\u5e93\uff0c\u53bb\u638990%\u4e8b\u4ef6\u4ee3\u7801\u7684nicegui](https://mp.weixin.qq.com/s?__biz=MzUzNDk1MTc5Mw==&mid=2247486796&idx=1&sn=457ed6fb9d6a25145f7704d5197d670d&chksm=fa8daf52cdfa2644bede50ae7f2551162ecaedecafec231ee4ce8f28775a599f8669ecf06af1#rd)\n\n\n## \ud83d\udce6 \u5b89\u88c5\n\n```\npip install ex4nicegui -U\n```\n\n---\n\n## \u5165\u95e8\n\n\u6211\u4eec\u4ece\u4e00\u4e2a\u7b80\u5355\u7684\u8ba1\u6570\u5668\u5e94\u7528\u5f00\u59cb\uff0c\u7528\u6237\u53ef\u4ee5\u901a\u8fc7\u70b9\u51fb\u6309\u94ae\u8ba9\u8ba1\u6570\u589e\u52a0\u6216\u51cf\u5c11\u3002\n\n![counter](https://gitee.com/carson_add/ex4nicegui-examples/raw/main/asset/counter.gif)\n\n\u4e0b\u9762\u662f\u5b8c\u6574\u4ee3\u7801\uff1a\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import rxui\n\n# \u6570\u636e\u72b6\u6001\u4ee3\u7801\nclass Counter(rxui.ViewModel):\n count: int = 0\n\n def increment(self):\n self.count += 1\n\n def decrement(self):\n self.count -= 1\n\n# \u754c\u9762\u4ee3\u7801\ncounter = Counter()\n\nwith ui.row(align_items=\"center\"):\n ui.button(icon=\"remove\", on_click=counter.decrement)\n rxui.label(counter.count)\n ui.button(icon=\"add\", on_click=counter.increment)\n\n\nui.run()\n```\n\n---\n\u73b0\u5728\u770b\u66f4\u591a\u7ec6\u8282\u3002`ex4nicegui` \u9075\u4ece\u6570\u636e\u9a71\u52a8\u65b9\u5f0f\u5b9a\u4e49\u754c\u9762\u3002\u72b6\u6001\u6570\u636e\u5b9a\u4e49\u5e94\u7528\u7a0b\u5e8f\u4e2d\u6240\u6709\u53ef\u4ee5\u53d8\u5316\u7684\u6570\u636e\u3002\n\n\u4e0b\u9762\u662f `Counter` \u72b6\u6001\u6570\u636e\u5b9a\u4e49\uff1a\n\n```python\nclass Counter(rxui.ViewModel):\n count: int = 0\n```\n\n- \u81ea\u5b9a\u4e49\u7c7b\u9700\u8981\u7ee7\u627f `rxui.ViewModel`\n- \u8fd9\u91cc\u5b9a\u4e49\u4e86\u4e00\u4e2a\u53d8\u91cf `count`\uff0c\u8868\u793a\u8ba1\u6570\u5668\u7684\u5f53\u524d\u503c\uff0c\u521d\u59cb\u503c\u4e3a 0\n\n\u63a5\u7740\uff0c\u5728\u7c7b\u4e2d\u5b9a\u4e49\u4e00\u7cfb\u5217\u64cd\u4f5c\u6570\u636e\u7684\u65b9\u6cd5\uff1a\n```python\ndef increment(self):\n self.count += 1\n\ndef decrement(self):\n self.count -= 1\n```\n\n- \u8fd9\u4e9b\u90fd\u662f\u5b9e\u4f8b\u65b9\u6cd5\uff0c\u53ef\u4ee5\u4fee\u6539 `count` \u53d8\u91cf\u7684\u503c\n\n\n\u7136\u540e\uff0c\u5728\u754c\u9762\u4ee3\u7801\u4e2d\uff0c\u5b9e\u4f8b\u5316 `Counter` \u7684\u5bf9\u8c61\u3002\n```python\ncounter = Counter()\n```\n\n\n\u6211\u4eec\u901a\u8fc7 `rxui.label` \u7ec4\u4ef6\u7ed1\u5b9a `count` \u53d8\u91cf\u3002\u628a\u64cd\u4f5c\u6570\u636e\u7684\u65b9\u6cd5\u7ed1\u5b9a\u5230\u6309\u94ae\u70b9\u51fb\u4e8b\u4ef6\u4e0a\u3002\n```python\nui.button(icon=\"remove\", on_click=counter.decrement)\nrxui.label(counter.count)\nui.button(icon=\"add\", on_click=counter.increment)\n```\n\n- \u6211\u4eec\u9700\u8981\u4f7f\u7528 `rxui` \u547d\u540d\u7a7a\u95f4\u4e0b\u7684 `label` \u7ec4\u4ef6\uff0c\u800c\u4e0d\u662f `nicegui` \u547d\u540d\u7a7a\u95f4\u4e0b\u7684 `label` \u7ec4\u4ef6\u3002\n- `rxui.label` \u7ec4\u4ef6\u7ed1\u5b9a `counter.count` \u53d8\u91cf\uff0c\u5f53 `counter.count` \u53d8\u5316\u65f6\uff0c`rxui.label` \u7ec4\u4ef6\u81ea\u52a8\u66f4\u65b0\u3002\n- `ui.button` \u7ec4\u4ef6\u7ed1\u5b9a `counter.decrement` \u548c `counter.increment` \u65b9\u6cd5\uff0c\u70b9\u51fb\u6309\u94ae\u65f6\u8c03\u7528\u76f8\u5e94\u65b9\u6cd5\u3002\n\n\n> \u5728\u590d\u6742\u9879\u76ee\u4e2d\uff0c`Counter` \u5b9a\u4e49\u7684\u4ee3\u7801\u53ef\u4ee5\u653e\u5230\u5355\u72ec\u7684\u6a21\u5757\u4e2d\uff0c\u7136\u540e\u5728\u754c\u9762\u4ee3\u7801\u4e2d\u5bfc\u5165\u3002\n\n\u6ce8\u610f\uff0c\u5f53\u7c7b\u53d8\u91cf\u540d\u524d\u9762\u5e26\u6709\u4e0b\u5212\u7ebf\u65f6\uff0c\u6570\u636e\u72b6\u6001\u4e0d\u4f1a\u81ea\u52a8\u66f4\u65b0\u3002\n\n```python\nclass Counter(rxui.ViewModel):\n count: int = 0 # \u54cd\u5e94\u5f0f\u6570\u636e\uff0c\u80fd\u81ea\u52a8\u540c\u6b65\u754c\u9762\n _count: int = 0 # \u8fd9\u91cc\u7684\u4e0b\u5212\u7ebf\u8868\u793a\u79c1\u6709\u53d8\u91cf\uff0c\u4e0d\u4f1a\u81ea\u52a8\u540c\u6b65\u754c\u9762\n\n```\n\n---\n\n### \u4e8c\u6b21\u8ba1\u7b97\n\n\u63a5\u7740\u524d\u9762\u7684\u4f8b\u5b50\uff0c\u6211\u4eec\u518d\u6dfb\u52a0\u4e00\u4e2a\u529f\u80fd\u3002\u5f53\u8ba1\u6570\u5668\u7684\u503c\u5c0f\u4e8e 0 \u65f6\uff0c\u5b57\u4f53\u663e\u793a\u4e3a\u7ea2\u8272\uff0c\u5927\u4e8e 0 \u65f6\u663e\u793a\u4e3a\u7eff\u8272\uff0c\u5426\u5219\u663e\u793a\u4e3a\u9ed1\u8272\u3002\n\n```python\n# \u6570\u636e\u72b6\u6001\u4ee3\u7801\nclass Counter(rxui.ViewModel):\n count: int = 0\n\n def text_color(self):\n if self.count > 0:\n return \"green\"\n elif self.count < 0:\n return \"red\"\n else:\n return \"black\"\n\n def increment(self):\n self.count += 1\n\n def decrement(self):\n self.count -= 1\n\n# \u754c\u9762\u4ee3\u7801\ncounter = Counter()\n\nwith ui.row(align_items=\"center\"):\n ui.button(icon=\"remove\", on_click=counter.decrement)\n rxui.label(counter.count).bind_color(counter.text_color)\n ui.button(icon=\"add\", on_click=counter.increment)\n```\n\n\u989c\u8272\u503c\u662f\u4f9d\u636e\u8ba1\u6570\u5668\u5f53\u524d\u503c\u8ba1\u7b97\u5f97\u5230\u7684\u3002\u5c5e\u4e8e\u4e8c\u6b21\u8ba1\u7b97\u3002\u901a\u8fc7\u5b9a\u4e49\u666e\u901a\u7684\u5b9e\u4f8b\u51fd\u6570\u5373\u53ef\u3002\n\n```python\ndef text_color(self):\n if self.count > 0:\n return \"green\"\n elif self.count < 0:\n return \"red\"\n else:\n return \"black\"\n```\n\n\n\u7136\u540e\uff0c\u901a\u8fc7 `rxui.label` \u7ec4\u4ef6\u7684 `bind_color` \u65b9\u6cd5\u7ed1\u5b9a `text_color` \u65b9\u6cd5\uff0c\u4f7f\u5f97\u989c\u8272\u503c\u81ea\u52a8\u66f4\u65b0\u3002\n```python\nrxui.label(counter.count).bind_color(counter.text_color)\n```\n\n### \u4e8c\u6b21\u8ba1\u7b97\u7f13\u5b58\n\u73b0\u5728\uff0c\u6211\u4eec\u5728\u8ba1\u6570\u5668\u4e0b\u65b9\u4f7f\u7528\u6587\u5b57\uff0c\u663e\u793a\u5f53\u524d\u8ba1\u6570\u5668\u7684\u989c\u8272\u6587\u672c\u503c\u3002\n\n```python\n...\n# \u6570\u636e\u72b6\u6001\u4ee3\u7801\nclass Counter(rxui.ViewModel):\n ...\n\n# \u754c\u9762\u4ee3\u7801\ncounter = Counter()\n\nwith ui.row(align_items=\"center\"):\n ui.button(icon=\"remove\", on_click=counter.decrement)\n rxui.label(counter.count).bind_color(counter.text_color)\n ui.button(icon=\"add\", on_click=counter.increment)\n\nrxui.label(lambda: f\"\u5f53\u524d\u8ba1\u6570\u5668\u503c\u4e3a {counter.count}, \u989c\u8272\u503c\u4e3a {counter.text_color()}\")\n```\n\n- \u5f53\u4e8c\u6b21\u8ba1\u7b97\u975e\u5e38\u7b80\u5355\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 lambda \u8868\u8fbe\u5f0f\n\n\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6709\u4e24\u4e2a\u5730\u65b9\u4f7f\u7528\u4e86 `counter.text_color` \u65b9\u6cd5\u3002\u5f53 `counter.count` \u53d8\u5316\u65f6\uff0c`counter.text_color` \u4f1a\u6267\u884c\u4e24\u6b21\u8ba1\u7b97\u3002\u7b2c\u4e8c\u6b21\u8ba1\u7b97\u662f\u591a\u4f59\u7684\u3002\n\n\u4e3a\u4e86\u907f\u514d\u591a\u4f59\u7684\u8ba1\u7b97\uff0c\u6211\u4eec\u53ef\u4ee5\u628a `counter.text_color` \u7f13\u5b58\u8d77\u6765\u3002\n\n```python\n# \u6570\u636e\u72b6\u6001\u4ee3\u7801\nclass Counter(rxui.ViewModel):\n count: int = 0\n\n @rxui.cached_var\n def text_color(self):\n if self.count > 0:\n return \"green\"\n elif self.count < 0:\n return \"red\"\n else:\n return \"black\"\n\n```\n\n- `rxui.cached_var` \u88c5\u9970\u5668\u53ef\u4ee5\u628a\u51fd\u6570\u7ed3\u679c\u7f13\u5b58\u8d77\u6765\uff0c\u907f\u514d\u591a\u4f59\u7684\u8ba1\u7b97\u3002\n\n### \u5217\u8868\n\n\u4e0b\u9762\u7684\u793a\u4f8b\uff0c\u5c55\u793a\u4e86\u5982\u4f55\u4f7f\u7528\u5217\u8868\u3002\n\n```python\n\nclass AppState(rxui.ViewModel):\n nums = []\n # nums = [1,2,3] \u274c \u5982\u679c\u9700\u8981\u521d\u59cb\u5316\uff0c\u5fc5\u987b\u5728 __init__ \u4e2d\u8bbe\u7f6e\n\n def __init__(self):\n super().__init__()\n self.nums = [1, 2, 3]\n\n def append(self):\n new_num = max(self.nums) + 1\n self.nums.append(new_num)\n\n def pop(self):\n self.nums.pop()\n\n def reverse(self):\n self.nums.reverse()\n\n def display_nums(self):\n return \", \".join(map(str, self.nums))\n\n\n# \u754c\u9762\u4ee3\u7801\nstate = AppState()\n\nwith ui.row(align_items=\"center\"):\n ui.button(\"append\", on_click=state.append)\n ui.button(\"pop\", on_click=state.pop)\n ui.button(\"reverse\", on_click=state.reverse)\n\nrxui.label(state.display_nums)\n\n```\n\n\u5982\u679c\u4f60\u9700\u8981\u5728\u5b9a\u4e49\u5217\u8868\u65f6\uff0c\u521d\u59cb\u5316\u5217\u8868\uff0c\u5efa\u8bae\u5728 `__init__` \u4e2d\u8bbe\u7f6e\u3002\n```python\nclass AppState(rxui.ViewModel):\n nums = []\n # nums = [1,2,3] \u274c \u5982\u679c\u9700\u8981\u521d\u59cb\u5316\uff0c\u5fc5\u987b\u5728 __init__ \u4e2d\u8bbe\u7f6e\n\n def __init__(self):\n super().__init__()\n self.nums = [1, 2, 3]\n\n ...\n```\n\n\u53e6\u4e00\u79cd\u65b9\u5f0f\u662f\u4f7f\u7528 `rxui.list_var`\n\n```python\nclass AppState(rxui.ViewModel):\n # nums = []\n # nums = [1,2,3] \u274c \u5982\u679c\u9700\u8981\u521d\u59cb\u5316\uff0c\u5fc5\u987b\u5728 __init__ \u4e2d\u8bbe\u7f6e\n nums = rxui.list_var(lambda: [1, 2, 3])\n\n ...\n```\n\n- `rxui.list_var` \u53c2\u6570\u662f\u4e00\u4e2a\u8fd4\u56de\u5217\u8868\u7684\u51fd\u6570\n\n\n### \u5217\u8868\u5faa\u73af\n\n\u5b9a\u4e49\u5217\u8868\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u7528 `effect_refreshable.on` \u88c5\u9970\u5668\uff0c\u5728\u754c\u9762\u4e2d\u5c55\u793a\u5217\u8868\u6570\u636e\u3002\n\n\u4e0b\u9762\u7684\u4f8b\u5b50\u4e2d\uff0c\u754c\u9762\u4f1a\u52a8\u6001\u5c55\u793a\u4e0b\u62c9\u6846\u9009\u4e2d\u7684\u56fe\u6807\n\n```python\nfrom ex4nicegui import rxui, effect_refreshable\n\n\nclass AppState(rxui.ViewModel):\n icons = []\n _option_icons = [\"font_download\", \"warning\", \"format_size\", \"print\"]\n\n\nstate = AppState()\n\n# \u754c\u9762\u4ee3\u7801\nwith ui.row(align_items=\"center\"):\n\n @effect_refreshable.on(state.icons)\n def _():\n for icon in state.icons:\n ui.icon(icon, size=\"2rem\")\n\n\nrxui.select(state._option_icons, value=state.icons, multiple=True)\n```\n\n\u5176\u4e2d\uff0c`@effect_refreshable.on(state.icons)` \u660e\u786e\u6307\u5b9a\u4e86\u4f9d\u8d56\u5173\u7cfb\u3002\u5f53 `state.icons` \u53d8\u5316\u65f6\uff0c`_` \u51fd\u6570\u4f1a\u91cd\u65b0\u6267\u884c\u3002\n\n```python\n@effect_refreshable.on(state.icons)\ndef _():\n # \u8fd9\u91cc\u7684\u4ee3\u7801\u4f1a\u5728 state.icons \u53d8\u5316\u65f6\u91cd\u65b0\u6267\u884c\n ...\n```\n\n> \u6ce8\u610f\uff0c\u6bcf\u6b21\u6267\u884c\uff0c\u91cc\u9762\u7684\u5185\u5bb9\u90fd\u4f1a\u88ab\u6e05\u9664\u3002\u8fd9\u662f\u6570\u636e\u9a71\u52a8\u7248\u672c\u7684 `ui.refreshable`\n\n\u539f\u5219\u4e0a\uff0c\u53ef\u4ee5\u4e0d\u901a\u8fc7 `.on` \u6307\u5b9a\u76d1\u63a7\u7684\u6570\u636e\uff0c\u53ea\u8981\u51fd\u6570\u4e2d\u4f7f\u7528\u5230\u7684\"\u54cd\u5e94\u5f0f\u6570\u636e\"\uff0c\u90fd\u4f1a\u81ea\u52a8\u76d1\u63a7\n```python\n@effect_refreshable # \u6ca1\u6709\u4f7f\u7528 .on(state.icons)\ndef _():\n # \u8fd9\u91cc\u8bfb\u53d6\u4e86 state.icons\uff0c\u56e0\u6b64\u4f1a\u81ea\u52a8\u76d1\u63a7\n for icon in state.icons:\n ui.icon(icon, size=\"2rem\")\n\n```\n\n> \u5efa\u8bae\u603b\u662f\u901a\u8fc7 `.on` \u6307\u5b9a\u4f9d\u8d56\u5173\u7cfb\uff0c\u907f\u514d\u9884\u6599\u4e4b\u5916\u7684\u5237\u65b0\n\n---\n\n### \u6570\u636e\u6301\u4e45\u5316\n\n`ViewModel` \u4f7f\u7528\u4ee3\u7406\u5bf9\u8c61\u521b\u5efa\u54cd\u5e94\u5f0f\u6570\u636e\uff0c\u5f53\u9700\u8981\u4fdd\u5b58\u6570\u636e\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528 `rxui.ViewModel.to_value` \u8f6c\u6362\u6210\u666e\u901a\u6570\u636e.\n\n\u4e0b\u9762\u7684\u4f8b\u5b50\uff0c\u70b9\u51fb\u6309\u94ae\u5c06\u663e\u793a my_app \u7684\u72b6\u6001\u6570\u636e\u5b57\u5178\u3002\n```python\nfrom nicegui import ui\nfrom ex4nicegui import rxui\n\n\nclass MyApp(rxui.ViewModel):\n a = 0\n sign = \"+\"\n b = 0\n\n def show_data(self):\n # >> {\"a\": 0, \"sign\": '+, \"b\": 0}\n return rxui.ViewModel.to_value(self)\n\n def show_a(self):\n # >> 0\n return rxui.ViewModel.to_value(self.a)\n\nmy_app = MyApp()\n\nrxui.number(value=my_app.a, min=0, max=10)\nrxui.radio([\"+\", \"-\", \"*\", \"/\"], value=my_app.sign)\nrxui.number(value=my_app.b, min=0, max=10)\n\nui.button(\"show data\", on_click=lambda: ui.notify(my_app.show_data()))\n\n```\n\n\u7ed3\u5408 `rxui.ViewModel.on_refs_changed` \uff0c\u53ef\u4ee5\u5728\u6570\u636e\u53d8\u5316\u65f6\uff0c\u81ea\u52a8\u4fdd\u5b58\u6570\u636e\u5230\u672c\u5730\u3002\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import rxui\nfrom pathlib import Path\nimport json\n\n\nclass MyApp(rxui.ViewModel):\n a = 0\n sign = \"+\"\n b = 0\n\n _json_path = Path(__file__).parent / \"data.json\"\n\n def __init__(self):\n super().__init__()\n\n @rxui.ViewModel.on_refs_changed(self)\n def _():\n # a, sign, b \u4efb\u610f\u4e00\u4e2a\u503c\u53d8\u5316\u65f6\uff0c\u81ea\u52a8\u4fdd\u5b58\u5230\u672c\u5730\n self._json_path.write_text(json.dumps(self.show_data()))\n\n def show_data(self):\n return rxui.ViewModel.to_value(self)\n...\n\n```\n\n\n---\n\n\n\n- [ex4nicegui](#ex4nicegui)\n - [\u6559\u7a0b](#\u6559\u7a0b)\n - [\ud83d\udce6 \u5b89\u88c5](#-\u5b89\u88c5)\n - [\u5165\u95e8](#\u5165\u95e8)\n - [\u4e8c\u6b21\u8ba1\u7b97](#\u4e8c\u6b21\u8ba1\u7b97)\n - [\u4e8c\u6b21\u8ba1\u7b97\u7f13\u5b58](#\u4e8c\u6b21\u8ba1\u7b97\u7f13\u5b58)\n - [\u5217\u8868](#\u5217\u8868)\n - [\u5217\u8868\u5faa\u73af](#\u5217\u8868\u5faa\u73af)\n - [\u6570\u636e\u6301\u4e45\u5316](#\u6570\u636e\u6301\u4e45\u5316)\n - [apis](#apis)\n - [ViewModel](#viewmodel)\n - [\u4f7f\u7528\u5217\u8868](#\u4f7f\u7528\u5217\u8868)\n - [\u54cd\u5e94\u5f0f](#\u54cd\u5e94\u5f0f)\n - [`to_ref`](#to_ref)\n - [`deep_ref`](#deep_ref)\n - [`effect`](#effect)\n - [`ref_computed`](#ref_computed)\n - [`async_computed`](#async_computed)\n - [`on`](#on)\n - [`new_scope`](#new_scope)\n - [\u7ec4\u4ef6\u529f\u80fd](#\u7ec4\u4ef6\u529f\u80fd)\n - [vmodel](#vmodel)\n - [vfor](#vfor)\n - [bind\\_classes](#bind_classes)\n - [bind\\_style](#bind_style)\n - [bind\\_prop](#bind_prop)\n - [rxui.echarts](#rxuiecharts)\n - [echarts \u56fe\u8868\u9f20\u6807\u4e8b\u4ef6](#echarts-\u56fe\u8868\u9f20\u6807\u4e8b\u4ef6)\n - [rxui.echarts.from\\_javascript](#rxuiechartsfrom_javascript)\n - [rxui.echarts.register\\_map](#rxuiechartsregister_map)\n - [tab\\_panels](#tab_panels)\n - [lazy\\_tab\\_panels](#lazy_tab_panels)\n - [scoped\\_style](#scoped_style)\n - [BI \u6a21\u5757](#bi-\u6a21\u5757)\n - [`bi.data_source`](#bidata_source)\n - [ui\\_select](#ui_select)\n - [ui\\_table](#ui_table)\n - [ui\\_aggrid](#ui_aggrid)\n\n---\n\n\n## apis\n\n### ViewModel\n\u5728 `v0.7.0` \u7248\u672c\u4e2d\uff0c\u5f15\u5165 `ViewModel` \u7c7b\uff0c\u7528\u4e8e\u7ba1\u7406\u4e00\u7ec4\u54cd\u5e94\u5f0f\u6570\u636e\u3002\n\n\u4e0b\u9762\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u8ba1\u7b97\u5668\u793a\u4f8b\uff1a\n\n1. \u5f53\u7528\u6237\u4fee\u6539\u6570\u503c\u8f93\u5165\u6846\u6216\u7b26\u53f7\u9009\u62e9\u6846\uff0c\u53f3\u4fa7\u4f1a\u81ea\u52a8\u663e\u793a\u8ba1\u7b97\u7ed3\u679c\n2. \u5f53\u7ed3\u679c\u5c0f\u4e8e 0 \u65f6\uff0c\u7ed3\u679c\u663e\u793a\u4e3a\u7ea2\u8272\uff0c\u5426\u5219\u4e3a\u9ed1\u8272\n\n```python\nfrom ex4nicegui import rxui\n\nclass Calculator(rxui.ViewModel):\n num1 = 0\n sign = \"+\"\n num2 = 0\n\n @rxui.cached_var\n def result(self):\n # \u5f53 num1,sign,num2 \u4efb\u610f\u4e00\u4e2a\u503c\u53d1\u751f\u53d8\u5316\u65f6\uff0cresult \u4e5f\u4f1a\u91cd\u65b0\u8ba1\u7b97\n return eval(f\"{self.num1}{self.sign}{self.num2}\")\n\n# \u6bcf\u4e2a\u5bf9\u8c61\u62e5\u6709\u72ec\u7acb\u7684\u6570\u636e\ncalc = Calculator()\n\nwith ui.row(align_items=\"center\"):\n rxui.number(value=calc.num1, label=\"Number 1\")\n rxui.select(value=calc.sign, options=[\"+\", \"-\", \"*\", \"/\"], label=\"Sign\")\n rxui.number(value=calc.num2, label=\"Number 2\")\n ui.label(\"=\")\n rxui.label(calc.result).bind_color(\n lambda: \"red\" if calc.result() < 0 else \"black\"\n )\n\n```\n\n#### \u4f7f\u7528\u5217\u8868\n\n\u4e0b\u9762\u7684\u793a\u4f8b\uff0c\u6bcf\u4e2a person \u4f7f\u7528\u5361\u7247\u5c55\u793a\u3002\u6700\u4e0a\u65b9\u663e\u793a\u6240\u6709\u4eba\u7684\u5e73\u5747\u5e74\u9f84\u3002\u5f53\u4e2a\u4eba\u5e74\u9f84\u5927\u4e8e\u5e73\u5747\u5e74\u9f84\uff0c\u5361\u7247\u5916\u8fb9\u6846\u5c06\u53d8\u4e3a\u7ea2\u8272\u3002\n\u901a\u8fc7 `number` \u7ec4\u4ef6\u4fee\u6539\u5e74\u9f84\uff0c\u4e00\u5207\u90fd\u4f1a\u81ea\u52a8\u66f4\u65b0\u3002\n\n```python\nfrom typing import List\nfrom ex4nicegui import rxui\nfrom itertools import count\nfrom nicegui import ui\n\nid_generator = count()\n\nclass Person(rxui.ViewModel):\n name = \"\"\n age = 0\n\n def __init__(self, name: str, age: int):\n super().__init__()\n self.name = name\n self.age = age\n self.id = next(id_generator)\n\n\nclass Home(rxui.ViewModel):\n persons: List[Person] = []\n deleted_person_index = 0\n\n @rxui.cached_var\n def avg_age(self) -> float:\n if len(self.persons) == 0:\n return 0\n\n return round(sum(p.age for p in self.persons) / len(self.persons), 2)\n\n def avg_name_length(self):\n if len(self.persons) == 0:\n return 0\n\n return round(sum(len(p.name) for p in self.persons) / len(self.persons), 2)\n\n def delete_person(self):\n if self.deleted_person_index < len(self.persons):\n del self.persons[int(self.deleted_person_index)]\n\n def sample_data(self):\n self.persons = [\n Person(\"alice\", 25),\n Person(\"bob\", 30),\n Person(\"charlie\", 31),\n Person(\"dave\", 22),\n Person(\"eve\", 26),\n Person(\"frank\", 29),\n ]\n\n\nhome = Home()\nhome.sample_data()\n\nrxui.label(lambda: f\"\u5e73\u5747\u5e74\u9f84: {home.avg_age()}\")\nrxui.label(lambda: f\"\u5e73\u5747\u540d\u5b57\u957f\u5ea6: {home.avg_name_length()}\")\n\nrxui.number(\n value=home.deleted_person_index, min=0, max=lambda: len(home.persons) - 1, step=1\n)\nui.button(\"\u5220\u9664\", on_click=home.delete_person)\n\nwith ui.row():\n\n @rxui.vfor(home.persons, key=\"id\")\n def _(store: rxui.VforStore[Person]):\n person = store.get_item()\n with rxui.card().classes(\"outline\").bind_classes(\n {\n \"outline-red-500\": lambda: person.age > home.avg_age(),\n }\n ):\n rxui.input(value=person.name, placeholder=\"\u540d\u5b57\")\n rxui.number(value=person.age, min=1, max=100, step=1, placeholder=\"\u5e74\u9f84\")\n\nui.run()\n```\n\n\u5982\u679c\u4f60\u89c9\u5f97 `rxui.vfor` \u4ee3\u7801\u8fc7\u4e8e\u590d\u6742\uff0c\u53ef\u4ee5\u4f7f\u7528 `effect_refreshable` \u88c5\u9970\u5668\u4ee3\u66ff\u3002\n\n```python\nfrom ex4nicegui import rxui, Ref,effect_refreshable\n...\n\n# \u660e\u786e\u6307\u5b9a\u76d1\u63a7 home.persons \u53d8\u5316\uff0c\u53ef\u4ee5\u907f\u514d\u610f\u5916\u5237\u65b0\n@effect_refreshable.on(home.persons)\ndef _():\n \n for person in home.persons.value:\n ...\n rxui.number(value=person.age, min=1, max=100, step=1, placeholder=\"\u5e74\u9f84\")\n...\n```\n\n\u9700\u8981\u6ce8\u610f\u5230\uff0c\u6bcf\u5f53 `home.persons` \u5217\u8868\u53d8\u5316\u65f6(\u6bd4\u5982\u65b0\u589e\u6216\u5220\u9664\u5143\u7d20)\uff0c`effect_refreshable` \u88c5\u9970\u7684\u51fd\u6570\u90fd\u4f1a\u91cd\u65b0\u6267\u884c\u3002\u610f\u5473\u7740\u6240\u6709\u5143\u7d20\u90fd\u4f1a\u91cd\u65b0\u521b\u5efa\u3002\n\n\n\u66f4\u591a\u590d\u6742\u7684\u5e94\u7528\uff0c\u53ef\u4ee5\u67e5\u770b [examples](./examples)\n\n---\n\n\n### \u54cd\u5e94\u5f0f\n\n```python\nfrom ex4nicegui import (\n to_ref,\n ref_computed,\n on,\n effect,\n effect_refreshable,\n batch,\n event_batch,\n deep_ref,\n async_computed\n)\n```\n\u5e38\u7528 `to_ref`,`deep_ref`,`effect`,`ref_computed`,`on`,`async_computed`\n\n---\n\n#### `to_ref`\n\u5b9a\u4e49\u54cd\u5e94\u5f0f\u5bf9\u8c61,\u901a\u8fc7 `.value` \u8bfb\u5199\n```python\na = to_ref(1)\nb = to_ref(\"text\")\n\na.value =2\nb.value = 'new text'\n\nprint(a.value)\n```\n\n\u5f53\u503c\u4e3a\u590d\u6742\u5bf9\u8c61\u65f6\uff0c\u9ed8\u8ba4\u4e0d\u4f1a\u4fdd\u6301\u5d4c\u5957\u5bf9\u8c61\u7684\u54cd\u5e94\u6027\u3002\n```python\na = to_ref([1,2])\n\n@effect\ndef _():\n print('len:',len(a.value))\n\n# \u4e0d\u4f1a\u89e6\u53d1 effect\na.value.append(10)\n\n# \u6574\u4e2a\u66ff\u6362\u5219\u4f1a\u89e6\u53d1\na.value = [1,2,10]\n```\n\n\u53c2\u6570 `is_deep` \u8bbe\u7f6e\u4e3a `True` \u65f6\uff0c\u80fd\u5f97\u5230\u6df1\u5ea6\u54cd\u5e94\u80fd\u529b\n\n```python\na = to_ref([1,2],is_deep=True)\n\n@effect\ndef _():\n print('len:',len(a.value))\n\n# print 3\na.value.append(10)\n\n```\n\n> `deep_ref` \u7b49\u4ef7\u4e8e `is_deep` \u8bbe\u7f6e\u4e3a `True` \u65f6\u7684 `to_ref`\n\n---\n\n#### `deep_ref`\n\u7b49\u4ef7\u4e8e `is_deep` \u8bbe\u7f6e\u4e3a `True` \u65f6\u7684 `to_ref`\u3002\n\n\u5f53\u6570\u636e\u6e90\u4e3a\u5217\u8868\u3001\u5b57\u5178\u6216\u81ea\u5b9a\u4e49\u7c7b\u65f6\uff0c\u7279\u522b\u6709\u7528\u3002\u901a\u8fc7 `.value` \u83b7\u53d6\u7684\u5bf9\u8c61\u4e3a\u4ee3\u7406\u5bf9\u8c61\n```python\ndata = [1,2,3]\ndata_ref = deep_ref(data)\n\nassert data_ref.value is not data\n```\n\n\u901a\u8fc7 `to_raw` \u53ef\u4ee5\u83b7\u53d6\u539f\u59cb\u5bf9\u8c61\n```python\nfrom ex4nicegui import to_raw, deep_ref\n\ndata = [1, 2, 3]\ndata_ref = deep_ref(data)\n\nassert data_ref.value is not data\nassert to_raw(data_ref.value) is data\n```\n\n\n---\n\n#### `effect`\n\u63a5\u53d7\u4e00\u4e2a\u51fd\u6570,\u81ea\u52a8\u76d1\u63a7\u51fd\u6570\u4e2d\u4f7f\u7528\u5230\u7684\u54cd\u5e94\u5f0f\u5bf9\u8c61\u53d8\u5316,\u4ece\u800c\u81ea\u52a8\u6267\u884c\u51fd\u6570\n\n```python\na = to_ref(1)\nb = to_ref(\"text\")\n\n\n@effect\ndef auto_run_when_ref_value():\n print(f\"a:{a.value}\")\n\n\ndef change_value():\n a.value = 2\n b.value = \"new text\"\n\n\nui.button(\"change\", on_click=change_value)\n```\n\n\u9996\u6b21\u6267\u884c effect ,\u51fd\u6570`auto_run_when_ref_value`\u5c06\u88ab\u6267\u884c\u4e00\u6b21.\u4e4b\u540e\u70b9\u51fb\u6309\u94ae,\u6539\u53d8 `a` \u7684\u503c(\u901a\u8fc7 `a.value`),\u51fd\u6570`auto_run_when_ref_value`\u518d\u6b21\u6267\u884c\n\n> \u5207\u5fcc\u628a\u5927\u91cf\u6570\u636e\u5904\u7406\u903b\u8f91\u5206\u6563\u5728\u591a\u4e2a `on` \u6216 `effect` \u4e2d\uff0c`on` \u6216 `effect` \u4e2d\u5e94\u8be5\u5927\u90e8\u5206\u4e3a\u754c\u9762\u64cd\u4f5c\u903b\u8f91\uff0c\u800c\u975e\u54cd\u5e94\u5f0f\u6570\u636e\u5904\u7406\u903b\u8f91\n\n---\n\n#### `ref_computed`\n\u4e0e `effect` \u5177\u5907\u4e00\u6837\u7684\u529f\u80fd\uff0c`ref_computed` \u8fd8\u80fd\u4ece\u51fd\u6570\u4e2d\u8fd4\u56de\u7ed3\u679c\u3002\u4e00\u822c\u7528\u4e8e\u4ece `to_ref` \u4e2d\u8fdb\u884c\u4e8c\u6b21\u8ba1\u7b97\n\n```python\na = to_ref(1)\na_square = ref_computed(lambda: a.value * 2)\n\n\n@effect\ndef effect1():\n print(f\"a_square:{a_square.value}\")\n\n\ndef change_value():\n a.value = 2\n\n\nui.button(\"change\", on_click=change_value)\n```\n\n\u70b9\u51fb\u6309\u94ae\u540e\uff0c`a.value` \u503c\u88ab\u4fee\u6539\uff0c\u4ece\u800c\u89e6\u53d1 `a_square` \u91cd\u65b0\u8ba1\u7b97.\u7531\u4e8e `effect1` \u4e2d\u8bfb\u53d6\u4e86 `a_square` \u7684\u503c\uff0c\u4ece\u800c\u89e6\u53d1 `effect1` \u6267\u884c\n\n> `ref_computed` \u662f\u53ea\u8bfb\u7684 `to_ref`\n\n\u4ece `v0.7.0` \u7248\u672c\u5f00\u59cb\uff0c\u4e0d\u5efa\u8bae\u4f7f\u7528 `ref_computed` \u5e94\u7528\u5b9e\u4f8b\u65b9\u6cd5\u3002\u4f60\u53ef\u4ee5\u4f7f\u7528 `rxui.ViewModel`\uff0c\u5e76\u4f7f\u7528 `rxui.cached_var` \u88c5\u9970\u5668\n\n```python\nclass MyState(rxui.ViewModel):\n def __init__(self) -> None:\n self.r_text = to_ref(\"\")\n\n @rxui.cached_var\n def post_text(self):\n return self.r_text.value + \"post\"\n\nstate = MyState()\n\nrxui.input(value=state.r_text)\nrxui.label(state.post_text)\n```\n\n---\n\n#### `async_computed`\n\u4e8c\u6b21\u8ba1\u7b97\u4e2d\u9700\u8981\u4f7f\u7528\u5f02\u6b65\u51fd\u6570\u65f6\uff0c\u4f7f\u7528 `async_computed`\n```python\n\n# \u6a21\u62df\u957f\u65f6\u95f4\u6267\u884c\u7684\u5f02\u6b65\u51fd\u6570\nasync def long_time_query(input: str):\n await asyncio.sleep(2)\n num = random.randint(20, 100)\n return f\"query result[{input=}]:{num=}\"\n\n\nsearch = to_ref(\"\")\nevaluating = to_ref(False)\n\n@async_computed(search, evaluating=evaluating, init=\"\")\nasync def search_result():\n return await long_time_query(search.value)\n\nrxui.lazy_input(value=search)\n\nrxui.label(\n lambda: \"\u67e5\u8be2\u4e2d\" if evaluating.value else \"\u4e0a\u65b9\u8f93\u5165\u6846\u8f93\u5165\u5185\u5bb9\u5e76\u56de\u8f66\u641c\u7d22\"\n)\nrxui.label(search_result)\n\n```\n\n- `async_computed` \u7b2c\u4e00\u4e2a\u53c2\u6570\u5fc5\u987b\u660e\u786e\u6307\u5b9a\u9700\u8981\u76d1\u63a7\u7684\u54cd\u5e94\u5f0f\u6570\u636e. \u4f7f\u7528\u5217\u8868\u53ef\u4ee5\u540c\u65f6\u6307\u5b9a\u591a\u4e2a\u54cd\u5e94\u5f0f\u6570\u636e\n- \u53c2\u6570 `evaluating` \u4e3a bool \u7c7b\u578b\u7684\u54cd\u5e94\u5f0f\u6570\u636e\uff0c\u5f53\u5f02\u6b65\u51fd\u6570\u6267\u884c\u4e2d\uff0c\u6b64\u53d8\u91cf\u503c\u4e3a `True`\uff0c\u8ba1\u7b97\u7ed3\u675f\u540e\u4e3a `False`\n- \u53c2\u6570 `init` \u6307\u5b9a\u521d\u59cb\u7ed3\u679c\n\n\n---\n\n#### `on`\n\u7c7b\u4f3c `effect` \u7684\u529f\u80fd,\u4f46\u662f `on` \u9700\u8981\u660e\u786e\u6307\u5b9a\u76d1\u63a7\u7684\u54cd\u5e94\u5f0f\u5bf9\u8c61\n\n```python\n\na1 = to_ref(1)\na2 = to_ref(10)\nb = to_ref(\"text\")\n\n\n@on(a1)\ndef watch_a1_only():\n print(f\"watch_a1_only ... a1:{a1.value},a2:{a2.value}\")\n\n\n@on([a1, b], onchanges=True)\ndef watch_a1_and_b():\n print(f\"watch_a1_and_b ... a1:{a1.value},a2:{a2.value},b:{b.value}\")\n\n\ndef change_a1():\n a1.value += 1\n ui.notify(\"change_a1\")\n\n\nui.button(\"change a1\", on_click=change_a1)\n\n\ndef change_a2():\n a2.value += 1\n ui.notify(\"change_a2\")\n\n\nui.button(\"change a2\", on_click=change_a2)\n\n\ndef change_b():\n b.value += \"x\"\n ui.notify(\"change_b\")\n\n\nui.button(\"change b\", on_click=change_b)\n\n```\n\n- \u53c2\u6570 `onchanges` \u4e3a True \u65f6(\u9ed8\u8ba4\u503c\u4e3a False),\u6307\u5b9a\u7684\u51fd\u6570\u4e0d\u4f1a\u5728\u7ed1\u5b9a\u65f6\u6267\u884c \n\n\n> \u5207\u5fcc\u628a\u5927\u91cf\u6570\u636e\u5904\u7406\u903b\u8f91\u5206\u6563\u5728\u591a\u4e2a `on` \u6216 `effect` \u4e2d\uff0c`on` \u6216 `effect` \u4e2d\u5e94\u8be5\u5927\u90e8\u5206\u4e3a\u754c\u9762\u64cd\u4f5c\u903b\u8f91\uff0c\u800c\u975e\u54cd\u5e94\u5f0f\u6570\u636e\u5904\u7406\u903b\u8f91\n\n---\n\n#### `new_scope`\n\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u6240\u6709\u68c0\u6d4b\u51fd\u6570\u5728\u5ba2\u6237\u7aef\u8fde\u63a5\u65ad\u5f00\u65f6\u81ea\u52a8\u9500\u6bc1\u3002\u5982\u679c\u9700\u8981\u66f4\u7ec6\u7c92\u5ea6\u7684\u63a7\u5236\uff0c\u53ef\u4ee5\u4f7f\u7528 `new_scope`\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import rxui, to_ref, effect, new_scope\n\na = to_ref(0.0)\n\nscope1 = new_scope()\n\n@scope1.run\ndef _():\n @effect\n def _():\n print(f\"scope 1:{a.value}\")\n\n\nrxui.number(value=a)\nrxui.button(\"dispose scope 1\", on_click=scope1.dispose)\n```\n\n---\n\n\n### \u7ec4\u4ef6\u529f\u80fd\n\n#### vmodel\n\u5728\u8868\u5355\u8f93\u5165\u5143\u7d20\u6216\u7ec4\u4ef6\u4e0a\u521b\u5efa\u53cc\u5411\u7ed1\u5b9a\u3002\n\n\u7b80\u5355\u503c\u7c7b\u578b\u7684 `ref` \u9ed8\u8ba4\u652f\u6301\u53cc\u5411\u7ed1\u5b9a\n```python\nfrom ex4nicegui import rxui, to_ref, deep_ref\n\ndata = to_ref(\"init\")\n\nrxui.label(lambda: f\"{data.value=}\")\n# \u9ed8\u8ba4\u5c31\u662f\u53cc\u5411\u7ed1\u5b9a\nrxui.input(value=data)\n```\n\n- \u7b80\u5355\u503c\u7c7b\u578b\u4e00\u822c\u662f `str`,`int` \u7b49\u4e0d\u53ef\u53d8\u503c\u7c7b\u578b\n\n\u5f53\u4f7f\u7528\u590d\u6742\u6570\u636e\u7ed3\u6784\u65f6\uff0c\u4f1a\u4f7f\u7528 `deep_ref` \u4fdd\u6301\u5d4c\u5957\u503c\u7684\u54cd\u5e94\u6027\n```python\ndata = deep_ref({\"a\": 1, \"b\": [1, 2, 3, 4]})\n\nrxui.label(lambda: f\"{data.value=!s}\")\n\n# \u5f53\u524d\u7248\u672c\u6ca1\u6709\u4efb\u4f55\u7ed1\u5b9a\u6548\u679c.\u6216\u8bb8\u672a\u6765\u7684\u7248\u672c\u53ef\u4ee5\u89e3\u51b3\nrxui.input(value=data.value[\"a\"])\n\n# \u53ea\u8bfb\u7ed1\u5b9a.\u5176\u4ed6\u9014\u5f84\u4fee\u6539\u4e86 `data.value[\"a\"]` \uff0c\u6b64\u8f93\u5165\u6846\u4f1a\u540c\u6b65\uff0c\u4f46\u53cd\u8fc7\u6765\u4e0d\u884c\nrxui.input(value=lambda: data.value[\"a\"])\n\n# \u8981\u4f7f\u7528 vmodel \u624d\u80fd\u53cc\u5411\u7ed1\u5b9a\nrxui.input(value=rxui.vmodel(data, \"a\"))\n\n# \u4e5f\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\uff0c\u4f46\u4e0d\u63a8\u8350\nrxui.input(value=rxui.vmodel(data.value['a']))\n\n```\n\n- \u7b2c\u4e00\u4e2a\u8f93\u5165\u6846\u5c06\u5b8c\u5168\u5931\u53bb\u54cd\u5e94\u6027\uff0c\u56e0\u4e3a\u4ee3\u7801\u7b49\u4ef7\u4e8e `rxui.input(value=1)`\n- \u7b2c\u4e8c\u4e2a\u8f93\u5165\u6846\u7531\u4e8e\u4f7f\u7528\u51fd\u6570\uff0c\u5c06\u5f97\u5230\u8bfb\u53d6\u54cd\u5e94\u6027(\u7b2c\u4e09\u4e2a\u8f93\u5165\u6846\u8f93\u5165\u503c\uff0c\u5c06\u5f97\u5230\u540c\u6b65)\n- \u7b2c\u4e09\u4e2a\u8f93\u5165\u6846\uff0c\u4f7f\u7528 `rxui.vmodel` \u5305\u88f9\uff0c\u5373\u53ef\u5b9e\u73b0\u53cc\u5411\u7ed1\u5b9a\n\n> \u5982\u679c\u4f7f\u7528 `rxui.ViewModel` \uff0c\u4f60\u53ef\u80fd\u4e0d\u9700\u8981\u4f7f\u7528 `vmodel`\n\n\u53ef\u53c2\u8003 [todo list \u6848\u4f8b](./examples/todomvc/)\n\n---\n\n#### vfor\n\u57fa\u4e8e\u5217\u8868\u54cd\u5e94\u5f0f\u6570\u636e\uff0c\u6e32\u67d3\u5217\u8868\u7ec4\u4ef6\u3002\u6bcf\u9879\u7ec4\u4ef6\u6309\u9700\u66f4\u65b0\u3002\u6570\u636e\u9879\u652f\u6301\u5b57\u5178\u6216\u4efb\u610f\u7c7b\u578b\u5bf9\u8c61\u3002\n\n\u4ece `v0.7.0` \u7248\u672c\u5f00\u59cb\uff0c\u5efa\u8bae\u914d\u5408 `rxui.ViewModel` \u4f7f\u7528\u3002\u4e0e\u4f7f\u7528 `effect_refreshable` \u88c5\u9970\u5668\u4e0d\u540c\uff0c`vfor` \u4e0d\u4f1a\u91cd\u65b0\u521b\u5efa\u6240\u6709\u7684\u5143\u7d20\uff0c\u800c\u662f\u66f4\u65b0\u5df2\u5b58\u5728\u7684\u5143\u7d20\u3002\n\n\u4e0b\u9762\u662f\u5361\u7247\u6392\u5e8f\u4f8b\u5b50\uff0c\u5361\u7247\u603b\u662f\u6309\u5e74\u9f84\u6392\u5e8f\u3002\u5f53\u4f60\u4fee\u6539\u67d0\u4e2a\u5361\u7247\u4e2d\u7684\u5e74\u9f84\u6570\u636e\u65f6\uff0c\u5361\u7247\u4f1a\u5b9e\u65f6\u8c03\u6574\u987a\u5e8f\u3002\u4f46\u662f\uff0c\u5149\u6807\u7126\u70b9\u4e0d\u4f1a\u79bb\u5f00\u8f93\u5165\u6846\u3002\n\n\n```python\nfrom typing import List\nfrom nicegui import ui\nfrom ex4nicegui import rxui, deep_ref as ref, Ref\n\n\nclass Person(rxui.ViewModel):\n def __init__(self, name: str, age: int) -> None:\n self.name = name\n self.age = ref(age)\n\n\nclass MyApp(rxui.ViewModel):\n persons: Ref[List[Person]] = rxui.var(lambda: [])\n order = rxui.var(\"asc\")\n\n def sort_by_age(self):\n return sorted(\n self.persons.value,\n key=lambda p: p.age.value,\n reverse=self.order.value == \"desc\",\n )\n\n @staticmethod\n def create():\n persons = [\n Person(name=\"Alice\", age=25),\n Person(name=\"Bob\", age=30),\n Person(name=\"Charlie\", age=20),\n Person(name=\"Dave\", age=35),\n Person(name=\"Eve\", age=28),\n ]\n app = MyApp()\n app.persons.value = persons\n return app\n\n\n# ui\napp = MyApp.create()\n\nwith rxui.tabs(app.order):\n rxui.tab(\"asc\", \"Ascending\")\n rxui.tab(\"desc\", \"Descending\")\n\n\n@rxui.vfor(app.sort_by_age, key=\"name\")\ndef each_person(s: rxui.VforStore[Person]):\n person = s.get_item()\n\n with ui.card(), ui.row(align_items=\"center\"):\n rxui.label(person.name)\n rxui.number(value=person.age, step=1, min=0, max=100)\n\n```\n\n- `rxui.vfor` \u88c5\u9970\u5668\u5230\u81ea\u5b9a\u4e49\u51fd\u6570\n - \u7b2c\u4e00\u4e2a\u53c2\u6570\u4f20\u5165\u54cd\u5e94\u5f0f\u5217\u8868\u3002\u6ce8\u610f\uff0c\u65e0\u987b\u8c03\u7528 `app.sort_by_age`\n - \u7b2c\u4e8c\u4e2a\u53c2\u6570 `key`: \u4e3a\u4e86\u53ef\u4ee5\u8ddf\u8e2a\u6bcf\u4e2a\u8282\u70b9\u7684\u6807\u8bc6\uff0c\u4ece\u800c\u91cd\u7528\u548c\u91cd\u65b0\u6392\u5e8f\u73b0\u6709\u7684\u5143\u7d20\uff0c\u4f60\u53ef\u4ee5\u4e3a\u6bcf\u4e2a\u5143\u7d20\u5bf9\u5e94\u7684\u5757\u63d0\u4f9b\u4e00\u4e2a\u552f\u4e00\u7684 key \u3002\u9ed8\u8ba4\u60c5\u51b5\u4f7f\u7528\u5217\u8868\u5143\u7d20\u7d22\u5f15\u3002\u4f8b\u5b50\u4e2d\u5047\u5b9a\u6bcf\u4e2a\u4eba\u7684\u540d\u5b57\u552f\u4e00\u3002\n- \u81ea\u5b9a\u4e49\u51fd\u6570\u5e26\u6709\u4e00\u4e2a\u53c2\u6570\u3002\u901a\u8fc7 `store.get_item` \u53ef\u4ee5\u83b7\u53d6\u5f53\u524d\u884c\u7684\u5bf9\u8c61\u3002\u7531\u4e8e Person \u672c\u8eab\u7ee7\u627f\u81ea `rxui.ViewModel`\uff0c\u6240\u4ee5\u5b83\u7684\u5404\u9879\u5c5e\u6027\u53ef\u4ee5\u76f4\u63a5\u7ed1\u5b9a\u5230\u7ec4\u4ef6\u3002\n\n\n---\n\n#### bind_classes\n\n\u6240\u6709\u7684\u7ec4\u4ef6\u7c7b\u63d0\u4f9b `bind_classes` \u7528\u4e8e\u7ed1\u5b9a `class`\uff0c\u652f\u6301\u4e09\u79cd\u4e0d\u540c\u7684\u6570\u636e\u7ed3\u6784\u3002\n\n\u7ed1\u5b9a\u5b57\u5178\n\n```python\nbg_color = to_ref(False)\nhas_error = to_ref(False)\n\nrxui.label(\"test\").bind_classes({\"bg-blue\": bg_color, \"text-red\": has_error})\n\nrxui.switch(\"bg_color\", value=bg_color)\nrxui.switch(\"has_error\", value=has_error)\n```\n\n\u5b57\u5178\u952e\u503c\u4e3a\u7c7b\u540d,\u5bf9\u5e94\u503c\u4e3a bool \u7684\u54cd\u5e94\u5f0f\u53d8\u91cf\u3002\u5f53\u54cd\u5e94\u5f0f\u503c\u4e3a `True`\uff0c\u7c7b\u540d\u5e94\u7528\u5230\u7ec4\u4ef6 class\n\n\n---\n\n\u7ed1\u5b9a\u8fd4\u56de\u503c\u4e3a\u5b57\u5178\u7684\u54cd\u5e94\u5f0f\u53d8\u91cf\n\n```python\nbg_color = to_ref(False)\nhas_error = to_ref(False)\n\nclass_obj = ref_computed(\n lambda: {\"bg-blue\": bg_color.value, \"text-red\": has_error.value}\n)\n\nrxui.switch(\"bg_color\", value=bg_color)\nrxui.switch(\"has_error\", value=has_error)\nrxui.label(\"bind to ref_computed\").bind_classes(class_obj)\n# or direct function passing\nrxui.label(\"bind to ref_computed\").bind_classes(\n lambda: {\"bg-blue\": bg_color.value, \"text-red\": has_error.value}\n)\n```\n\n---\n\n\u7ed1\u5b9a\u4e3a\u5217\u8868\u6216\u5355\u4e2a\u5b57\u7b26\u4e32\u7684\u54cd\u5e94\u5f0f\u53d8\u91cf\n\n```python\nbg_color = to_ref(\"red\")\nbg_color_class = ref_computed(lambda: f\"bg-{bg_color.value}\")\n\ntext_color = to_ref(\"green\")\ntext_color_class = ref_computed(lambda: f\"text-{text_color.value}\")\n\nrxui.select([\"red\", \"green\", \"yellow\"], label=\"bg color\", value=bg_color)\nrxui.select([\"red\", \"green\", \"yellow\"], label=\"text color\", value=text_color)\n\nrxui.label(\"binding to arrays\").bind_classes([bg_color_class, text_color_class])\nrxui.label(\"binding to single string\").bind_classes(bg_color_class)\n```\n\n- \u5217\u8868\u4e2d\u6bcf\u4e2a\u5143\u7d20\u4e3a\u8fd4\u56de\u7c7b\u540d\u7684\u54cd\u5e94\u5f0f\u53d8\u91cf\n\n\n---\n\n#### bind_style\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui.reactive import rxui\nfrom ex4nicegui.utils.signals import to_ref\n\n\nbg_color = to_ref(\"blue\")\ntext_color = to_ref(\"red\")\n\nrxui.label(\"test\").bind_style(\n {\n \"background-color\": bg_color,\n \"color\": text_color,\n }\n)\n\nrxui.select([\"blue\", \"green\", \"yellow\"], label=\"bg color\", value=bg_color)\nrxui.select([\"red\", \"green\", \"yellow\"], label=\"text color\", value=text_color)\n```\n\n`bind_style` \u4f20\u5165\u5b57\u5178\uff0c`key` \u4e3a\u6837\u5f0f\u540d\u5b57\uff0c`value` \u4e3a\u6837\u5f0f\u503c\uff0c\u54cd\u5e94\u5f0f\u5b57\u7b26\u4e32\n\n---\n\n#### bind_prop\n\n\u7ed1\u5b9a\u5355\u4e2a\u5c5e\u6027\n\n```python\n\nlabel = to_ref(\"hello\")\n\nrxui.button(\"\").bind_prop(\"label\", label)\n# \u5141\u8bb8\u4f7f\u7528\u51fd\u6570\nrxui.button(\"\").bind_prop(\n \"label\", lambda: f\"{label.value} world\"\n)\n\nrxui.input(value=label)\n```\n\n\n---\n\n#### rxui.echarts\n\u4f7f\u7528 echarts \u5236\u4f5c\u56fe\u8868\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import ref_computed, effect, to_ref\nfrom ex4nicegui.reactive import rxui\n\nr_input = to_ref(\"\")\n\n# ref_computed \u521b\u5efa\u53ea\u8bfb\u54cd\u5e94\u5f0f\u53d8\u91cf\n# \u51fd\u6570\u4e2d\u4f7f\u7528\u4efb\u610f\u5176\u4ed6\u54cd\u5e94\u5f0f\u53d8\u91cf\uff0c\u4f1a\u81ea\u52a8\u5173\u8054\n@ref_computed\ndef cp_echarts_opts():\n return {\n \"title\": {\"text\": r_input.value}, #\u5b57\u5178\u4e2d\u4f7f\u7528\u4efb\u610f\u54cd\u5e94\u5f0f\u53d8\u91cf\uff0c\u901a\u8fc7 .value \u83b7\u53d6\u503c\n \"xAxis\": {\n \"type\": \"category\",\n \"data\": [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"],\n },\n \"yAxis\": {\"type\": \"value\"},\n \"series\": [\n {\n \"data\": [120, 200, 150, 80, 70, 110, 130],\n \"type\": \"bar\",\n \"showBackground\": True,\n \"backgroundStyle\": {\"color\": \"rgba(180, 180, 180, 0.2)\"},\n }\n ],\n }\n\ninput = rxui.input(\"\u8f93\u5165\u5185\u5bb9\uff0c\u56fe\u8868\u6807\u9898\u4f1a\u540c\u6b65\", value=r_input)\n# \u901a\u8fc7\u54cd\u5e94\u5f0f\u7ec4\u4ef6\u5bf9\u8c61\u7684 element \u5c5e\u6027\uff0c\u83b7\u53d6\u539f\u751f nicegui \u7ec4\u4ef6\u5bf9\u8c61\ninput.element.classes(\"w-full\")\n\nrxui.echarts(cp_echarts_opts)\n\nui.run()\n```\n![](./asset/asyc_echarts_title.gif)\n\n\n##### echarts \u56fe\u8868\u9f20\u6807\u4e8b\u4ef6\n\n`on` \u51fd\u6570\u53c2\u6570 `event_name` \u4ee5\u53ca `query` \u4f7f\u7528,\u67e5\u770b[echarts \u4e8b\u4ef6\u4e2d\u6587\u6587\u6863](https://echarts.apache.org/handbook/zh/concepts/event/)\n\n\n\u4ee5\u4e0b\u4f8b\u5b50\u7ed1\u5b9a\u9f20\u6807\u5355\u51fb\u4e8b\u4ef6\n```python\nfrom nicegui import ui\nfrom ex4nicegui.reactive import rxui\n\nopts = {\n \"xAxis\": {\"type\": \"value\", \"boundaryGap\": [0, 0.01]},\n \"yAxis\": {\n \"type\": \"category\",\n \"data\": [\"Brazil\", \"Indonesia\", \"USA\", \"India\", \"China\", \"World\"],\n },\n \"series\": [\n {\n \"name\": \"first\",\n \"type\": \"bar\",\n \"data\": [18203, 23489, 29034, 104970, 131744, 630230],\n },\n {\n \"name\": \"second\",\n \"type\": \"bar\",\n \"data\": [19325, 23438, 31000, 121594, 134141, 681807],\n },\n ],\n}\n\nbar = rxui.echarts(opts)\n\ndef on_click(e: rxui.echarts.EChartsMouseEventArguments):\n ui.notify(f\"on_click:{e.seriesName}:{e.name}:{e.value}\")\n\n\nbar.on(\"click\", on_click)\n```\n\n\n\u4ee5\u4e0b\u4f8b\u5b50\u53ea\u9488\u5bf9\u6307\u5b9a\u7cfb\u5217\u89e6\u53d1\u9f20\u6807\u5212\u8fc7\u4e8b\u4ef6\n```python\nfrom nicegui import ui\nfrom ex4nicegui.reactive import rxui\n\nopts = {\n \"xAxis\": {\"type\": \"value\", \"boundaryGap\": [0, 0.01]},\n \"yAxis\": {\n \"type\": \"category\",\n \"data\": [\"Brazil\", \"Indonesia\", \"USA\", \"India\", \"China\", \"World\"],\n },\n \"series\": [\n {\n \"name\": \"first\",\n \"type\": \"bar\",\n \"data\": [18203, 23489, 29034, 104970, 131744, 630230],\n },\n {\n \"name\": \"second\",\n \"type\": \"bar\",\n \"data\": [19325, 23438, 31000, 121594, 134141, 681807],\n },\n ],\n}\n\nbar = rxui.echarts(opts)\n\ndef on_first_series_mouseover(e: rxui.echarts.EChartsMouseEventArguments):\n ui.notify(f\"on_first_series_mouseover:{e.seriesName}:{e.name}:{e.value}\")\n\n\nbar.on(\"mouseover\", on_first_series_mouseover, query={\"seriesName\": \"first\"})\n\nui.run()\n```\n---\n\n\n\n---\n\n##### rxui.echarts.from_javascript\n\u4ece javascript \u4ee3\u7801\u521b\u5efa echart\n\n```python\nfrom pathlib import Path\n\nrxui.echarts.from_javascript(Path(\"code.js\"))\n# or\nrxui.echarts.from_javascript(\n \"\"\"\n(myChart) => {\n\n option = {\n xAxis: {\n type: 'category',\n data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n },\n yAxis: {\n type: 'value'\n },\n series: [\n {\n data: [120, 200, 150, 80, 70, 110, 130],\n type: 'bar'\n }\n ]\n };\n\n myChart.setOption(option);\n}\n\"\"\"\n)\n```\n\n- \u51fd\u6570\u7b2c\u4e00\u4e2a\u53c2\u6570\u4e3a echart \u5b9e\u4f8b\u5bf9\u8c61.\u4f60\u9700\u8981\u5728\u51fd\u6570\u4e2d\u901a\u8fc7 `setOption` \u5b8c\u6210\u56fe\u8868\u914d\u7f6e\n\n\u51fd\u6570\u4e5f\u6709\u7b2c\u4e8c\u4e2a\u53c2\u6570\uff0c\u4e3a `echarts` \u5168\u5c40\u5bf9\u8c61\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7 `echarts.registerMap` \u6ce8\u518c\u5730\u56fe\u3002\n\n```python\nrxui.echarts.from_javascript(\n\"\"\"\n(chart,echarts) =>{\n\n fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')\n .then(response => response.json())\n .then(data => {\n echarts.registerMap('test_map', data);\n\n chart.setOption({\n geo: {\n map: 'test_map',\n roam: true,\n },\n tooltip: {},\n legend: {},\n series: [],\n });\n });\n}\n\"\"\"\n)\n```\n\n---\n\n##### rxui.echarts.register_map\n\u6ce8\u518c\u5730\u56fe.\n\n```python\nrxui.echarts.register_map(\n \"china\", \"https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json\"\n)\n\nrxui.echarts(\n {\n \"geo\": {\n \"map\": \"china\",\n \"roam\": True,\n },\n \"tooltip\": {},\n \"legend\": {},\n \"series\": [],\n }\n)\n```\n\n- \u53c2\u6570 `map_name` \u4e3a\u81ea\u5b9a\u4e49\u7684\u5730\u56fe\u540d\u5b57\u3002\u6ce8\u610f\u5728\u56fe\u8868\u914d\u7f6e\u4e2d `map` \u5fc5\u9700\u5bf9\u5e94\u6ce8\u518c\u7684\u540d\u5b57\n- \u53c2\u6570 `src` \u4e3a\u6709\u6548\u7684\u5730\u56fe\u6570\u636e\u7f51\u7edc\u94fe\u63a5\u3002\n\n\n\u5982\u679c\u662f svg \u6570\u636e\uff0c\u9700\u8981\u8bbe\u7f6e\u53c2\u6570 `type=\"svg\"`\n```python\nrxui.echarts.register_map(\"svg-rect\", \"/test/svg\", type=\"svg\")\n```\n\n\n\n\u4f60\u4e5f\u53ef\u4ee5\u76f4\u63a5\u63d0\u4f9b\u672c\u5730\u5730\u56fe\u6570\u636e\u7684json\u6587\u4ef6\u8def\u5f84\u5bf9\u8c61(Path)\n```python\nfrom pathlib import Path\n\nrxui.echarts.register_map(\n \"china\", Path(\"map-data.json\")\n)\n```\n\n---\n\n#### tab_panels\n\n\u76f8\u6bd4\u8f83\u4e8e `nicegui.ui.tab_panels` , `rxui.tab_panels` \u6ca1\u6709\u53c2\u6570 `tabs`\u3002\u5728\u6570\u636e\u54cd\u5e94\u5f0f\u673a\u5236\u4e0b\uff0c`tabs` \u4e0e `tab_panels` \u8054\u52a8\u53ea\u9700\u8981\u901a\u8fc7\u53c2\u6570 `value` \u5373\u53ef\u3002\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import rxui, to_ref\n\nnames = [\"Tab 1\", \"Tab 2\", \"Tab 3\"]\ncurrent_tab = to_ref(names[0])\n\nwith rxui.tabs(current_tab):\n for name in names:\n rxui.tab(name)\n\nwith rxui.tab_panels(current_tab):\n for name in names:\n with rxui.tab_panel(name):\n ui.label(f\"Content of {name}\")\n```\n\n\u8fd9\u662f\u56e0\u4e3a\uff0c\u6570\u636e\u54cd\u5e94\u673a\u5236\u4e0b\uff0c\u7ec4\u4ef6\u8054\u52a8\u662f\u901a\u8fc7\u4e2d\u95f4\u6570\u636e\u5c42(`to_ref`)\u5b9e\u73b0\u7684\u3002\u56e0\u6b64\uff0c`tab_panels` \u53ef\u4ee5\u4e0e\u5176\u4ed6\u7ec4\u4ef6\u8054\u52a8(\u53ea\u9700\u8981\u4fdd\u8bc1\u4f7f\u7528\u540c\u6837\u7684 `ref` \u5bf9\u8c61\u5373\u53ef)\n\n```python\nnames = [\"Tab 1\", \"Tab 2\", \"Tab 3\"]\ncurrent_tab = to_ref(names[0])\n\n\nwith rxui.tab_panels(current_tab):\n for name in names:\n with rxui.tab_panel(name):\n ui.label(f\"Content of {name}\")\n\n# tabs \u4e0d\u5fc5\u5728 panels \u524d\u9762\nwith rxui.tabs(current_tab):\n for name in names:\n rxui.tab(name)\n\nrxui.select(names, value=current_tab)\nrxui.radio(names, value=current_tab).props(\"inline\")\n\nrxui.label(lambda: f\"\u5f53\u524d tab \u4e3a:{current_tab.value}\")\n```\n---\n\n#### lazy_tab_panels\n\n\u61d2\u52a0\u8f7d\u6a21\u5f0f\u4e0b\uff0c\u53ea\u6709\u5f53\u524d\u6fc0\u6d3b\u7684 tab \u624d\u4f1a\u6e32\u67d3\u3002\n```python\nfrom ex4nicegui import to_ref, rxui, on, deep_ref\n\ncurrent_tab = to_ref(\"t1\")\n\nwith rxui.tabs(current_tab):\n ui.tab(\"t1\")\n ui.tab(\"t2\")\n\nwith rxui.lazy_tab_panels(current_tab) as panels:\n\n @panels.add_tab_panel(\"t1\")\n def _():\n # \u901a\u8fc7 `panels.get_panel` \u83b7\u53d6\u5f53\u524d\u6fc0\u6d3b\u7684 panel \u7ec4\u4ef6\n panels.get_panel(\"t1\").classes(\"bg-green\")\n ui.notify(\"Hello from t1\")\n ui.label(\"This is t1\")\n\n @panels.add_tab_panel(\"t2\")\n def _():\n panels.get_panel(\"t2\").style(\"background-color : red\")\n ui.notify(\"Hello from t2\")\n ui.label(\"This is t2\")\n\n```\n\n\u9875\u9762\u52a0\u8f7d\u540e\uff0c\u7acb\u523b\u663e\u793a \"Hello from t1\"\u3002\u5f53\u5207\u6362\u5230 \"t2\" \u9875\u7b7e\uff0c\u624d\u4f1a\u663e\u793a \"Hello from t2\"\u3002\n\n---\n\n#### scoped_style\n\n`scoped_style` \u65b9\u6cd5\u5141\u8bb8\u4f60\u521b\u5efa\u9650\u5b9a\u5728\u7ec4\u4ef6\u5185\u90e8\u7684\u6837\u5f0f\u3002\n\n```python\n# \u6240\u6709\u5b50\u5143\u7d20\u90fd\u4f1a\u6709\u7ea2\u8272\u8f6e\u5ed3\uff0c\u4f46\u6392\u9664\u81ea\u8eab\nwith rxui.row().scoped_style(\"*\", \"outline: 1px solid red;\") as row:\n ui.label(\"Hello\")\n ui.label(\"World\")\n\n\n# \u6240\u6709\u5b50\u5143\u7d20\u90fd\u4f1a\u6709\u7ea2\u8272\u8f6e\u5ed3\uff0c\u5305\u62ec\u81ea\u8eab\nwith rxui.row().scoped_style(\":self *\", \"outline: 1px solid red;\") as row:\n ui.label(\"Hello\")\n ui.label(\"World\")\n\n# \u5f53\u9f20\u6807\u60ac\u505c\u5728 row \u7ec4\u4ef6\u65f6,\u6240\u6709\u5b50\u5143\u7d20\u90fd\u4f1a\u6709\u7ea2\u8272\u8f6e\u5ed3\uff0c\u4f46\u6392\u9664\u81ea\u8eab\nwith rxui.row().scoped_style(\":hover *\", \"outline: 1px solid red;\") as row:\n ui.label(\"Hello\")\n ui.label(\"World\")\n\n# \u5f53\u9f20\u6807\u60ac\u505c\u5728 row \u7ec4\u4ef6\u65f6,\u6240\u6709\u5b50\u5143\u7d20\u90fd\u4f1a\u6709\u7ea2\u8272\u8f6e\u5ed3\uff0c\u5305\u62ec\u81ea\u8eab\nwith rxui.row().scoped_style(\":self:hover *\", \"outline: 1px solid red;\") as row:\n ui.label(\"Hello\")\n ui.label(\"World\")\n```\n\n\n---\n\n### BI \u6a21\u5757\n\n\u4ee5\u6700\u7cbe\u7b80\u7684 apis \u521b\u5efa\u53ef\u4ea4\u4e92\u7684\u6570\u636e\u53ef\u89c6\u5316\u62a5\u8868\n\n![](./asset/bi_examples1.gif)\n\n```python\nfrom nicegui import ui\nimport pandas as pd\nimport numpy as np\nfrom ex4nicegui import bi\nfrom ex4nicegui.reactive import rxui\nfrom ex4nicegui import effect, effect_refreshable\nfrom pyecharts.charts import Bar\n\n\n# data ready\ndef gen_data():\n np.random.seed(265)\n field1 = [\"a1\", \"a2\", \"a3\", \"a4\"]\n field2 = [f\"name{i}\" for i in range(1, 11)]\n df = (\n pd.MultiIndex.from_product([field1, field2], names=[\"cat\", \"name\"])\n .to_frame()\n .reset_index(drop=True)\n )\n df[[\"idc1\", \"idc2\"]] = np.random.randint(50, 1000, size=(len(df), 2))\n return df\n\n\ndf = gen_data()\n\n# \u521b\u5efa\u6570\u636e\u6e90\nds = bi.data_source(df)\n\n# ui\nui.query(\".nicegui-content\").classes(\"items-stretch no-wrap\")\n\nwith ui.row().classes(\"justify-evenly\"):\n # \u57fa\u4e8e\u6570\u636e\u6e90 `ds` \u521b\u5efa\u754c\u9762\u7ec4\u4ef6\n ds.ui_select(\"cat\").classes(\"min-w-[10rem]\")\n ds.ui_select(\"name\").classes(\"min-w-[10rem]\")\n\n\nwith ui.grid(columns=2):\n # \u4f7f\u7528\u5b57\u5178\u914d\u7f6e\u56fe\u8868\n @ds.ui_echarts\n def bar1(data: pd.DataFrame):\n data = data.groupby(\"name\").agg({\"idc1\": \"sum\", \"idc2\": \"sum\"}).reset_index()\n\n return {\n \"xAxis\": {\"type\": \"value\"},\n \"yAxis\": {\n \"type\": \"category\",\n \"data\": data[\"name\"].tolist(),\n \"inverse\": True,\n },\n \"legend\": {\"textStyle\": {\"color\": \"gray\"}},\n \"series\": [\n {\"type\": \"bar\", \"name\": \"idc1\", \"data\": data[\"idc1\"].tolist()},\n {\"type\": \"bar\", \"name\": \"idc2\", \"data\": data[\"idc2\"].tolist()},\n ],\n }\n\n bar1.classes(\"h-[20rem]\")\n\n # \u4f7f\u7528pyecharts\u914d\u7f6e\u56fe\u8868\n @ds.ui_echarts\n def bar2(data: pd.DataFrame):\n data = data.groupby(\"name\").agg({\"idc1\": \"sum\", \"idc2\": \"sum\"}).reset_index()\n\n return (\n Bar()\n .add_xaxis(data[\"name\"].tolist())\n .add_yaxis(\"idc1\", data[\"idc1\"].tolist())\n .add_yaxis(\"idc2\", data[\"idc2\"].tolist())\n )\n\n bar2.classes(\"h-[20rem]\")\n\n # \u7ed1\u5b9a\u70b9\u51fb\u4e8b\u4ef6\uff0c\u5373\u53ef\u5b9e\u73b0\u8df3\u8f6c\n @bar2.on_chart_click\n def _(e: rxui.echarts.EChartsMouseEventArguments):\n ui.open(f\"/details/{e.name}\", new_tab=True)\n\n\n# \u5229\u7528\u54cd\u5e94\u5f0f\u673a\u5236\uff0c\u4f60\u53ef\u4ee5\u968f\u610f\u7ec4\u5408\u539f\u751f nicegui \u7ec4\u4ef6\nlabel_a1_total = ui.label(\"\")\n\n\n# \u5f53 ds \u6709\u53d8\u5316\uff0c\u90fd\u4f1a\u89e6\u53d1\u6b64\u51fd\u6570\n@effect\ndef _():\n # filtered_data \u4e3a\u8fc7\u6ee4\u540e\u7684 DataFrame\n df = ds.filtered_data\n total = df[df[\"cat\"] == \"a1\"][\"idc1\"].sum()\n label_a1_total.text = f\"idc1 total(cat==a1):{total}\"\n\n\n# \u4f60\u4e5f\u53ef\u4ee5\u4f7f\u7528 `effect_refreshable`,\u4f46\u9700\u8981\u6ce8\u610f\u51fd\u6570\u4e2d\u7684\u7ec4\u4ef6\u6bcf\u6b21\u90fd\u88ab\u91cd\u5efa\n@effect_refreshable\ndef _():\n df = ds.filtered_data\n total = df[df[\"cat\"] == \"a2\"][\"idc1\"].sum()\n ui.label(f\"idc1 total(cat==a2):{total}\")\n\n\n# \u5f53\u70b9\u51fb\u56fe\u8868\u7cfb\u5217\u65f6\uff0c\u8df3\u8f6c\u7684\u9875\u9762\n@ui.page(\"/details/{name}\")\ndef details_page(name: str):\n ui.label(\"This table data will not change\")\n ui.aggrid.from_pandas(ds.data.query(f'name==\"{name}\"'))\n\n ui.label(\"This table will change when the homepage data changes. \")\n\n @bi.data_source\n def new_ds():\n return ds.filtered_data[[\"name\", \"idc1\", \"idc2\"]]\n\n new_ds.ui_aggrid()\n\n\nui.run()\n```\n\n---\n\n#### `bi.data_source`\n\u6570\u636e\u6e90\u662f BI \u6a21\u5757\u7684\u6838\u5fc3\u6982\u5ff5\uff0c\u6240\u6709\u6570\u636e\u7684\u8054\u52a8\u57fa\u4e8e\u6b64\u5c55\u5f00\u3002\u5f53\u524d\u7248\u672c(0.4.3)\u4e2d\uff0c\u6709\u4e24\u79cd\u521b\u5efa\u6570\u636e\u6e90\u7684\u65b9\u5f0f\n\n\u63a5\u6536 `pandas` \u7684 `DataFrame`:\n```python\nfrom nicegui import ui\nfrom ex4nicegui import bi\nimport pandas as pd\n\ndf = pd.DataFrame(\n {\n \"name\": list(\"aabcdf\"),\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\n \"value\": range(6),\n }\n)\n\nds = bi.data_source(df)\n```\n\n---\n\u6709\u65f6\u5019\uff0c\u6211\u4eec\u5e0c\u671b\u57fa\u4e8e\u53e6\u4e00\u4e2a\u6570\u636e\u6e90\u521b\u5efa\u65b0\u7684\u6570\u636e\u6e90\uff0c\u6b64\u65f6\u53ef\u4ee5\u4f7f\u7528\u88c5\u9970\u5668\u521b\u5efa\u8054\u52a8\u6570\u636e\u6e90:\n```python\ndf = pd.DataFrame(\n {\n \"name\": list(\"aabcdf\"),\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\n \"value\": range(6),\n }\n)\n\nds = bi.data_source(df)\n\n@bi.data_source\ndef new_ds():\n # df is pd.DataFrame \n df = ds.filtered_data\n df=df.copy()\n df['value'] = df['value'] * 100\n return df\n\nds.ui_select('name')\nnew_ds.ui_aggrid()\n\n```\n\n\u6ce8\u610f\uff0c\u7531\u4e8e `new_ds` \u4e2d\u4f7f\u7528\u4e86 `ds.filtered_data` \uff0c\u56e0\u6b64 `ds` \u7684\u53d8\u52a8\u4f1a\u89e6\u53d1 `new_ds` \u7684\u8054\u52a8\u53d8\u5316\uff0c\u4ece\u800c\u5bfc\u81f4 `new_ds` \u521b\u5efa\u7684\u8868\u683c\u7ec4\u4ef6\u4ea7\u751f\u53d8\u5316\n\n---\n\u901a\u8fc7 `ds.remove_filters` \u65b9\u6cd5\uff0c\u79fb\u9664\u6240\u6709\u7b5b\u9009\u72b6\u6001:\n```python\nds = bi.data_source(df)\n\ndef on_remove_filters():\n ds.remove_filters()\n\nui.button(\"remove all filters\", on_click=on_remove_filters)\n\nds.ui_select(\"name\")\nds.ui_aggrid()\n```\n---\n\n\u901a\u8fc7 `ds.reload` \u65b9\u6cd5\uff0c\u91cd\u8bbe\u6570\u636e\u6e90:\n```python\n\ndf = pd.DataFrame(\n {\n \"name\": list(\"aabcdf\"),\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\n \"value\": range(6),\n }\n)\n\nnew_df = pd.DataFrame(\n {\n \"name\": list(\"xxyyds\"),\n \"cls\": [\"cla1\", \"cla2\", \"cla3\", \"cla3\", \"cla3\", None],\n \"value\": range(100, 106),\n }\n)\n\nds = bi.data_source(df)\n\ndef on_remove_filters():\n ds.reload(new_df)\n\nui.button(\"reload data\", on_click=on_remove_filters)\n\nds.ui_select(\"name\")\nds.ui_aggrid()\n```\n\n---\n#### ui_select\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import bi\nimport pandas as pd\n\ndf = pd.DataFrame(\n {\n \"name\": list(\"aabcdf\"),\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\n \"value\": range(6),\n }\n)\n\nds = bi.data_source(df)\n\nds.ui_select(\"name\")\n```\n\n\u7b2c\u4e00\u4e2a\u53c2\u6570 column \u6307\u5b9a\u6570\u636e\u6e90\u7684\u5217\u540d\n\n---\n\u901a\u8fc7\u53c2\u6570 `sort_options` \u8bbe\u7f6e\u9009\u9879\u987a\u5e8f:\n```python\nds.ui_select(\"name\", sort_options={\"value\": \"desc\", \"name\": \"asc\"})\n\n```\n\n---\n\u53c2\u6570 `exclude_null_value` \u8bbe\u7f6e\u662f\u5426\u6392\u9664\u7a7a\u503c:\n```python\ndf = pd.DataFrame(\n {\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\n }\n)\n\nds = bi.data_source(df)\nds.ui_select(\"cls\", exclude_null_value=True)\n```\n\n---\n\n\u4f60\u53ef\u4ee5\u901a\u8fc7\u5173\u952e\u5b57\u53c2\u6570\uff0c\u8bbe\u7f6e\u539f\u751f nicegui select \u7ec4\u4ef6\u7684\u53c2\u6570.\n\n\u901a\u8fc7 value \u5c5e\u6027\uff0c\u8bbe\u7f6e\u9ed8\u8ba4\u503c:\n```python\nds.ui_select(\"cls\",value=['c1','c2'])\nds.ui_select(\"cls\",multiple=False,value='c1')\n\n```\n\n\u591a\u9009\u65f6(\u53c2\u6570 `multiple` \u9ed8\u8ba4\u4e3a True)\uff0c`value` \u9700\u8981\u6307\u5b9a\u4e3a list\n\n\u5355\u9009\u65f6\uff0c`value` \u8bbe\u7f6e\u4e3a\u975e list\n\n---\n\n#### ui_table\n\n\u8868\u683c\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import bi\nimport pandas as pd\n\ndata = pd.DataFrame({\"name\": [\"f\", \"a\", \"c\", \"b\"], \"age\": [1, 2, 3, 1]})\nds = bi.data_source(data)\n\nds.ui_table(\n columns=[\n {\"label\": \"new colA\", \"field\": \"colA\", \"sortable\": True},\n ]\n)\n\n```\n\n- columns \u4e0e nicegui `ui.table` \u4e00\u81f4\u3002\u5176\u4e2d \u952e\u503c `field` \u5bf9\u5e94\u6570\u636e\u6e90\u7684\u5217\u540d\uff0c\u5982\u679c\u4e0d\u5b58\u5728\uff0c\u5219\u8be5\u914d\u7f6e\u4e0d\u4f1a\u751f\u6548\n- rows \u53c2\u6570\u4e0d\u4f1a\u751f\u6548\u3002\u56e0\u4e3a\u8868\u683c\u7684\u6570\u636e\u6e90\u59cb\u7ec8\u7531 data source \u63a7\u5236\n\n---\n\n#### ui_aggrid\n\n\n```python\nfrom nicegui import ui\nfrom ex4nicegui import bi\nimport pandas as pd\n\ndata = pd.DataFrame(\n {\n \"colA\": list(\"abcde\"),\n \"colB\": [f\"n{idx}\" for idx in range(5)],\n \"colC\": list(range(5)),\n }\n)\ndf = pd.DataFrame(data)\n\nsource = bi.data_source(df)\n\nsource.ui_aggrid(\n options={\n \"columnDefs\": [\n {\"headerName\": \"xx\", \"field\": \"no exists\"},\n {\"headerName\": \"new colA\", \"field\": \"colA\"},\n {\n \"field\": \"colC\",\n \"cellClassRules\": {\n \"bg-red-300\": \"x < 3\",\n \"bg-green-300\": \"x >= 3\",\n },\n },\n ],\n \"rowData\": [{\"colX\": [1, 2, 3, 4, 5]}],\n }\n)\n```\n\n- \u53c2\u6570 options \u4e0e nicegui `ui.aggrid` \u4e00\u81f4\u3002\u5176\u4e2d `columnDefs` \u4e2d\u7684\u952e\u503c `field` \u5bf9\u5e94\u6570\u636e\u6e90\u7684\u5217\u540d\uff0c\u5982\u679c\u4e0d\u5b58\u5728\uff0c\u5219\u8be5\u914d\u7f6e\u4e0d\u4f1a\u751f\u6548\n- `rowData` \u952e\u503c\u4e0d\u4f1a\u751f\u6548\u3002\u56e0\u4e3a\u8868\u683c\u7684\u6570\u636e\u6e90\u59cb\u7ec8\u7531 data source \u63a7\u5236\n\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Extension library based on nicegui, providing data responsive,BI functionality modules",
"version": "0.8.4",
"project_urls": {
"Homepage": "https://github.com/CrystalWindSnake/ex4nicegui",
"Repository": "https://github.com/CrystalWindSnake/ex4nicegui"
},
"split_keywords": [
"nicegui",
" ex4nicegui",
" webui"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2901960c6cf1ed6a880e088f384f6310864b9208d62bfc4732c4a3c43d80ba0d",
"md5": "03034085dc5dac00d8606b9ea4d68086",
"sha256": "bc47ec634c89abf8ec3c2d59a7df6128ce1e3b7131442d492ec7aaaf932539c3"
},
"downloads": -1,
"filename": "ex4nicegui-0.8.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "03034085dc5dac00d8606b9ea4d68086",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 381306,
"upload_time": "2024-11-23T12:42:01",
"upload_time_iso_8601": "2024-11-23T12:42:01.574496Z",
"url": "https://files.pythonhosted.org/packages/29/01/960c6cf1ed6a880e088f384f6310864b9208d62bfc4732c4a3c43d80ba0d/ex4nicegui-0.8.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "361bbfe44fa85eadaf632248c42ba2e3b2980c478cba351913b4919ec58a36e2",
"md5": "59bf171f9d7524dbe69bdc095ce9ca60",
"sha256": "deea0c61fa0b45dd79ca744495a482a065e0257092a33b7c2b14d61e16b1b977"
},
"downloads": -1,
"filename": "ex4nicegui-0.8.4.tar.gz",
"has_sig": false,
"md5_digest": "59bf171f9d7524dbe69bdc095ce9ca60",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 318659,
"upload_time": "2024-11-23T12:42:03",
"upload_time_iso_8601": "2024-11-23T12:42:03.648086Z",
"url": "https://files.pythonhosted.org/packages/36/1b/bfe44fa85eadaf632248c42ba2e3b2980c478cba351913b4919ec58a36e2/ex4nicegui-0.8.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-23 12:42:03",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "CrystalWindSnake",
"github_project": "ex4nicegui",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "ex4nicegui"
}