ex4nicegui 0.2.18__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/PKG-INFO +1 -1
- ex4nicegui-0.3.1/README.md +209 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/__init__.py +1 -1
- ex4nicegui-0.3.1/ex4nicegui/bi/__init__.py +13 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/dataSource.py +203 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/dataSourceFacade.py +148 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/containers.py +13 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/layouts.py +28 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/models.py +55 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/text.py +33 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_aggrid.py +46 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_date_picker.js +35 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_date_picker.py +77 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_echarts.py +72 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_radio.py +60 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_range.py +119 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_select.py +105 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/elements/ui_slider.py +59 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/index.py +76 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/protocols.py +158 -0
- ex4nicegui-0.3.1/ex4nicegui/bi/types.py +13 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/EChartsComponent/ECharts.js +1 -2
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/EChartsComponent/ECharts.py +1 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/base.py +12 -1
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/button.py +1 -1
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/drawer.py +1 -2
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/echarts.py +1 -1
- ex4nicegui-0.3.1/ex4nicegui/utils/__init__.py +0 -0
- ex4nicegui-0.3.1/ex4nicegui/utils/clientScope.py +42 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/utils/signals.py +54 -5
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui.egg-info/PKG-INFO +1 -1
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui.egg-info/SOURCES.txt +20 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui.egg-info/requires.txt +1 -1
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/setup.py +1 -1
- ex4nicegui-0.2.18/README.md +0 -78
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/LICENSE +0 -0
- {ex4nicegui-0.2.18/ex4nicegui/reactive/EChartsComponent → ex4nicegui-0.3.1/ex4nicegui/bi/elements}/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/experimental_/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/experimental_/gridLayout/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/experimental_/gridLayout/index.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/gridFlex/GridFlex.js +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/gridFlex/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/gridFlex/gridFlex.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/gridFlex/utils.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/rxFlex/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/rxFlex/index.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/layout/rxFlex/types.py +0 -0
- {ex4nicegui-0.2.18/ex4nicegui/reactive/UseDraggable → ex4nicegui-0.3.1/ex4nicegui/reactive/EChartsComponent}/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/UseDraggable/UseDraggable.js +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/UseDraggable/UseDraggable.py +0 -0
- {ex4nicegui-0.2.18/ex4nicegui/reactive/dropZone → ex4nicegui-0.3.1/ex4nicegui/reactive/UseDraggable}/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/__index.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/drawer.py +0 -0
- {ex4nicegui-0.2.18/ex4nicegui/reactive/useMouse → ex4nicegui-0.3.1/ex4nicegui/reactive/dropZone}/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/dropZone/dropZone.js +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/dropZone/dropZone.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/fileWatcher.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/local_file_picker.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/aggrid.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/card.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/checkbox.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/color_picker.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/column.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/date.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/expansion.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/grid.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/html.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/icon.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/image.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/input.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/label.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/number.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/radio.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/row.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/select.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/slider.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/switch.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/table.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/textarea.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/upload.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/officials/utils.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/q_pagination.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/rxui.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/useMouse/UseMouse.js +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/useMouse/UseMouse.py +0 -0
- {ex4nicegui-0.2.18/ex4nicegui/utils → ex4nicegui-0.3.1/ex4nicegui/reactive/useMouse}/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/reactive/usePagination.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/tools/__init__.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/tools/debug.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui/utils/common.py +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui.egg-info/dependency_links.txt +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui.egg-info/not-zip-safe +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/ex4nicegui.egg-info/top_level.txt +0 -0
- {ex4nicegui-0.2.18 → ex4nicegui-0.3.1}/setup.cfg +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# ex4nicegui
|
|
2
|
+
[ENGLISH README](./README.en.md)
|
|
3
|
+
|
|
4
|
+
- [教程](#教程)
|
|
5
|
+
- [安装](#-安装)
|
|
6
|
+
- [使用](#-使用)
|
|
7
|
+
- [功能](#-功能)
|
|
8
|
+
- [BI 模块](#bi-模块)
|
|
9
|
+
|
|
10
|
+
对 [nicegui](https://github.com/zauberzeug/nicegui) 做的扩展库。内置响应式组件,完全实现数据响应式界面编程。
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 教程
|
|
14
|
+
[头条文章-秒杀官方实现,python界面库,去掉90%事件代码的nicegui](https://www.toutiao.com/item/7253786340574265860/)
|
|
15
|
+
|
|
16
|
+
[微信公众号-秒杀官方实现,python界面库,去掉90%事件代码的nicegui](https://mp.weixin.qq.com/s?__biz=MzUzNDk1MTc5Mw==&mid=2247486796&idx=1&sn=457ed6fb9d6a25145f7704d5197d670d&chksm=fa8daf52cdfa2644bede50ae7f2551162ecaedecafec231ee4ce8f28775a599f8669ecf06af1#rd)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## 📦 安装
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
pip install ex4nicegui -U
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🦄 使用
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from nicegui import ui
|
|
29
|
+
from ex4nicegui import ref_computed, effect, to_ref
|
|
30
|
+
from ex4nicegui.reactive import rxui
|
|
31
|
+
|
|
32
|
+
# 定义响应式数据
|
|
33
|
+
r_input = to_ref("")
|
|
34
|
+
|
|
35
|
+
# 按照 nicegui 使用方式传入响应式数据即可
|
|
36
|
+
rxui.input(value=r_input)
|
|
37
|
+
rxui.label(r_input)
|
|
38
|
+
|
|
39
|
+
ui.run()
|
|
40
|
+
```
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### 提供 echarts 图表组件
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from nicegui import ui
|
|
48
|
+
from ex4nicegui import ref_computed, effect, to_ref
|
|
49
|
+
from ex4nicegui.reactive import rxui
|
|
50
|
+
|
|
51
|
+
r_input = to_ref("")
|
|
52
|
+
|
|
53
|
+
# ref_computed 创建只读响应式变量
|
|
54
|
+
# 函数中使用任意其他响应式变量,会自动关联
|
|
55
|
+
@ref_computed
|
|
56
|
+
def cp_echarts_opts():
|
|
57
|
+
return {
|
|
58
|
+
"title": {"text": r_input.value}, #字典中使用任意响应式变量,通过 .value 获取值
|
|
59
|
+
"xAxis": {
|
|
60
|
+
"type": "category",
|
|
61
|
+
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
62
|
+
},
|
|
63
|
+
"yAxis": {"type": "value"},
|
|
64
|
+
"series": [
|
|
65
|
+
{
|
|
66
|
+
"data": [120, 200, 150, 80, 70, 110, 130],
|
|
67
|
+
"type": "bar",
|
|
68
|
+
"showBackground": True,
|
|
69
|
+
"backgroundStyle": {"color": "rgba(180, 180, 180, 0.2)"},
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
input = rxui.input("输入内容,图表标题会同步", value=r_input)
|
|
75
|
+
# 通过响应式组件对象的 element 属性,获取原生 nicegui 组件对象
|
|
76
|
+
input.element.classes("w-full")
|
|
77
|
+
|
|
78
|
+
rxui.echarts(cp_echarts_opts)
|
|
79
|
+
|
|
80
|
+
ui.run()
|
|
81
|
+
```
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
## BI 模块
|
|
87
|
+
|
|
88
|
+
以最精简的 apis 创建可交互的数据可视化报表
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from nicegui import ui
|
|
94
|
+
import pandas as pd
|
|
95
|
+
import numpy as np
|
|
96
|
+
from ex4nicegui import bi
|
|
97
|
+
from ex4nicegui import effect, effect_refreshable
|
|
98
|
+
from pyecharts.charts import Bar
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# data ready
|
|
102
|
+
def gen_data():
|
|
103
|
+
np.random.seed(265)
|
|
104
|
+
field1 = ["a1", "a2", "a3", "a4"]
|
|
105
|
+
field2 = [f"name{i}" for i in range(1, 11)]
|
|
106
|
+
df = (
|
|
107
|
+
pd.MultiIndex.from_product([field1, field2], names=["cat", "name"])
|
|
108
|
+
.to_frame()
|
|
109
|
+
.reset_index(drop=True)
|
|
110
|
+
)
|
|
111
|
+
df[["idc1", "idc2"]] = np.random.randint(50, 1000, size=(len(df), 2))
|
|
112
|
+
return df
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
df = gen_data()
|
|
116
|
+
|
|
117
|
+
# 创建数据源
|
|
118
|
+
ds = bi.data_source(df)
|
|
119
|
+
|
|
120
|
+
# ui
|
|
121
|
+
ui.query(".nicegui-content").classes("items-stretch no-wrap")
|
|
122
|
+
|
|
123
|
+
with ui.row().classes("justify-evenly"):
|
|
124
|
+
# 基于数据源 `ds` 创建界面组件
|
|
125
|
+
ds.ui_select("cat").classes("min-w-[10rem]")
|
|
126
|
+
ds.ui_select("name").classes("min-w-[10rem]")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
with ui.grid(columns=2):
|
|
130
|
+
# 使用字典配置图表
|
|
131
|
+
@ds.ui_echarts
|
|
132
|
+
def bar1(data: pd.DataFrame):
|
|
133
|
+
data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
"xAxis": {"type": "value"},
|
|
137
|
+
"yAxis": {
|
|
138
|
+
"type": "category",
|
|
139
|
+
"data": data["name"].tolist(),
|
|
140
|
+
"inverse": True,
|
|
141
|
+
},
|
|
142
|
+
"legend": {"textStyle": {"color": "gray"}},
|
|
143
|
+
"series": [
|
|
144
|
+
{"type": "bar", "name": "idc1", "data": data["idc1"].tolist()},
|
|
145
|
+
{"type": "bar", "name": "idc2", "data": data["idc2"].tolist()},
|
|
146
|
+
],
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
bar1.classes("h-[20rem]")
|
|
150
|
+
|
|
151
|
+
# 使用pyecharts配置图表
|
|
152
|
+
@ds.ui_echarts
|
|
153
|
+
def bar2(data: pd.DataFrame):
|
|
154
|
+
data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
Bar()
|
|
158
|
+
.add_xaxis(data["name"].tolist())
|
|
159
|
+
.add_yaxis("idc1", data["idc1"].tolist())
|
|
160
|
+
.add_yaxis("idc2", data["idc2"].tolist())
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
bar2.classes("h-[20rem]")
|
|
164
|
+
|
|
165
|
+
# 绑定点击事件,即可实现跳转
|
|
166
|
+
@bar2.on_chart_click
|
|
167
|
+
def _(e):
|
|
168
|
+
ui.open(f"/details/{e.name}", new_tab=True)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# 利用响应式机制,你可以随意组合原生 nicegui 组件
|
|
172
|
+
label_a1_total = ui.label("")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# 当 ds 有变化,都会触发此函数
|
|
176
|
+
@effect
|
|
177
|
+
def _():
|
|
178
|
+
# filtered_data 为过滤后的 DataFrame
|
|
179
|
+
df = ds.filtered_data
|
|
180
|
+
total = df[df["cat"] == "a1"]["idc1"].sum()
|
|
181
|
+
label_a1_total.text = f"idc1 total(cat==a1):{total}"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# 你也可以使用 `effect_refreshable`,但需要注意函数中的组件每次都被重建
|
|
185
|
+
@effect_refreshable
|
|
186
|
+
def _():
|
|
187
|
+
df = ds.filtered_data
|
|
188
|
+
total = df[df["cat"] == "a2"]["idc1"].sum()
|
|
189
|
+
ui.label(f"idc1 total(cat==a2):{total}")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# 当点击图表系列时,跳转的页面
|
|
193
|
+
@ui.page("/details/{name}")
|
|
194
|
+
def details_page(name: str):
|
|
195
|
+
ui.label("This table data will not change")
|
|
196
|
+
ui.aggrid.from_pandas(ds.data.query(f'name=="{name}"'))
|
|
197
|
+
|
|
198
|
+
ui.label("This table will change when the homepage data changes. ")
|
|
199
|
+
|
|
200
|
+
@bi.data_source
|
|
201
|
+
def new_ds():
|
|
202
|
+
return ds.filtered_data[["name", "idc1", "idc2"]]
|
|
203
|
+
|
|
204
|
+
new_ds.ui_aggrid()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
ui.run()
|
|
208
|
+
```
|
|
209
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .index import data_source
|
|
2
|
+
from .elements.text import ui_title, ui_header, ui_subheader
|
|
3
|
+
from .elements.containers import ui_cols
|
|
4
|
+
from .elements.layouts import ui_left_drawer
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"data_source",
|
|
8
|
+
"ui_left_drawer",
|
|
9
|
+
"ui_title",
|
|
10
|
+
"ui_header",
|
|
11
|
+
"ui_subheader",
|
|
12
|
+
"ui_cols",
|
|
13
|
+
]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from typing import Callable, Dict, List, Optional, Set, cast
|
|
2
|
+
from ex4nicegui import to_ref, ref_computed, on
|
|
3
|
+
from nicegui import globals as ng_globals, Client, ui
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from . import types
|
|
7
|
+
from .protocols import IDataSourceAble
|
|
8
|
+
|
|
9
|
+
_TComponentUpdateCallback = Callable[[], None]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Filter:
|
|
14
|
+
callback: types._TFilterCallback
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ComponentInfoKey:
|
|
19
|
+
client_id: types._TNgClientID
|
|
20
|
+
element_id: types._TElementID
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ComponentInfo:
|
|
25
|
+
key: ComponentInfoKey
|
|
26
|
+
update_callback: Optional[_TComponentUpdateCallback] = None
|
|
27
|
+
filter: Optional[Filter] = None
|
|
28
|
+
exclude_keys: Set[ComponentInfoKey] = field(default_factory=set)
|
|
29
|
+
|
|
30
|
+
def __eq__(self, other):
|
|
31
|
+
return isinstance(other, ComponentInfo) and self.key == other.key
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ComponentMap:
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
self._client_map: Dict[
|
|
37
|
+
types._TNgClientID, Dict[types._TElementID, ComponentInfo]
|
|
38
|
+
] = {}
|
|
39
|
+
|
|
40
|
+
def has_client_record(self, client_id: types._TNgClientID):
|
|
41
|
+
return client_id in self._client_map
|
|
42
|
+
|
|
43
|
+
def add_info(self, info: ComponentInfo):
|
|
44
|
+
client_id = info.key.client_id
|
|
45
|
+
element_id = info.key.element_id
|
|
46
|
+
|
|
47
|
+
if client_id not in self._client_map:
|
|
48
|
+
self._client_map[client_id] = {element_id: info}
|
|
49
|
+
|
|
50
|
+
element_map = self._client_map[client_id]
|
|
51
|
+
|
|
52
|
+
if element_id not in element_map:
|
|
53
|
+
element_map[element_id] = info
|
|
54
|
+
|
|
55
|
+
def remove_client(self, client_id: types._TNgClientID):
|
|
56
|
+
if client_id in self._client_map:
|
|
57
|
+
del self._client_map[client_id]
|
|
58
|
+
|
|
59
|
+
def has_record(self, key: ComponentInfoKey):
|
|
60
|
+
return (
|
|
61
|
+
key.client_id in self._client_map
|
|
62
|
+
and key.element_id in self._client_map[key.client_id]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def set_filter(
|
|
66
|
+
self,
|
|
67
|
+
client_id: types._TNgClientID,
|
|
68
|
+
element_id: types._TElementID,
|
|
69
|
+
filter: Filter,
|
|
70
|
+
):
|
|
71
|
+
self._client_map[client_id][element_id].filter = filter
|
|
72
|
+
|
|
73
|
+
def get_all_info(self):
|
|
74
|
+
return (
|
|
75
|
+
info for ele_map in self._client_map.values() for info in ele_map.values()
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def get_info(self, key: ComponentInfoKey) -> ComponentInfo:
|
|
79
|
+
return self._client_map[key.client_id][key.element_id]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DataSource:
|
|
83
|
+
_global_id_count: types._TDataSourceId = 0
|
|
84
|
+
|
|
85
|
+
def __init__(self, data: IDataSourceAble) -> None:
|
|
86
|
+
DataSource._global_id_count += 1
|
|
87
|
+
self.__id = DataSource._global_id_count
|
|
88
|
+
|
|
89
|
+
self._idataSource = data
|
|
90
|
+
|
|
91
|
+
data_fn = lambda: data.get_data()
|
|
92
|
+
data_fn = ref_computed(data_fn)
|
|
93
|
+
|
|
94
|
+
self.__filters = to_ref(cast(List[Filter], []))
|
|
95
|
+
|
|
96
|
+
@ref_computed
|
|
97
|
+
def apply_filters():
|
|
98
|
+
df = data_fn.value
|
|
99
|
+
for f in self.__filters.value:
|
|
100
|
+
df = f.callback(df)
|
|
101
|
+
|
|
102
|
+
return df
|
|
103
|
+
|
|
104
|
+
self.__filtered_data = apply_filters
|
|
105
|
+
|
|
106
|
+
self.__data = data_fn
|
|
107
|
+
self._component_map = ComponentMap()
|
|
108
|
+
|
|
109
|
+
@on(data_fn)
|
|
110
|
+
def _():
|
|
111
|
+
self.__notify_update()
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def data(self):
|
|
115
|
+
return self.__data.value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def filtered_data(self):
|
|
119
|
+
return self.__filtered_data.value
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def id(self):
|
|
123
|
+
return self.__id
|
|
124
|
+
|
|
125
|
+
def get_component_info_key(self, element_id: types._TElementID):
|
|
126
|
+
client_id = ng_globals.get_client().id
|
|
127
|
+
return ComponentInfoKey(client_id, element_id)
|
|
128
|
+
|
|
129
|
+
def get_filtered_data(self, element: ui.element):
|
|
130
|
+
data = self._idataSource.get_data()
|
|
131
|
+
|
|
132
|
+
# note:must be use client id of the element
|
|
133
|
+
key = ComponentInfoKey(element.client.id, element.id)
|
|
134
|
+
current_info = self._component_map.get_info(key)
|
|
135
|
+
|
|
136
|
+
filters = [
|
|
137
|
+
info.filter.callback
|
|
138
|
+
for info in self._component_map.get_all_info()
|
|
139
|
+
if current_info.key != info.key
|
|
140
|
+
and (info.key not in current_info.exclude_keys)
|
|
141
|
+
and info.filter
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
return self._idataSource.apply_filters(data, filters)
|
|
145
|
+
|
|
146
|
+
def _register_component(
|
|
147
|
+
self,
|
|
148
|
+
element_id: types._TElementID,
|
|
149
|
+
update_callback: Optional[_TComponentUpdateCallback] = None,
|
|
150
|
+
):
|
|
151
|
+
ng_client = ng_globals.get_client()
|
|
152
|
+
client_id = ng_client.id
|
|
153
|
+
|
|
154
|
+
if not self._component_map.has_client_record(client_id):
|
|
155
|
+
|
|
156
|
+
@ng_client.on_disconnect
|
|
157
|
+
def _(e: Client):
|
|
158
|
+
if not e.shared:
|
|
159
|
+
self._component_map.remove_client(e.id)
|
|
160
|
+
|
|
161
|
+
info = ComponentInfo(ComponentInfoKey(client_id, element_id), update_callback)
|
|
162
|
+
self._component_map.add_info(info)
|
|
163
|
+
|
|
164
|
+
return info
|
|
165
|
+
|
|
166
|
+
def send_filter(self, element_id: types._TElementID, filter: Filter):
|
|
167
|
+
client_id = ng_globals.get_client().id
|
|
168
|
+
key = ComponentInfoKey(client_id, element_id)
|
|
169
|
+
|
|
170
|
+
if not self._component_map.has_record(key):
|
|
171
|
+
raise ValueError("element not register")
|
|
172
|
+
|
|
173
|
+
self._component_map.set_filter(client_id, element_id, filter)
|
|
174
|
+
|
|
175
|
+
trigger_info = self._component_map.get_info(
|
|
176
|
+
ComponentInfoKey(client_id, element_id)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
self.__notify_update(trigger_info)
|
|
180
|
+
self.__filters.value = [
|
|
181
|
+
info.filter for info in self._component_map.get_all_info() if info.filter
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def __notify_update(self, trigger_info: Optional[ComponentInfo] = None):
|
|
187
|
+
# nodify every component
|
|
188
|
+
for current_info in self._component_map.get_all_info():
|
|
189
|
+
# not nodify the self triggering
|
|
190
|
+
if trigger_info and current_info.key == trigger_info.key:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
update_callback = current_info.update_callback
|
|
194
|
+
if update_callback:
|
|
195
|
+
update_callback()
|
|
196
|
+
|
|
197
|
+
def on_source_update(
|
|
198
|
+
self,
|
|
199
|
+
element_id: types._TElementID,
|
|
200
|
+
callback: _TComponentUpdateCallback,
|
|
201
|
+
):
|
|
202
|
+
key = self.get_component_info_key(element_id)
|
|
203
|
+
self._component_map.get_info(key).update_callback = callback
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Callable, Dict, TypeVar, Generic, Union, cast
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
from .dataSource import DataSource, Filter
|
|
5
|
+
from . import types as bi_types
|
|
6
|
+
from .elements.ui_select import ui_select
|
|
7
|
+
from .elements.ui_radio import ui_radio
|
|
8
|
+
from .elements.ui_slider import ui_slider
|
|
9
|
+
from .elements.ui_range import ui_range
|
|
10
|
+
from .elements.ui_echarts import ui_echarts
|
|
11
|
+
from .elements.ui_aggrid import ui_aggrid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_TData = TypeVar("_TData")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataSourceFacade(Generic[_TData]):
|
|
18
|
+
def __init__(self, ds: DataSource) -> None:
|
|
19
|
+
self._dataSource = ds
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def data(self) -> _TData:
|
|
23
|
+
"""Data without any filtering"""
|
|
24
|
+
return cast(_TData, self._dataSource.data)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def filtered_data(self) -> _TData:
|
|
28
|
+
"""Data after filtering"""
|
|
29
|
+
return cast(_TData, self._dataSource.filtered_data)
|
|
30
|
+
|
|
31
|
+
def ui_select(self, column: str, *, clearable=True, multiple=True, **kwargs):
|
|
32
|
+
"""
|
|
33
|
+
Creates a user interface select box.
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
column (str): The column name of the data source.
|
|
37
|
+
clearable (bool, optional): Whether to allow clearing the content of the select box. Default is True.
|
|
38
|
+
multiple (bool, optional): Whether to allow multiple selections.
|
|
39
|
+
**kwargs: Additional optional parameters that will be passed to the ui.select constructor.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
SelectResult: An instance of a user interface select box.
|
|
43
|
+
"""
|
|
44
|
+
kws = {key: value for key, value in locals().items() if key not in ("kwargs")}
|
|
45
|
+
kws.update(kwargs)
|
|
46
|
+
return ui_select(**kws)
|
|
47
|
+
|
|
48
|
+
def ui_aggrid(self, **kwargs):
|
|
49
|
+
"""
|
|
50
|
+
Creates aggrid table.
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
**kwargs: Additional optional parameters that will be passed to the ui.aggrid constructor.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
ui.aggrid: aggrid table.
|
|
57
|
+
"""
|
|
58
|
+
return ui_aggrid(self, **kwargs)
|
|
59
|
+
|
|
60
|
+
def ui_radio(self, column: str, **kwargs):
|
|
61
|
+
"""
|
|
62
|
+
Creates radio Selection.
|
|
63
|
+
|
|
64
|
+
Parameters:
|
|
65
|
+
column (str): The column name of the data source.
|
|
66
|
+
**kwargs: Additional optional parameters that will be passed to the ui.radio constructor.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
RadioResult: An radio Selection.
|
|
70
|
+
"""
|
|
71
|
+
kws = {key: value for key, value in locals().items() if key not in ("kwargs")}
|
|
72
|
+
kws.update(kwargs)
|
|
73
|
+
return ui_radio(**kws)
|
|
74
|
+
|
|
75
|
+
def ui_slider(self, column: str, **kwargs):
|
|
76
|
+
"""
|
|
77
|
+
Creates Slider.
|
|
78
|
+
|
|
79
|
+
Parameters:
|
|
80
|
+
column (str): The column name of the data source.
|
|
81
|
+
**kwargs: Additional optional parameters that will be passed to the ui.slider constructor.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
ui.radio: An Slider.
|
|
85
|
+
"""
|
|
86
|
+
kws = {key: value for key, value in locals().items() if key not in ("kwargs")}
|
|
87
|
+
kws.update(kwargs)
|
|
88
|
+
return ui_slider(**kws)
|
|
89
|
+
|
|
90
|
+
def ui_range(self, column: str, **kwargs):
|
|
91
|
+
"""
|
|
92
|
+
Creates Range.
|
|
93
|
+
|
|
94
|
+
Parameters:
|
|
95
|
+
column (str): The column name of the data source.
|
|
96
|
+
**kwargs: Additional optional parameters that will be passed to the ui.slider constructor.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
QRange: An Range.
|
|
100
|
+
"""
|
|
101
|
+
kws = {key: value for key, value in locals().items() if key not in ("kwargs")}
|
|
102
|
+
kws.update(kwargs)
|
|
103
|
+
return ui_range(**kws)
|
|
104
|
+
|
|
105
|
+
def ui_echarts(
|
|
106
|
+
self, fn: Callable[[Any], Union[Dict, "pyecharts.Base"]] # pyright: ignore
|
|
107
|
+
):
|
|
108
|
+
"""Create charts
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
fn (Callable[[Any], Union[Dict, "pyecharts.Base"]]): builder function.
|
|
112
|
+
|
|
113
|
+
## Examples
|
|
114
|
+
|
|
115
|
+
Support pyecharts
|
|
116
|
+
|
|
117
|
+
```py
|
|
118
|
+
import pandas as pd
|
|
119
|
+
from ex4nicegui import bi
|
|
120
|
+
from pyecharts.charts import Bar
|
|
121
|
+
|
|
122
|
+
df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
|
|
123
|
+
ds = bi.data_source(df)
|
|
124
|
+
|
|
125
|
+
@ds.ui_echarts
|
|
126
|
+
def bar(data: pd.DataFrame):
|
|
127
|
+
c = (
|
|
128
|
+
Bar()
|
|
129
|
+
.add_xaxis(data["name"].tolist())
|
|
130
|
+
.add_yaxis("value", data["value"].tolist())
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return c
|
|
134
|
+
|
|
135
|
+
bar.classes("h-[20rem]")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
return ui_echarts(self, fn)
|
|
140
|
+
|
|
141
|
+
def send_filter(
|
|
142
|
+
self, element: ui.element, filter: bi_types._TFilterCallback[_TData]
|
|
143
|
+
):
|
|
144
|
+
ele_id = element.id
|
|
145
|
+
key = self._dataSource.get_component_info_key(ele_id)
|
|
146
|
+
if not self._dataSource._component_map.has_record(key):
|
|
147
|
+
self._dataSource._register_component(ele_id)
|
|
148
|
+
self._dataSource.send_filter(ele_id, Filter(filter))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from nicegui import ui
|
|
2
|
+
from ex4nicegui.layout import grid_box
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def ui_cols(num: int, min_width="0"):
|
|
6
|
+
with grid_box(template_columns=f"repeat({num},1fr)").classes(
|
|
7
|
+
"justify-between"
|
|
8
|
+
) as gb:
|
|
9
|
+
divs = [ui.column() for _ in range(num)]
|
|
10
|
+
|
|
11
|
+
gb.grid_box(template_columns="1fr", break_point="<sm[0-599.99px]")
|
|
12
|
+
|
|
13
|
+
return divs
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from nicegui import ui
|
|
2
|
+
from ex4nicegui.reactive import rxui
|
|
3
|
+
from ex4nicegui import to_ref
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def ui_left_drawer():
|
|
7
|
+
drawer_show = to_ref(True)
|
|
8
|
+
|
|
9
|
+
with rxui.drawer("left", value=drawer_show) as drawer:
|
|
10
|
+
with ui.page_sticky("top-right", x_offset=10, y_offset=10):
|
|
11
|
+
icon_close = (
|
|
12
|
+
rxui.icon(name="close")
|
|
13
|
+
.classes("cursor-pointer")
|
|
14
|
+
.props("round ")
|
|
15
|
+
.bind_visible(drawer_show)
|
|
16
|
+
)
|
|
17
|
+
icon_close.on("click", drawer.toggle)
|
|
18
|
+
|
|
19
|
+
with ui.page_sticky("top-left", x_offset=10, y_offset=10):
|
|
20
|
+
icon_close = (
|
|
21
|
+
rxui.icon(name="keyboard_arrow_right", size="1.2rem")
|
|
22
|
+
.classes("cursor-pointer")
|
|
23
|
+
.props("round ")
|
|
24
|
+
.bind_not_visible(drawer_show)
|
|
25
|
+
)
|
|
26
|
+
icon_close.on("click", drawer.toggle)
|
|
27
|
+
|
|
28
|
+
return drawer.element
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, TypeVar, Generic, TYPE_CHECKING, Union
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ex4nicegui.bi.dataSource import DataSource
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_T_ELEMENT = TypeVar("_T_ELEMENT", bound=ui.element)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UiResult(Generic[_T_ELEMENT]):
|
|
13
|
+
def __init__(self, element: _T_ELEMENT, dataSource: "DataSource") -> None:
|
|
14
|
+
self.__element = element
|
|
15
|
+
self._dataSource = dataSource
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def element(self):
|
|
19
|
+
return self.__element
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def id(self):
|
|
23
|
+
return self.element.id
|
|
24
|
+
|
|
25
|
+
def classes(
|
|
26
|
+
self,
|
|
27
|
+
add: Optional[str] = None,
|
|
28
|
+
*,
|
|
29
|
+
remove: Optional[str] = None,
|
|
30
|
+
replace: Optional[str] = None,
|
|
31
|
+
):
|
|
32
|
+
self.element.classes(add, remove=remove, replace=replace)
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def props(
|
|
36
|
+
self,
|
|
37
|
+
add: Optional[str] = None,
|
|
38
|
+
*,
|
|
39
|
+
remove: Optional[str] = None,
|
|
40
|
+
):
|
|
41
|
+
self.element.props(add, remove=remove)
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def cancel_linkage(self, *source: Union[ui.element, "UiResult"]):
|
|
45
|
+
get_info_key = self._dataSource.get_component_info_key
|
|
46
|
+
|
|
47
|
+
key = get_info_key(self.element.id)
|
|
48
|
+
|
|
49
|
+
info = self._dataSource._component_map.get_info(key)
|
|
50
|
+
|
|
51
|
+
for s in source:
|
|
52
|
+
res_key = get_info_key(s.id)
|
|
53
|
+
info.exclude_keys.add(res_key)
|
|
54
|
+
|
|
55
|
+
return self
|