ex4nicegui 0.2.17__tar.gz → 0.3.0__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.
Files changed (86) hide show
  1. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/PKG-INFO +1 -1
  2. ex4nicegui-0.3.0/README.md +209 -0
  3. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/__init__.py +1 -1
  4. ex4nicegui-0.3.0/ex4nicegui/bi/__init__.py +3 -0
  5. ex4nicegui-0.3.0/ex4nicegui/bi/dataSource.py +174 -0
  6. ex4nicegui-0.3.0/ex4nicegui/bi/dataSourceFacade.py +263 -0
  7. ex4nicegui-0.3.0/ex4nicegui/bi/index.py +76 -0
  8. ex4nicegui-0.3.0/ex4nicegui/bi/protocols.py +111 -0
  9. ex4nicegui-0.3.0/ex4nicegui/bi/types.py +12 -0
  10. ex4nicegui-0.3.0/ex4nicegui/layout/__init__.py +2 -0
  11. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/layout/gridFlex/gridFlex.py +1 -1
  12. ex4nicegui-0.3.0/ex4nicegui/layout/rxFlex/__init__.py +1 -0
  13. ex4nicegui-0.3.0/ex4nicegui/layout/rxFlex/index.py +121 -0
  14. ex4nicegui-0.3.0/ex4nicegui/layout/rxFlex/types.py +80 -0
  15. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/EChartsComponent/ECharts.js +1 -2
  16. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/echarts.py +1 -1
  17. ex4nicegui-0.3.0/ex4nicegui/utils/clientScope.py +42 -0
  18. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/utils/signals.py +50 -5
  19. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui.egg-info/PKG-INFO +1 -1
  20. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui.egg-info/SOURCES.txt +10 -1
  21. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui.egg-info/requires.txt +1 -1
  22. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/setup.py +1 -1
  23. ex4nicegui-0.2.17/README.md +0 -78
  24. ex4nicegui-0.2.17/ex4nicegui/layout/__init__.py +0 -2
  25. ex4nicegui-0.2.17/ex4nicegui/layout/_gridbox.py +0 -87
  26. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/LICENSE +0 -0
  27. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/experimental_/__init__.py +0 -0
  28. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/experimental_/gridLayout/__init__.py +0 -0
  29. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/experimental_/gridLayout/index.py +0 -0
  30. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/layout/gridFlex/GridFlex.js +0 -0
  31. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/layout/gridFlex/__init__.py +0 -0
  32. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/layout/gridFlex/utils.py +0 -0
  33. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/EChartsComponent/ECharts.py +0 -0
  34. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/EChartsComponent/__init__.py +0 -0
  35. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/UseDraggable/UseDraggable.js +0 -0
  36. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/UseDraggable/UseDraggable.py +0 -0
  37. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/UseDraggable/__init__.py +0 -0
  38. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/__index.py +0 -0
  39. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/__init__.py +0 -0
  40. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/drawer.py +0 -0
  41. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/dropZone/__init__.py +0 -0
  42. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/dropZone/dropZone.js +0 -0
  43. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/dropZone/dropZone.py +0 -0
  44. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/fileWatcher.py +0 -0
  45. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/local_file_picker.py +0 -0
  46. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/__init__.py +0 -0
  47. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/aggrid.py +0 -0
  48. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/base.py +0 -0
  49. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/button.py +0 -0
  50. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/card.py +0 -0
  51. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/checkbox.py +0 -0
  52. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/color_picker.py +0 -0
  53. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/column.py +0 -0
  54. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/date.py +0 -0
  55. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/drawer.py +0 -0
  56. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/expansion.py +0 -0
  57. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/grid.py +0 -0
  58. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/html.py +0 -0
  59. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/icon.py +0 -0
  60. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/image.py +0 -0
  61. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/input.py +0 -0
  62. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/label.py +0 -0
  63. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/number.py +0 -0
  64. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/radio.py +0 -0
  65. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/row.py +0 -0
  66. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/select.py +0 -0
  67. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/slider.py +0 -0
  68. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/switch.py +0 -0
  69. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/table.py +0 -0
  70. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/textarea.py +0 -0
  71. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/upload.py +0 -0
  72. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/officials/utils.py +0 -0
  73. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/q_pagination.py +0 -0
  74. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/rxui.py +0 -0
  75. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/useMouse/UseMouse.js +0 -0
  76. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/useMouse/UseMouse.py +0 -0
  77. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/useMouse/__init__.py +0 -0
  78. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/reactive/usePagination.py +0 -0
  79. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/tools/__init__.py +0 -0
  80. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/tools/debug.py +0 -0
  81. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/utils/__init__.py +0 -0
  82. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui/utils/common.py +0 -0
  83. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui.egg-info/dependency_links.txt +0 -0
  84. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui.egg-info/not-zip-safe +0 -0
  85. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/ex4nicegui.egg-info/top_level.txt +0 -0
  86. {ex4nicegui-0.2.17 → ex4nicegui-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ex4nicegui
3
- Version: 0.2.17
3
+ Version: 0.3.0
4
4
  Summary: ...
5
5
  Home-page:
6
6
  Author: carson_jia
@@ -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
+ ![](./asset/sync_input.gif)
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
+ ![](./asset/asyc_echarts_title.gif)
83
+
84
+
85
+
86
+ ## BI 模块
87
+
88
+ 以最精简的 apis 创建可交互的数据可视化报表
89
+
90
+ ![](./asset/bi_examples1.gif)
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
+
@@ -16,4 +16,4 @@ from signe import batch
16
16
  from ex4nicegui.experimental_ import gridLayout as exp_ui
17
17
 
18
18
 
19
- __version__ = "0.2.17"
19
+ __version__ = "0.3.0"
@@ -0,0 +1,3 @@
1
+ from .index import data_source
2
+
3
+ __all__ = ["data_source"]
@@ -0,0 +1,174 @@
1
+ from typing import Dict, List, Optional, cast
2
+ from ex4nicegui import to_ref, ref_computed, on
3
+ from nicegui import globals, Client
4
+
5
+ from dataclasses import dataclass
6
+ from . import types
7
+ from .protocols import IDataSourceAble
8
+
9
+
10
+ @dataclass
11
+ class DataSourceInfo:
12
+ source: "DataSource"
13
+ update_callback: types._TSourceBuildFn
14
+
15
+
16
+ @dataclass
17
+ class Filter:
18
+ callback: types._TFilterCallback
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ComponentInfoKey:
23
+ client_id: types._TNgClientID
24
+ element_id: types._TElementID
25
+
26
+
27
+ @dataclass
28
+ class ComponentInfo:
29
+ key: ComponentInfoKey
30
+ update_callback: types._TComponentUpdateCallback
31
+ filter: Optional[Filter] = None
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, client_id: types._TNgClientID, element_id: types._TElementID):
60
+ return (
61
+ client_id in self._client_map and element_id in self._client_map[client_id]
62
+ )
63
+
64
+ def set_filter(
65
+ self,
66
+ client_id: types._TNgClientID,
67
+ element_id: types._TElementID,
68
+ filter: Filter,
69
+ ):
70
+ self._client_map[client_id][element_id].filter = filter
71
+
72
+ def get_all_info(self):
73
+ return (
74
+ info for ele_map in self._client_map.values() for info in ele_map.values()
75
+ )
76
+
77
+
78
+ class DataSource:
79
+ _global_id_count: types._TDataSourceId = 0
80
+
81
+ def __init__(self, data: IDataSourceAble) -> None:
82
+ DataSource._global_id_count += 1
83
+ self.__id = DataSource._global_id_count
84
+
85
+ self._idataSource = data
86
+
87
+ data_fn = lambda: data.get_data()
88
+ data_fn = ref_computed(data_fn)
89
+
90
+ self.__filters = to_ref(cast(List[Filter], []))
91
+
92
+ @ref_computed
93
+ def apply_filters():
94
+ df = data_fn.value
95
+ for f in self.__filters.value:
96
+ df = f.callback(df)
97
+
98
+ return df
99
+
100
+ self.__filtered_data = apply_filters
101
+
102
+ self.__data = data_fn
103
+ self._component_map = ComponentMap()
104
+
105
+ @on(data_fn)
106
+ def _():
107
+ self.__notify_update()
108
+
109
+ @property
110
+ def data(self):
111
+ return self.__data.value
112
+
113
+ @property
114
+ def filtered_data(self):
115
+ return self.__filtered_data.value
116
+
117
+ @property
118
+ def id(self):
119
+ return self.__id
120
+
121
+ def _register_component(
122
+ self,
123
+ element_id: types._TElementID,
124
+ update_callback: types._TComponentUpdateCallback,
125
+ ):
126
+ ng_client = globals.get_client()
127
+ client_id = ng_client.id
128
+
129
+ if not self._component_map.has_client_record(client_id):
130
+
131
+ @ng_client.on_disconnect
132
+ def _(e: Client):
133
+ if not e.shared:
134
+ self._component_map.remove_client(e.id)
135
+
136
+ self._component_map.add_info(
137
+ ComponentInfo(ComponentInfoKey(client_id, element_id), update_callback)
138
+ )
139
+
140
+ return self
141
+
142
+ def send_filter(self, element_id: types._TElementID, filter: Filter):
143
+ client_id = globals.get_client().id
144
+
145
+ if not self._component_map.has_record(client_id, element_id):
146
+ raise ValueError("element not register")
147
+
148
+ self._component_map.set_filter(client_id, element_id, filter)
149
+
150
+ self.__notify_update([ComponentInfoKey(client_id, element_id)])
151
+ return self
152
+
153
+ def __notify_update(self, ignore_keys: Optional[List[ComponentInfoKey]] = None):
154
+ ignore_keys = ignore_keys or []
155
+ ignore_ids_set = set(ignore_keys)
156
+
157
+ # nodify every component
158
+ for current_info in self._component_map.get_all_info():
159
+ if current_info.key in ignore_ids_set:
160
+ continue
161
+
162
+ # apply filters ,except current target
163
+ filters = [
164
+ info.filter.callback
165
+ for info in self._component_map.get_all_info()
166
+ if (info.key != current_info.key) and info.filter
167
+ ]
168
+
169
+ new_data = self._idataSource.apply_filters(self.__data.value, filters)
170
+ current_info.update_callback(new_data)
171
+
172
+ self.__filters.value = [
173
+ info.filter for info in self._component_map.get_all_info() if info.filter
174
+ ]
@@ -0,0 +1,263 @@
1
+ from __future__ import annotations
2
+ from typing import Any, Callable, Dict, TypeVar, Generic, Union, cast
3
+ from nicegui import ui
4
+ from ex4nicegui import ref_computed
5
+ from ex4nicegui.reactive import rxui
6
+ from .dataSource import DataSource, Filter
7
+ from ex4nicegui.reactive.EChartsComponent.ECharts import echarts
8
+
9
+
10
+ _TData = TypeVar("_TData")
11
+
12
+
13
+ class DataSourceFacade(Generic[_TData]):
14
+ def __init__(self, ds: DataSource) -> None:
15
+ self._dataSource = ds
16
+
17
+ @property
18
+ def data(self) -> _TData:
19
+ """Data without any filtering"""
20
+ return cast(_TData, self._dataSource.data)
21
+
22
+ @property
23
+ def filtered_data(self) -> _TData:
24
+ """Data after filtering"""
25
+ return cast(_TData, self._dataSource.filtered_data)
26
+
27
+ def ui_select(
28
+ self, column: str, *, clearable=True, multiple=True, **kwargs
29
+ ) -> ui.select:
30
+ """
31
+ Creates a user interface select box.
32
+
33
+ Parameters:
34
+ column (str): The column name of the data source.
35
+ clearable (bool, optional): Whether to allow clearing the content of the select box. Default is True.
36
+ multiple (bool, optional): Whether to allow multiple selections.
37
+ **kwargs: Additional optional parameters that will be passed to the ui.select constructor.
38
+
39
+ Returns:
40
+ ui.select: An instance of a user interface select box.
41
+ """
42
+ options = self._dataSource._idataSource.duplicates_column_values(
43
+ self.data, column
44
+ )
45
+ kwargs.update(
46
+ {
47
+ "options": options,
48
+ "multiple": multiple,
49
+ "clearable": clearable,
50
+ "label": column,
51
+ }
52
+ )
53
+
54
+ cp = ui.select(**kwargs).props("use-chips outlined")
55
+
56
+ def onchange(e):
57
+ value = None
58
+ if e.args:
59
+ if isinstance(e.args, list):
60
+ value = [arg["label"] for arg in e.args]
61
+ else:
62
+ value = e.args["label"]
63
+
64
+ cp.value = value
65
+
66
+ def data_filter(data):
67
+ if cp.value is None or not cp.value:
68
+ return data
69
+
70
+ cond = None
71
+ if isinstance(cp.value, list):
72
+ cond = data[column].isin(cp.value)
73
+ else:
74
+ cond = data[column] == cp.value
75
+ return data[cond]
76
+
77
+ self._dataSource.send_filter(cp.id, Filter(data_filter))
78
+
79
+ cp.on("update:modelValue", onchange)
80
+
81
+ def on_source_update(data):
82
+ options = self._dataSource._idataSource.duplicates_column_values(
83
+ data, column
84
+ )
85
+ value = cp.value
86
+
87
+ # Make the value within the options
88
+ if isinstance(value, list):
89
+ value = list(set(value) & set(options))
90
+ else:
91
+ if value not in options:
92
+ value = ""
93
+
94
+ cp.set_options(options, value=value)
95
+
96
+ self._dataSource._register_component(cp.id, on_source_update)
97
+
98
+ return cp
99
+
100
+ def ui_aggrid(self, **kwargs):
101
+ """
102
+ Creates aggrid table.
103
+
104
+ Parameters:
105
+ **kwargs: Additional optional parameters that will be passed to the ui.aggrid constructor.
106
+
107
+ Returns:
108
+ ui.aggrid: aggrid table.
109
+ """
110
+ kwargs.update(
111
+ {"options": self._dataSource._idataSource.get_aggrid_options(self.data)}
112
+ )
113
+
114
+ cp = ui.aggrid(**kwargs)
115
+
116
+ def on_source_update(data):
117
+ cp._props["options"] = self._dataSource._idataSource.get_aggrid_options(
118
+ data
119
+ )
120
+ cp.update()
121
+
122
+ on_source_update(self.filtered_data)
123
+
124
+ self._dataSource._register_component(cp.id, on_source_update)
125
+
126
+ return cp
127
+
128
+ def ui_radio(self, column: str, **kwargs):
129
+ """
130
+ Creates radio Selection.
131
+
132
+ Parameters:
133
+ column (str): The column name of the data source.
134
+ **kwargs: Additional optional parameters that will be passed to the ui.radio constructor.
135
+
136
+ Returns:
137
+ ui.radio: An radio Selection.
138
+ """
139
+ options = self._dataSource._idataSource.duplicates_column_values(
140
+ self.data, column
141
+ )
142
+ kwargs.update({"options": options})
143
+
144
+ cp = ui.radio(**kwargs)
145
+
146
+ def onchange(e):
147
+ cp.value = cp.options[e.args]
148
+
149
+ def data_filter(data):
150
+ if cp.value not in cp.options:
151
+ return data
152
+ cond = data[column] == cp.value
153
+ return data[cond]
154
+
155
+ self._dataSource.send_filter(cp.id, Filter(data_filter))
156
+
157
+ cp.on("update:modelValue", onchange)
158
+
159
+ def on_source_update(data):
160
+ options = self._dataSource._idataSource.duplicates_column_values(
161
+ data, column
162
+ )
163
+ value = cp.value
164
+ if value not in options:
165
+ value = ""
166
+
167
+ cp.set_options(options, value=value)
168
+
169
+ self._dataSource._register_component(cp.id, on_source_update)
170
+
171
+ return cp
172
+
173
+ def ui_slider(self, column: str, **kwargs):
174
+ """
175
+ Creates Slider.
176
+
177
+ Parameters:
178
+ column (str): The column name of the data source.
179
+ **kwargs: Additional optional parameters that will be passed to the ui.slider constructor.
180
+
181
+ Returns:
182
+ ui.radio: An Slider.
183
+ """
184
+ self._dataSource._idataSource.slider_check(self.data, column)
185
+
186
+ min, max = self._dataSource._idataSource.slider_min_max(self.data, column)
187
+ kwargs.update({"min": min, "max": max})
188
+
189
+ cp = ui.slider(**kwargs).props("label label-always switch-label-side")
190
+
191
+ def onchange():
192
+ def data_filter(data):
193
+ if cp.value is None or cp.value < min:
194
+ return data
195
+ cond = data[column] == cp.value
196
+ return data[cond]
197
+
198
+ self._dataSource.send_filter(cp.id, Filter(data_filter))
199
+
200
+ cp.on("change", onchange)
201
+
202
+ def on_source_update(data):
203
+ min, max = self._dataSource._idataSource.slider_min_max(data, column)
204
+ if min is None or max is None:
205
+ cp.value = None
206
+ else:
207
+ cp._props["min"] = min
208
+ cp._props["max"] = max
209
+
210
+ self._dataSource._register_component(cp.id, on_source_update)
211
+
212
+ return cp
213
+
214
+ def ui_echarts(
215
+ self, fn: Callable[[Any], Union[Dict, "pyecharts.Base"]] # pyright: ignore
216
+ ) -> echarts:
217
+ """Create charts
218
+
219
+ Args:
220
+ fn (Callable[[Any], Union[Dict, "pyecharts.Base"]]): builder function.
221
+
222
+ ## Examples
223
+
224
+ Support pyecharts
225
+
226
+ ```py
227
+ import pandas as pd
228
+ from ex4nicegui import bi
229
+ from pyecharts.charts import Bar
230
+
231
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
232
+ ds = bi.data_source(df)
233
+
234
+ @ds.ui_echarts
235
+ def bar(data: pd.DataFrame):
236
+ c = (
237
+ Bar()
238
+ .add_xaxis(data["name"].tolist())
239
+ .add_yaxis("value", data["value"].tolist())
240
+ )
241
+
242
+ return c
243
+
244
+ bar.classes("h-[20rem]")
245
+ ```
246
+
247
+ """
248
+
249
+ @ref_computed
250
+ def chart_options():
251
+ options = fn(self.filtered_data)
252
+ if isinstance(options, Dict):
253
+ return options
254
+
255
+ import simplejson as json
256
+ from pyecharts.charts.chart import Base
257
+
258
+ if isinstance(options, Base):
259
+ return cast(Dict, json.loads(options.dump_options()))
260
+
261
+ cp = rxui.echarts(chart_options) # type: ignore
262
+
263
+ return cp.element # type: ignore