ex4nicegui 0.6.5__py3-none-any.whl → 0.6.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. ex4nicegui/__init__.py +3 -2
  2. ex4nicegui/bi/elements/ui_echarts.py +2 -5
  3. ex4nicegui/libs/gsap/.DS_Store +0 -0
  4. ex4nicegui/libs/gsap/gsap.mjs +6 -0
  5. ex4nicegui/reactive/EChartsComponent/ECharts.js +69 -86
  6. ex4nicegui/reactive/EChartsComponent/ECharts.py +20 -103
  7. ex4nicegui/reactive/EChartsComponent/events.py +26 -0
  8. ex4nicegui/reactive/EChartsComponent/types.py +51 -0
  9. ex4nicegui/reactive/EChartsComponent/utils.py +44 -0
  10. ex4nicegui/reactive/deferredTask.py +29 -0
  11. ex4nicegui/reactive/officials/aggrid.py +3 -3
  12. ex4nicegui/reactive/officials/base.py +10 -0
  13. ex4nicegui/reactive/officials/button.py +4 -4
  14. ex4nicegui/reactive/officials/checkbox.py +3 -3
  15. ex4nicegui/reactive/officials/circular_progress.py +3 -3
  16. ex4nicegui/reactive/officials/color_picker.py +4 -4
  17. ex4nicegui/reactive/officials/column.py +3 -2
  18. ex4nicegui/reactive/officials/date.py +4 -9
  19. ex4nicegui/reactive/officials/echarts.py +39 -34
  20. ex4nicegui/reactive/officials/expansion.py +3 -2
  21. ex4nicegui/reactive/officials/icon.py +4 -4
  22. ex4nicegui/reactive/officials/image.py +3 -3
  23. ex4nicegui/reactive/officials/input.py +4 -4
  24. ex4nicegui/reactive/officials/knob.py +3 -3
  25. ex4nicegui/reactive/officials/label.py +4 -3
  26. ex4nicegui/reactive/officials/linear_progress.py +4 -4
  27. ex4nicegui/reactive/officials/number.py +24 -5
  28. ex4nicegui/reactive/officials/radio.py +4 -4
  29. ex4nicegui/reactive/officials/select.py +4 -4
  30. ex4nicegui/reactive/officials/slider.py +3 -3
  31. ex4nicegui/reactive/officials/switch.py +3 -3
  32. ex4nicegui/reactive/officials/tab_panels.py +14 -2
  33. ex4nicegui/reactive/officials/table.py +5 -4
  34. ex4nicegui/reactive/officials/tabs.py +3 -2
  35. ex4nicegui/reactive/officials/textarea.py +3 -3
  36. ex4nicegui/reactive/q_pagination.py +3 -2
  37. ex4nicegui/utils/clientScope.py +14 -6
  38. {ex4nicegui-0.6.5.dist-info → ex4nicegui-0.6.7.dist-info}/METADATA +1245 -1099
  39. {ex4nicegui-0.6.5.dist-info → ex4nicegui-0.6.7.dist-info}/RECORD +65 -60
  40. {ex4nicegui-0.6.5.dist-info → ex4nicegui-0.6.7.dist-info}/WHEEL +1 -2
  41. ex4nicegui-0.6.5.dist-info/top_level.txt +0 -1
  42. {ex4nicegui-0.6.5.dist-info → ex4nicegui-0.6.7.dist-info}/LICENSE +0 -0
@@ -1,1099 +1,1245 @@
1
- Metadata-Version: 2.1
2
- Name: ex4nicegui
3
- Version: 0.6.5
4
- Summary: Extension library based on nicegui, providing data responsive,BI functionality modules
5
- Home-page:
6
- Author: carson_jia
7
- Author-email: 568166495@qq.com
8
- License: MIT license
9
- Keywords: nicegui,ex4nicegui,webui
10
- Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Natural Language :: English
13
- Classifier: Programming Language :: Python :: 3.8
14
- Requires-Python: >=3.8
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: signe (>=0.4.14)
18
- Requires-Dist: nicegui (>=1.4.23)
19
- Requires-Dist: typing-extensions
20
- Requires-Dist: executing
21
-
22
- # ex4nicegui
23
- [中文 README](./README.md)
24
-
25
- - [Install](#-install)
26
- - [Usage](#-usage)
27
- - [Features](#-features)
28
- - [BI Module](#bi-module)
29
-
30
- An extension library for [nicegui](https://github.com/zauberzeug/nicegui). It has built-in responsive components and fully implements data-responsive interface programming.
31
-
32
-
33
- ## 📦 Install
34
-
35
- ```
36
- pip install ex4nicegui -U
37
- ```
38
-
39
- ## examples
40
- - [basic](./examples/basic/)
41
- - [todo list mvc](./examples/todomvc/)
42
-
43
-
44
- ## 🦄 Usage
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
- # Define responsive data
52
- r_input = to_ref("")
53
-
54
- # Pass in the responsive data according to the nicegui usage method.
55
- rxui.input(value=r_input)
56
- rxui.label(r_input)
57
-
58
- ui.run()
59
- ```
60
- ![](./asset/sync_input.gif)
61
-
62
-
63
- ## 🚀 Features
64
-
65
- ### echarts components
66
-
67
- ```python
68
- from nicegui import ui
69
- from ex4nicegui import ref_computed, effect, to_ref
70
- from ex4nicegui.reactive import rxui
71
-
72
- r_input = to_ref("")
73
-
74
-
75
- # ref_computed Creates a read-only reactive variable
76
- # Functions can use any other reactive variables automatically when they are associated
77
- @ref_computed
78
- def cp_echarts_opts():
79
- return {
80
- "title": {"text": r_input.value}, #In the dictionary, use any reactive variable. Get the value by .value
81
- "xAxis": {
82
- "type": "category",
83
- "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
84
- },
85
- "yAxis": {"type": "value"},
86
- "series": [
87
- {
88
- "data": [120, 200, 150, 80, 70, 110, 130],
89
- "type": "bar",
90
- "showBackground": True,
91
- "backgroundStyle": {"color": "rgba(180, 180, 180, 0.2)"},
92
- }
93
- ],
94
- }
95
-
96
-
97
- input = rxui.input("Enter the content, and the chart title will be synchronized", value=r_input)
98
-
99
- # Get the native nicegui component object through the element attribute of the responsive component object
100
- input.element.classes("w-full")
101
-
102
- rxui.echarts(cp_echarts_opts).classes('h-[20rem]')
103
-
104
- ui.run()
105
- ```
106
- ![](./asset/asyc_echarts_title.gif)
107
-
108
-
109
- ### echarts mouse events
110
-
111
- the `on` function parameters `event_name` and `query` to view the[echarts English Documentation](https://echarts.apache.org/handbook/en/concepts/event/)
112
-
113
-
114
- The following example binds the mouse click event
115
- ```python
116
- from nicegui import ui
117
- from ex4nicegui.reactive import rxui
118
-
119
- opts = {
120
- "xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
121
- "yAxis": {
122
- "type": "category",
123
- "data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
124
- },
125
- "series": [
126
- {
127
- "name": "first",
128
- "type": "bar",
129
- "data": [18203, 23489, 29034, 104970, 131744, 630230],
130
- },
131
- {
132
- "name": "second",
133
- "type": "bar",
134
- "data": [19325, 23438, 31000, 121594, 134141, 681807],
135
- },
136
- ],
137
- }
138
-
139
- bar = rxui.echarts(opts)
140
-
141
- def on_click(e: rxui.echarts.EChartsMouseEventArguments):
142
- ui.notify(f"on_click:{e.seriesName}:{e.name}:{e.value}")
143
-
144
- bar.on("click", on_click)
145
- ```
146
-
147
-
148
- The following example only triggers the mouseover event for a specified series.
149
- ```python
150
- from nicegui import ui
151
- from ex4nicegui.reactive import rxui
152
-
153
- opts = {
154
- "xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
155
- "yAxis": {
156
- "type": "category",
157
- "data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
158
- },
159
- "series": [
160
- {
161
- "name": "first",
162
- "type": "bar",
163
- "data": [18203, 23489, 29034, 104970, 131744, 630230],
164
- },
165
- {
166
- "name": "second",
167
- "type": "bar",
168
- "data": [19325, 23438, 31000, 121594, 134141, 681807],
169
- },
170
- ],
171
- }
172
-
173
- bar = rxui.echarts(opts)
174
-
175
- def on_first_series_mouseover(e: rxui.echarts.EChartsMouseEventArguments):
176
- ui.notify(f"on_first_series_mouseover:{e.seriesName}:{e.name}:{e.value}")
177
-
178
-
179
- bar.on("mouseover", on_first_series_mouseover, query={"seriesName": "first"})
180
-
181
- ui.run()
182
- ```
183
- ---
184
-
185
-
186
- ## responsive
187
-
188
- ```python
189
- from ex4nicegui import (
190
- to_ref,
191
- ref_computed,
192
- on,
193
- effect,
194
- effect_refreshable,
195
- batch,
196
- event_batch,
197
- deep_ref,
198
- )
199
- ```
200
- Commonly used `to_ref`,`deep_ref`,`effect`,`ref_computed`,`on`
201
-
202
- ### `to_ref`
203
- Defines responsive objects, read and written by `.value`.
204
- ```python
205
- a = to_ref(1)
206
- b = to_ref("text")
207
-
208
- a.value =2
209
- b.value = 'new text'
210
-
211
- print(a.value)
212
- ```
213
-
214
- When the value is a complex object, responsiveness of nested objects is not maintained by default.
215
-
216
- ```python
217
- a = to_ref([1,2])
218
-
219
- @effect
220
- def _().
221
- print(len(a.value))
222
-
223
- # doesn't trigger the effect
224
- a.value.append(10)
225
-
226
- # the whole substitution will be triggered
227
- a.value = [1,2,10]
228
- ```
229
-
230
- Depth responsiveness is obtained when `is_deep` is set to `True`.
231
-
232
- ```python
233
- a = to_ref([1,2],is_deep=True)
234
-
235
- @effect
236
- def _():
237
- print('len:',len(a.value))
238
-
239
- # print 3
240
- a.value.append(10)
241
-
242
- ```
243
-
244
- > `deep_ref` is equivalent to `to_ref` if `is_deep` is set to `True`.
245
- ---
246
-
247
- ### `deep_ref`
248
- Equivalent to `to_ref` when `is_deep` is set to `True`.
249
-
250
- Especially useful when the data source is a list, dictionary or custom class. Objects obtained via `.value` are proxies
251
-
252
- ```python
253
- data = [1,2,3]
254
- data_ref = deep_ref(data)
255
-
256
- assert data_ref.value is not data
257
- ```
258
-
259
- You can get the raw object with `to_raw`.
260
- ```python
261
- from ex4nicegui import to_raw, deep_ref
262
-
263
- data = [1, 2, 3]
264
- data_ref = deep_ref(data)
265
-
266
- assert data_ref.value is not data
267
- assert to_raw(data_ref.value) is data
268
- ```
269
-
270
- ---
271
-
272
- ### `effect`
273
- Accepts a function and automatically monitors changes to the responsive objects used in the function to automatically execute the function.
274
-
275
- ```python
276
- a = to_ref(1)
277
- b = to_ref("text")
278
-
279
-
280
- @effect
281
- def auto_run_when_ref_value():
282
- print(f"a:{a.value}")
283
-
284
-
285
- def change_value():
286
- a.value = 2
287
- b.value = "new text"
288
-
289
-
290
- ui.button("change", on_click=change_value)
291
- ```
292
-
293
- The first time the effect is executed, the function `auto_run_when_ref_value` will be executed once. After that, clicking on the button changes the value of `a` (via `a.value`) and the function `auto_run_when_ref_value` is executed again.
294
-
295
- > Never spread a lot of data processing logic across multiple `on`s or `effects`s, which should be mostly interface manipulation logic rather than responsive data processing logic
296
-
297
- ---
298
-
299
- ### `ref_computed`
300
- As with `effect`, `ref_computed` can also return results from functions. Typically used for secondary computation from `to_ref`.
301
-
302
- ```python
303
- a = to_ref(1)
304
- a_square = ref_computed(lambda: a.value * 2)
305
-
306
-
307
- @effect
308
- def effect1():
309
- print(f"a_square:{a_square.value}")
310
-
311
-
312
- def change_value():
313
- a.value = 2
314
-
315
-
316
- ui.button("change", on_click=change_value)
317
- ```
318
-
319
- When the button is clicked, the value of `a.value` is modified, triggering a recalculation of `a_square`. As the value of `a_square` is read in `effect1`, it triggers `effect1` to execute the
320
-
321
- > `ref_computed` is read-only `to_ref`
322
-
323
- If you prefer to organize your code by class, ``ref_computed`` also supports acting on instance methods
324
-
325
- ```python
326
- class MyState.
327
- def __init__(self) -> None.
328
- self.r_text = to_ref("")
329
-
330
- @ref_computed
331
- def post_text(self): return self.r_text.value
332
- return self.r_text.value + "post"
333
-
334
- state = MyState()
335
-
336
- rxui.input(value=state.r_text)
337
- rxui.label(state.post_text)
338
- ```
339
-
340
- ---
341
-
342
- ### `async_computed`
343
- Use `async_computed` when asynchronous functions are required for computed.
344
-
345
- ```python
346
-
347
- # Simulate asynchronous functions that take a long time to execute
348
- async def long_time_query(input: str):
349
- await asyncio.sleep(2)
350
- num = random.randint(20, 100)
351
- return f"query result[{input=}]:{num=}"
352
-
353
-
354
- search = to_ref("")
355
- evaluating = to_ref(False)
356
-
357
- @async_computed(search, evaluating=evaluating, init="")
358
- async def search_result():
359
- return await long_time_query(search.value)
360
-
361
- rxui.lazy_input(value=search)
362
-
363
- rxui.label(
364
- lambda: "Query in progress" if evaluating.value else "Enter content in input box above and return to search"
365
- )
366
- rxui.label(search_result)
367
-
368
- ```
369
-
370
- - The first argument to `async_computed` must explicitly specify the responsive data to be monitored. Multiple responses can be specified using a list.
371
- - Parameter `evaluating` is responsive data of type bool, which is `True` while the asynchronous function is executing, and `False` after the computation is finished.
372
- - Parameter `init` specifies the initial result
373
-
374
- ---
375
-
376
- ### `on`
377
- Similar to `effect`, but `on` needs to explicitly specify the responsive object to monitor.
378
-
379
- ```python
380
-
381
- a1 = to_ref(1)
382
- a2 = to_ref(10)
383
- b = to_ref("text")
384
-
385
-
386
- @on(a1)
387
- def watch_a1_only():
388
- print(f"watch_a1_only ... a1:{a1.value},a2:{a2.value}")
389
-
390
-
391
- @on([a1, b], onchanges=True)
392
- def watch_a1_and_b():
393
- print(f"watch_a1_and_b ... a1:{a1.value},a2:{a2.value},b:{b.value}")
394
-
395
-
396
- def change_a1():
397
- a1.value += 1
398
- ui.notify("change_a1")
399
-
400
-
401
- ui.button("change a1", on_click=change_a1)
402
-
403
-
404
- def change_a2():
405
- a2.value += 1
406
- ui.notify("change_a2")
407
-
408
-
409
- ui.button("change a2", on_click=change_a2)
410
-
411
-
412
- def change_b():
413
- b.value += "x"
414
- ui.notify("change_b")
415
-
416
-
417
- ui.button("change b", on_click=change_b)
418
-
419
- ```
420
-
421
- - If the parameter `onchanges` is True (the default value is False), the specified function will not be executed at binding time.
422
-
423
- > Never spread a lot of data processing logic across multiple `on`s or `effects`s, which should be mostly interface manipulation logic rather than responsive data processing logic
424
-
425
- ---
426
-
427
- ## functionality
428
-
429
- ### vmodel
430
- Create two-way bindings on form input elements or components.
431
-
432
- Bidirectional bindings are supported by default for `ref` simple value types
433
- ```python
434
- from ex4nicegui import rxui, to_ref, deep_ref
435
-
436
- data = to_ref("init")
437
-
438
- rxui.label(lambda: f"{data.value=}")
439
- # two-way binding
440
- rxui.input(value=data)
441
- ```
442
- - Simple value types are generally immutable values such as `str`, `int`, etc.
443
-
444
- When using complex data structures, `deep_ref` is used to keep nested values responsive.
445
- ```python
446
- data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
447
-
448
- rxui.label(lambda: f"{data.value=!s}")
449
-
450
- # No binding effect
451
- rxui.input(value=data.value["a"])
452
-
453
- # readonly binding
454
- rxui.input(value=lambda: data.value["a"])
455
-
456
- # two-way binding
457
- rxui.input(value=rxui.vmodel(data.value["a"]))
458
- ```
459
-
460
- - The first input box will be completely unresponsive because the code is equivalent to passing in a value `1` directly
461
- - The second input box will get read responsiveness due to the use of a function.
462
- - The third input box, wrapped in `rxui.vmodel`, can be bi-directionally bound.
463
-
464
- Most use `vmodel` with `vfor`, see [todo list examples](./examples/todomvc/)
465
-
466
- ---
467
-
468
- ### vfor
469
- Render list components based on list responsive data. Each component is updated on demand. Data items support dictionaries or objects of any type
470
-
471
- ```python
472
- from nicegui import ui
473
- from ex4nicegui.reactive import rxui
474
- from ex4nicegui import deep_ref, ref_computed
475
- from typing import Dict
476
-
477
- # refs
478
- items = deep_ref(
479
- [
480
- {"id": 1, "message": "foo", "done": False},
481
- {"id": 2, "message": "bar", "done": True},
482
- ]
483
- )
484
-
485
- # ref_computeds
486
- @ref_computed
487
- def done_count_info():
488
- return f"done count:{sum(item['done'] for item in items.value)}"
489
-
490
- # method
491
- def check():
492
- for item in items.value:
493
- item["done"] = not item["done"]
494
-
495
-
496
- # ui
497
- rxui.label(done_count_info)
498
- ui.button("check", on_click=check)
499
-
500
-
501
- @rxui.vfor(items,key='id')
502
- def _(store: rxui.VforStore[Dict]):
503
- # function to build the interface for each row of data
504
- item = store.get() # Get responsive object with `store.get`
505
- mes = rxui.vmodel(item.value['message'])
506
-
507
- # Enter the content of the input box,
508
- # you can see the title of the radio box changes synchronously
509
- with ui.card():
510
- with ui.row():
511
- rxui.input(value=mes)
512
- rxui.label(lambda: f"{mes.value=!s}")
513
- rxui.checkbox(text=mes, value=rxui.vmodel(item.value['done']))
514
-
515
- ```
516
-
517
- - `rxui.vfor` decorator to custom function
518
- - The first argument is passed to the responsive list. Each item in the list can be a dictionary or other object (`dataclasses` etc.)
519
- - Second parameter `key`: In order to be able to keep track of the identity of each node, and thus reuse and reorder existing elements, you can provide a unique key for the block corresponding to each element. The default(`None`) is to use the list element index.
520
- - The custom function takes one argument. The current row's can be retrieved via `store.get`, which is a responsive object.
521
-
522
-
523
- > vfor are created only when new data is added.
524
-
525
-
526
- ---
527
-
528
-
529
- ### Bind class names
530
-
531
- All component classes provide `bind_classes` for binding `class`, supporting three different data structures.
532
-
533
- Bind dictionaries
534
-
535
- ```python
536
- bg_color = to_ref(False)
537
- has_error = to_ref(False)
538
-
539
- rxui.label("test").bind_classes({"bg-blue": bg_color, "text-red": has_error})
540
-
541
- rxui.switch("bg_color", value=bg_color)
542
- rxui.switch("has_error", value=has_error)
543
- ```
544
-
545
- Dictionary key is the class name, and a dictionary value of a responsive variable with value `bool`. When the responsive value is `True`, the class name is applied to the component `class`.
546
-
547
- ---
548
-
549
- Bind a responsive variable whose return value is a dictionary.
550
-
551
- ```python
552
- bg_color = to_ref(False)
553
- has_error = to_ref(False)
554
-
555
- class_obj = ref_computed(
556
- lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
557
- )
558
-
559
- rxui.switch("bg_color", value=bg_color)
560
- rxui.switch("has_error", value=has_error)
561
- rxui.label("bind to ref_computed").bind_classes(class_obj)
562
-
563
- # or direct function passing
564
- rxui.label("bind to ref_computed").bind_classes(
565
- lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
566
- )
567
- ```
568
-
569
- ---
570
-
571
- Bind to list
572
-
573
- ```python
574
- bg_color = to_ref("red")
575
- bg_color_class = ref_computed(lambda: f"bg-{bg_color.value}")
576
-
577
- text_color = to_ref("green")
578
- text_color_class = ref_computed(lambda: f"text-{text_color.value}")
579
-
580
- rxui.select(["red", "green", "yellow"], label="bg color", value=bg_color)
581
- rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
582
-
583
- rxui.label("binding to arrays").bind_classes([bg_color_class, text_color_class])
584
-
585
- ```
586
-
587
- Each element in the list is a responsive variable that returns the class name
588
-
589
- ---
590
-
591
- ### bind-style
592
-
593
- ```python
594
- from nicegui import ui
595
- from ex4nicegui.reactive import rxui
596
- from ex4nicegui.utils.signals import to_ref
597
-
598
-
599
- bg_color = to_ref("blue")
600
- text_color = to_ref("red")
601
-
602
- rxui.label("test").bind_style(
603
- {
604
- "background-color": bg_color,
605
- "color": text_color,
606
- }
607
- )
608
-
609
- rxui.select(["blue", "green", "yellow"], label="bg color", value=bg_color)
610
- rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
611
- ```
612
-
613
- `bind_style` passed into dictionary, `key` is style name, `value` is style value, responsive string
614
-
615
- ---
616
-
617
- ### rxui.echarts
618
- Charting with echarts
619
-
620
- ---
621
-
622
- #### rxui.echarts.from_javascript
623
- Create echart from javascript code
624
-
625
- ```python
626
- from pathlib import Path
627
-
628
- rxui.echarts.from_javascript(Path("code.js"))
629
- # or
630
- rxui.echarts.from_javascript(
631
- """
632
- (myChart) => {
633
-
634
- option = {
635
- xAxis: {
636
- type: 'category',
637
- data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
638
- },
639
- yAxis: {
640
- type: 'value'
641
- },
642
- series: [
643
- {
644
- data: [120, 200, 150, 80, 70, 110, 130],
645
- type: 'bar'
646
- }
647
- ]
648
- };
649
-
650
- myChart.setOption(option);
651
- }
652
- """
653
- )
654
- ```
655
-
656
- - The first parameter of the function is the echart instance object. You need to configure the chart in the function with `setOption`.
657
-
658
- ---
659
-
660
- #### rxui.echarts.register_map
661
- Register a map.
662
-
663
- ```python
664
- rxui.echarts.register_map(
665
- "china", "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
666
- )
667
-
668
- rxui.echarts(
669
- {
670
- "geo": {
671
- "map": "china",
672
- "roam": True,
673
- },
674
- "tooltip": {},
675
- "legend": {},
676
- "series": [], }
677
- }
678
- )
679
- ```
680
-
681
- - The parameter `map_name` is a customized map name. Note that `map` must correspond to a registered name in the chart configuration.
682
- - The parameter `src` is a valid network link to the map data.
683
-
684
- You can also provide the path to the local json file for the map data.
685
- ```python
686
- from pathlib import Path
687
-
688
- rxui.echarts.register_map(
689
- "china", Path("map-data.json")
690
- )
691
- ```
692
-
693
- ---
694
-
695
- ### gsap
696
-
697
- js animation library. [gsap documentation](https://gsap.com/docs/v3/)
698
-
699
- ```python
700
- from nicegui import ui
701
- from ex4nicegui import gsap
702
- ```
703
-
704
- #### gsap.from_
705
-
706
- Set the start property, the animation will transition from the set property to the original position
707
-
708
- ```python
709
-
710
- ui.label("test from").classes("target")
711
- gsap.from_(".target", {"x": 50,'duration':1})
712
-
713
- ```
714
-
715
- After the screen is loaded, the starting position of the text is shifted to the right by 50px, and then moved to the original position within 1 second.
716
-
717
- - Arguments `targets` are css selectors.
718
- - Arguments `vars` are attribute values
719
-
720
- ---
721
-
722
- #### gsap.to
723
-
724
- Set the end property, the animation will transition from the original property to the set property.
725
-
726
- ```python
727
-
728
- ui.label("test to").classes("target")
729
- gsap.to(".target", {"x": 50,'duration':1})
730
-
731
- ```
732
-
733
- After loading the screen, the text will be moved back 50px from the original position within 1 second.
734
-
735
- - Arguments `targets` are css selectors.
736
- - Arguments `vars` are attribute values
737
-
738
- ---
739
-
740
- #### gsap.run_script
741
-
742
- Setting up animations by writing js
743
-
744
- ```python
745
-
746
- gsap.run_script(
747
- r"""function setGsap(gsap) {
748
- gsap.to('.target',{"duration": 0.3,y:60})
749
- }
750
- """)
751
- ```
752
-
753
- - The parameter `script` can be text or a file with a js extension `Path`.
754
- - The name of the defined js function doesn't matter, the first argument is a gsap object.
755
-
756
- ---
757
-
758
- ## BI Module
759
-
760
- Create an interactive data visualization report using the minimal API.
761
-
762
- ![](./asset/bi_examples1.gif)
763
-
764
- ```python
765
- from nicegui import ui
766
- import pandas as pd
767
- import numpy as np
768
- from ex4nicegui import bi
769
- from ex4nicegui.reactive import rxui
770
- from ex4nicegui import effect, effect_refreshable
771
- from pyecharts.charts import Bar
772
-
773
-
774
- # data ready
775
- def gen_data():
776
- np.random.seed(265)
777
- field1 = ["a1", "a2", "a3", "a4"]
778
- field2 = [f"name{i}" for i in range(1, 11)]
779
- df = (
780
- pd.MultiIndex.from_product([field1, field2], names=["cat", "name"])
781
- .to_frame()
782
- .reset_index(drop=True)
783
- )
784
- df[["idc1", "idc2"]] = np.random.randint(50, 1000, size=(len(df), 2))
785
- return df
786
-
787
-
788
- df = gen_data()
789
-
790
- # Create a data source.
791
- ds = bi.data_source(df)
792
-
793
- # ui
794
- ui.query(".nicegui-content").classes("items-stretch no-wrap")
795
-
796
- with ui.row().classes("justify-evenly"):
797
- # Create components based on the data source `ds`.
798
- ds.ui_select("cat").classes("min-w-[10rem]")
799
- ds.ui_select("name").classes("min-w-[10rem]")
800
-
801
-
802
- with ui.grid(columns=2):
803
- # Configure the chart using a dictionary.
804
- @ds.ui_echarts
805
- def bar1(data: pd.DataFrame):
806
- data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
807
-
808
- return {
809
- "xAxis": {"type": "value"},
810
- "yAxis": {
811
- "type": "category",
812
- "data": data["name"].tolist(),
813
- "inverse": True,
814
- },
815
- "legend": {"textStyle": {"color": "gray"}},
816
- "series": [
817
- {"type": "bar", "name": "idc1", "data": data["idc1"].tolist()},
818
- {"type": "bar", "name": "idc2", "data": data["idc2"].tolist()},
819
- ],
820
- }
821
-
822
- bar1.classes("h-[20rem]")
823
-
824
- # Configure the chart using pyecharts.
825
- @ds.ui_echarts
826
- def bar2(data: pd.DataFrame):
827
- data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
828
-
829
- return (
830
- Bar()
831
- .add_xaxis(data["name"].tolist())
832
- .add_yaxis("idc1", data["idc1"].tolist())
833
- .add_yaxis("idc2", data["idc2"].tolist())
834
- )
835
-
836
- bar2.classes("h-[20rem]")
837
-
838
- # Bind the click event to achieve navigation.
839
- @bar2.on_chart_click
840
- def _(e: rxui.echarts.EChartsMouseEventArguments):
841
- ui.open(f"/details/{e.name}", new_tab=True)
842
-
843
-
844
- # with response mechanisms, you can freely combine native nicegui components.
845
- label_a1_total = ui.label("")
846
-
847
-
848
- # this function will be triggered when ds changed.
849
- @effect
850
- def _():
851
- # prop `filtered_data` is the filtered DataFrame.
852
- df = ds.filtered_data
853
- total = df[df["cat"] == "a1"]["idc1"].sum()
854
- label_a1_total.text = f"idc1 total(cat==a1):{total}"
855
-
856
-
857
- # you can also use `effect_refreshable`, but you need to note that the components in the function are rebuilt each time.
858
- @effect_refreshable
859
- def _():
860
- df = ds.filtered_data
861
- total = df[df["cat"] == "a2"]["idc1"].sum()
862
- ui.label(f"idc1 total(cat==a2):{total}")
863
-
864
-
865
- # the page to be navigated when clicking on the chart series.
866
- @ui.page("/details/{name}")
867
- def details_page(name: str):
868
- ui.label("This table data will not change")
869
- ui.aggrid.from_pandas(ds.data.query(f'name=="{name}"'))
870
-
871
- ui.label("This table will change when the homepage data changes. ")
872
-
873
- @bi.data_source
874
- def new_ds():
875
- return ds.filtered_data[["name", "idc1", "idc2"]]
876
-
877
- new_ds.ui_aggrid()
878
-
879
-
880
- ui.run()
881
- ```
882
-
883
- ### Details
884
-
885
- #### `bi.data_source`
886
- The data source is the core concept of the BI module, and all data linkage is based on this. In the current version (0.4.3), there are two ways to create a data source.
887
-
888
- Receive `pandas`'s `DataFrame`:
889
- ```python
890
- from nicegui import ui
891
- from ex4nicegui import bi
892
- import pandas as pd
893
-
894
- df = pd.DataFrame(
895
- {
896
- "name": list("aabcdf"),
897
- "cls": ["c1", "c2", "c1", "c1", "c3", None],
898
- "value": range(6),
899
- }
900
- )
901
-
902
- ds = bi.data_source(df)
903
- ```
904
-
905
- Sometimes, we want to create a new data source based on another data source, in which case we can use a decorator to create a linked data source:
906
- ```python
907
- df = pd.DataFrame(
908
- {
909
- "name": list("aabcdf"),
910
- "cls": ["c1", "c2", "c1", "c1", "c3", None],
911
- "value": range(6),
912
- }
913
- )
914
-
915
- ds = bi.data_source(df)
916
-
917
- @bi.data_source
918
- def new_ds():
919
- # df is pd.DataFrame
920
- df = ds.filtered_data
921
- df=df.copy()
922
- df['value'] = df['value'] * 100
923
- return df
924
-
925
- ds.ui_select('name')
926
- new_ds.ui_aggrid()
927
- ```
928
-
929
- Note that since `new_ds` uses `ds.filtered_data`, changes to `ds` will trigger the linkage change of `new_ds`, causing the table component created by `new_ds` to change.
930
-
931
- ---
932
-
933
- Remove all filter states through the `ds.remove_filters` method:
934
- ```python
935
- ds = bi.data_source(df)
936
-
937
- def on_remove_filters():
938
- ds.remove_filters()
939
-
940
- ui.button("remove all filters", on_click=on_remove_filters)
941
-
942
- ds.ui_select("name")
943
- ds.ui_aggrid()
944
- ```
945
- ---
946
-
947
- Reset the data source through the `ds.reload` method:
948
- ```python
949
-
950
- df = pd.DataFrame(
951
- {
952
- "name": list("aabcdf"),
953
- "cls": ["c1", "c2", "c1", "c1", "c3", None],
954
- "value": range(6),
955
- }
956
- )
957
-
958
- new_df = pd.DataFrame(
959
- {
960
- "name": list("xxyyds"),
961
- "cls": ["cla1", "cla2", "cla3", "cla3", "cla3", None],
962
- "value": range(100, 106),
963
- }
964
- )
965
-
966
- ds = bi.data_source(df)
967
-
968
- def on_remove_filters():
969
- ds.reload(new_df)
970
-
971
- ui.button("reload data", on_click=on_remove_filters)
972
-
973
- ds.ui_select("name")
974
- ds.ui_aggrid()
975
- ```
976
-
977
- ---
978
- #### ui_select
979
-
980
- Dropdown Select Box
981
-
982
- ```python
983
- from nicegui import ui
984
- from ex4nicegui import bi
985
- import pandas as pd
986
-
987
- df = pd.DataFrame(
988
- {
989
- "name": list("aabcdf"),
990
- "cls": ["c1", "c2", "c1", "c1", "c3", None],
991
- "value": range(6),
992
- }
993
- )
994
-
995
- ds = bi.data_source(df)
996
-
997
- ds.ui_select("name")
998
- ```
999
-
1000
- The first parameter column specifies the column name of the data source.
1001
-
1002
- ---
1003
- Set the order of options using the parameter `sort_options`:
1004
- ```python
1005
- ds.ui_select("name", sort_options={"value": "desc", "name": "asc"})
1006
- ```
1007
-
1008
- ---
1009
- Set whether to exclude null values using the parameter `exclude_null_value`:
1010
- ```python
1011
- df = pd.DataFrame(
1012
- {
1013
- "cls": ["c1", "c2", "c1", "c1", "c3", None],
1014
- }
1015
- )
1016
-
1017
- ds = bi.data_source(df)
1018
- ds.ui_select("cls", exclude_null_value=True)
1019
- ```
1020
-
1021
- ---
1022
- You can set the parameters of the native nicegui select component through keyword arguments.
1023
-
1024
- Set default values through the value attribute:
1025
- ```python
1026
- ds.ui_select("cls",value=['c1','c2'])
1027
- ds.ui_select("cls",multiple=False,value='c1')
1028
- ```
1029
- For multiple selections (the parameter `multiple` is defaulted to True), `value` needs to be specified as a list. For single selections, `value` should be set to non-list.
1030
-
1031
- ---
1032
-
1033
-
1034
- #### ui_table
1035
-
1036
- Table
1037
-
1038
- ```python
1039
- from nicegui import ui
1040
- from ex4nicegui import bi
1041
- import pandas as pd
1042
-
1043
- data = pd.DataFrame({"name": ["f", "a", "c", "b"], "age": [1, 2, 3, 1]})
1044
- ds = bi.data_source(data)
1045
-
1046
- ds.ui_table(
1047
- columns=[
1048
- {"label": "new colA", "field": "colA", "sortable": True},
1049
- ]
1050
- )
1051
-
1052
- ```
1053
-
1054
- - The parameter `columns` are consistent with nicegui `ui.table`. The key value `field` corresponds to the column name of the data source, and if it does not exist, this configuration will not take effect
1055
- - The `rows` parameter will not take effect. Because the data source of the table is always controlled by the data source.
1056
-
1057
- ---
1058
-
1059
- #### ui_aggrid
1060
-
1061
-
1062
- ```python
1063
- from nicegui import ui
1064
- from ex4nicegui import bi
1065
- import pandas as pd
1066
-
1067
- data = pd.DataFrame(
1068
- {
1069
- "colA": list("abcde"),
1070
- "colB": [f"n{idx}" for idx in range(5)],
1071
- "colC": list(range(5)),
1072
- }
1073
- )
1074
- df = pd.DataFrame(data)
1075
-
1076
- source = bi.data_source(df)
1077
-
1078
- source.ui_aggrid(
1079
- options={
1080
- "columnDefs": [
1081
- {"headerName": "xx", "field": "no exists"},
1082
- {"headerName": "new colA", "field": "colA"},
1083
- {
1084
- "field": "colC",
1085
- "cellClassRules": {
1086
- "bg-red-300": "x < 3",
1087
- "bg-green-300": "x >= 3",
1088
- },
1089
- },
1090
- ],
1091
- "rowData": [{"colX": [1, 2, 3, 4, 5]}],
1092
- }
1093
- )
1094
- ```
1095
-
1096
- - The parameter `options` is consistent with nicegui `ui.aggrid`. The key value `field` in columnDefs corresponds to the column name of the data source, and if it does not exist, this configuration will not take effect.
1097
- - The `rowData` key value will not take effect. Because the data source of the table is always controlled by the data source.
1098
-
1099
-
1
+ Metadata-Version: 2.1
2
+ Name: ex4nicegui
3
+ Version: 0.6.7
4
+ Summary: Extension library based on nicegui, providing data responsive,BI functionality modules
5
+ Home-page: https://github.com/CrystalWindSnake/ex4nicegui
6
+ License: MIT
7
+ Keywords: nicegui,ex4nicegui,webui
8
+ Author: CrystalWindSnake
9
+ Author-email: 568166495@qq.com
10
+ Requires-Python: >=3.8,<4.0
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Dist: executing (>=2.0.1,<3.0.0)
19
+ Requires-Dist: nicegui (>=1.4.25,<2.0.0)
20
+ Requires-Dist: signe (>=0.4.14,<0.5.0)
21
+ Project-URL: Repository, https://github.com/CrystalWindSnake/ex4nicegui
22
+ Description-Content-Type: text/markdown
23
+
24
+ # ex4nicegui
25
+ [ENGLISH README](./README.en.md)
26
+
27
+
28
+ - [教程](#教程)
29
+ - [安装](#-安装)
30
+ - [使用](#-使用)
31
+ - [功能](#-功能)
32
+ - [BI 模块](#bi-模块)
33
+
34
+ 对 [nicegui](https://github.com/zauberzeug/nicegui) 做的扩展库。内置响应式组件,完全实现数据响应式界面编程。
35
+
36
+
37
+ ## 教程
38
+ [头条文章-秒杀官方实现,python界面库,去掉90%事件代码的nicegui](https://www.toutiao.com/item/7253786340574265860/)
39
+
40
+ [微信公众号-秒杀官方实现,python界面库,去掉90%事件代码的nicegui](https://mp.weixin.qq.com/s?__biz=MzUzNDk1MTc5Mw==&mid=2247486796&idx=1&sn=457ed6fb9d6a25145f7704d5197d670d&chksm=fa8daf52cdfa2644bede50ae7f2551162ecaedecafec231ee4ce8f28775a599f8669ecf06af1#rd)
41
+
42
+
43
+ ## 📦 安装
44
+
45
+ ```
46
+ pip install ex4nicegui -U
47
+ ```
48
+
49
+
50
+ ## 示例项目
51
+ - [入门](./examples/basic/)
52
+ - [todo list mvc](./examples/todomvc/)
53
+
54
+ ---
55
+
56
+ ## 🦄 使用
57
+
58
+ ```python
59
+ from nicegui import ui
60
+ from ex4nicegui import ref_computed, effect, to_ref
61
+ from ex4nicegui.reactive import rxui
62
+
63
+ # 定义响应式数据
64
+ r_input = to_ref("")
65
+
66
+ # 按照 nicegui 使用方式传入响应式数据即可
67
+ rxui.input(value=r_input)
68
+ rxui.label(r_input)
69
+
70
+ ui.run()
71
+ ```
72
+ ![](./asset/sync_input.gif)
73
+
74
+
75
+ ### 提供 echarts 图表组件
76
+
77
+ ```python
78
+ from nicegui import ui
79
+ from ex4nicegui import ref_computed, effect, to_ref
80
+ from ex4nicegui.reactive import rxui
81
+
82
+ r_input = to_ref("")
83
+
84
+ # ref_computed 创建只读响应式变量
85
+ # 函数中使用任意其他响应式变量,会自动关联
86
+ @ref_computed
87
+ def cp_echarts_opts():
88
+ return {
89
+ "title": {"text": r_input.value}, #字典中使用任意响应式变量,通过 .value 获取值
90
+ "xAxis": {
91
+ "type": "category",
92
+ "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
93
+ },
94
+ "yAxis": {"type": "value"},
95
+ "series": [
96
+ {
97
+ "data": [120, 200, 150, 80, 70, 110, 130],
98
+ "type": "bar",
99
+ "showBackground": True,
100
+ "backgroundStyle": {"color": "rgba(180, 180, 180, 0.2)"},
101
+ }
102
+ ],
103
+ }
104
+
105
+ input = rxui.input("输入内容,图表标题会同步", value=r_input)
106
+ # 通过响应式组件对象的 element 属性,获取原生 nicegui 组件对象
107
+ input.element.classes("w-full")
108
+
109
+ rxui.echarts(cp_echarts_opts)
110
+
111
+ ui.run()
112
+ ```
113
+ ![](./asset/asyc_echarts_title.gif)
114
+
115
+
116
+ ### echarts 图表鼠标事件
117
+
118
+ `on` 函数参数 `event_name` 以及 `query` 使用,查看[echarts 事件中文文档](https://echarts.apache.org/handbook/zh/concepts/event/)
119
+
120
+
121
+ 以下例子绑定鼠标单击事件
122
+ ```python
123
+ from nicegui import ui
124
+ from ex4nicegui.reactive import rxui
125
+
126
+ opts = {
127
+ "xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
128
+ "yAxis": {
129
+ "type": "category",
130
+ "data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
131
+ },
132
+ "series": [
133
+ {
134
+ "name": "first",
135
+ "type": "bar",
136
+ "data": [18203, 23489, 29034, 104970, 131744, 630230],
137
+ },
138
+ {
139
+ "name": "second",
140
+ "type": "bar",
141
+ "data": [19325, 23438, 31000, 121594, 134141, 681807],
142
+ },
143
+ ],
144
+ }
145
+
146
+ bar = rxui.echarts(opts)
147
+
148
+ def on_click(e: rxui.echarts.EChartsMouseEventArguments):
149
+ ui.notify(f"on_click:{e.seriesName}:{e.name}:{e.value}")
150
+
151
+
152
+ bar.on("click", on_click)
153
+ ```
154
+
155
+
156
+ 以下例子只针对指定系列触发鼠标划过事件
157
+ ```python
158
+ from nicegui import ui
159
+ from ex4nicegui.reactive import rxui
160
+
161
+ opts = {
162
+ "xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
163
+ "yAxis": {
164
+ "type": "category",
165
+ "data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
166
+ },
167
+ "series": [
168
+ {
169
+ "name": "first",
170
+ "type": "bar",
171
+ "data": [18203, 23489, 29034, 104970, 131744, 630230],
172
+ },
173
+ {
174
+ "name": "second",
175
+ "type": "bar",
176
+ "data": [19325, 23438, 31000, 121594, 134141, 681807],
177
+ },
178
+ ],
179
+ }
180
+
181
+ bar = rxui.echarts(opts)
182
+
183
+ def on_first_series_mouseover(e: rxui.echarts.EChartsMouseEventArguments):
184
+ ui.notify(f"on_first_series_mouseover:{e.seriesName}:{e.name}:{e.value}")
185
+
186
+
187
+ bar.on("mouseover", on_first_series_mouseover, query={"seriesName": "first"})
188
+
189
+ ui.run()
190
+ ```
191
+ ---
192
+
193
+
194
+ ## 响应式
195
+
196
+ ```python
197
+ from ex4nicegui import (
198
+ to_ref,
199
+ ref_computed,
200
+ on,
201
+ effect,
202
+ effect_refreshable,
203
+ batch,
204
+ event_batch,
205
+ deep_ref,
206
+ async_computed
207
+ )
208
+ ```
209
+ 常用 `to_ref`,`deep_ref`,`effect`,`ref_computed`,`on`,`async_computed`
210
+
211
+ ---
212
+
213
+ ### `to_ref`
214
+ 定义响应式对象,通过 `.value` 读写
215
+ ```python
216
+ a = to_ref(1)
217
+ b = to_ref("text")
218
+
219
+ a.value =2
220
+ b.value = 'new text'
221
+
222
+ print(a.value)
223
+ ```
224
+
225
+ 当值为复杂对象时,默认不会保持嵌套对象的响应性。
226
+ ```python
227
+ a = to_ref([1,2])
228
+
229
+ @effect
230
+ def _():
231
+ print('len:',len(a.value))
232
+
233
+ # 不会触发 effect
234
+ a.value.append(10)
235
+
236
+ # 整个替换则会触发
237
+ a.value = [1,2,10]
238
+ ```
239
+
240
+ 参数 `is_deep` 设置为 `True` 时,能得到深度响应能力
241
+
242
+ ```python
243
+ a = to_ref([1,2],is_deep=True)
244
+
245
+ @effect
246
+ def _():
247
+ print('len:',len(a.value))
248
+
249
+ # print 3
250
+ a.value.append(10)
251
+
252
+ ```
253
+
254
+ > `deep_ref` 等价于 `is_deep` 设置为 `True` 时的 `to_ref`
255
+
256
+ ---
257
+
258
+ ### `deep_ref`
259
+ 等价于 `is_deep` 设置为 `True` 时的 `to_ref`。
260
+
261
+ 当数据源为列表、字典或自定义类时,特别有用。通过 `.value` 获取的对象为代理对象
262
+ ```python
263
+ data = [1,2,3]
264
+ data_ref = deep_ref(data)
265
+
266
+ assert data_ref.value is not data
267
+ ```
268
+
269
+ 通过 `to_raw` 可以获取原始对象
270
+ ```python
271
+ from ex4nicegui import to_raw, deep_ref
272
+
273
+ data = [1, 2, 3]
274
+ data_ref = deep_ref(data)
275
+
276
+ assert data_ref.value is not data
277
+ assert to_raw(data_ref.value) is data
278
+ ```
279
+
280
+
281
+ ---
282
+
283
+ ### `effect`
284
+ 接受一个函数,自动监控函数中使用到的响应式对象变化,从而自动执行函数
285
+
286
+ ```python
287
+ a = to_ref(1)
288
+ b = to_ref("text")
289
+
290
+
291
+ @effect
292
+ def auto_run_when_ref_value():
293
+ print(f"a:{a.value}")
294
+
295
+
296
+ def change_value():
297
+ a.value = 2
298
+ b.value = "new text"
299
+
300
+
301
+ ui.button("change", on_click=change_value)
302
+ ```
303
+
304
+ 首次执行 effect ,函数`auto_run_when_ref_value`将被执行一次.之后点击按钮,改变 `a` 的值(通过 `a.value`),函数`auto_run_when_ref_value`再次执行
305
+
306
+ > 切忌把大量数据处理逻辑分散在多个 `on` 或 `effect` 中,`on` 或 `effect` 中应该大部分为界面操作逻辑,而非响应式数据处理逻辑
307
+
308
+ ---
309
+
310
+ ### `ref_computed`
311
+ 与 `effect` 具备一样的功能,`ref_computed` 还能从函数中返回结果。一般用于从 `to_ref` 中进行二次计算
312
+
313
+ ```python
314
+ a = to_ref(1)
315
+ a_square = ref_computed(lambda: a.value * 2)
316
+
317
+
318
+ @effect
319
+ def effect1():
320
+ print(f"a_square:{a_square.value}")
321
+
322
+
323
+ def change_value():
324
+ a.value = 2
325
+
326
+
327
+ ui.button("change", on_click=change_value)
328
+ ```
329
+
330
+ 点击按钮后,`a.value` 值被修改,从而触发 `a_square` 重新计算.由于 `effect1` 中读取了 `a_square` 的值,从而触发 `effect1` 执行
331
+
332
+ > `ref_computed` 是只读的 `to_ref`
333
+
334
+
335
+ 如果你更喜欢通过类组织代码,`ref_computed` 同样支持作用到实例方法上
336
+
337
+ ```python
338
+ class MyState:
339
+ def __init__(self) -> None:
340
+ self.r_text = to_ref("")
341
+
342
+ @ref_computed
343
+ def post_text(self):
344
+ return self.r_text.value + "post"
345
+
346
+ state = MyState()
347
+
348
+ rxui.input(value=state.r_text)
349
+ rxui.label(state.post_text)
350
+ ```
351
+
352
+ ---
353
+
354
+ ### `async_computed`
355
+ 二次计算中需要使用异步函数时,使用 `async_computed`
356
+ ```python
357
+
358
+ # 模拟长时间执行的异步函数
359
+ async def long_time_query(input: str):
360
+ await asyncio.sleep(2)
361
+ num = random.randint(20, 100)
362
+ return f"query result[{input=}]:{num=}"
363
+
364
+
365
+ search = to_ref("")
366
+ evaluating = to_ref(False)
367
+
368
+ @async_computed(search, evaluating=evaluating, init="")
369
+ async def search_result():
370
+ return await long_time_query(search.value)
371
+
372
+ rxui.lazy_input(value=search)
373
+
374
+ rxui.label(
375
+ lambda: "查询中" if evaluating.value else "上方输入框输入内容并回车搜索"
376
+ )
377
+ rxui.label(search_result)
378
+
379
+ ```
380
+
381
+ - `async_computed` 第一个参数必须明确指定需要监控的响应式数据. 使用列表可以同时指定多个响应式数据
382
+ - 参数 `evaluating` 为 bool 类型的响应式数据,当异步函数执行中,此变量值为 `True`,计算结束后为 `False`
383
+ - 参数 `init` 指定初始结果
384
+
385
+
386
+ ---
387
+
388
+ ### `on`
389
+ 类似 `effect` 的功能,但是 `on` 需要明确指定监控的响应式对象
390
+
391
+ ```python
392
+
393
+ a1 = to_ref(1)
394
+ a2 = to_ref(10)
395
+ b = to_ref("text")
396
+
397
+
398
+ @on(a1)
399
+ def watch_a1_only():
400
+ print(f"watch_a1_only ... a1:{a1.value},a2:{a2.value}")
401
+
402
+
403
+ @on([a1, b], onchanges=True)
404
+ def watch_a1_and_b():
405
+ print(f"watch_a1_and_b ... a1:{a1.value},a2:{a2.value},b:{b.value}")
406
+
407
+
408
+ def change_a1():
409
+ a1.value += 1
410
+ ui.notify("change_a1")
411
+
412
+
413
+ ui.button("change a1", on_click=change_a1)
414
+
415
+
416
+ def change_a2():
417
+ a2.value += 1
418
+ ui.notify("change_a2")
419
+
420
+
421
+ ui.button("change a2", on_click=change_a2)
422
+
423
+
424
+ def change_b():
425
+ b.value += "x"
426
+ ui.notify("change_b")
427
+
428
+
429
+ ui.button("change b", on_click=change_b)
430
+
431
+ ```
432
+
433
+ - 参数 `onchanges` 为 True 时(默认值为 False),指定的函数不会在绑定时执行
434
+
435
+
436
+ > 切忌把大量数据处理逻辑分散在多个 `on` 或 `effect` 中,`on` 或 `effect` 中应该大部分为界面操作逻辑,而非响应式数据处理逻辑
437
+
438
+ ---
439
+
440
+ ### `new_scope`
441
+
442
+ 默认情况下,所有检测函数在客户端连接断开时自动销毁。如果需要更细粒度的控制,可以使用 `new_scope`
443
+
444
+ ```python
445
+ from nicegui import ui
446
+ from ex4nicegui import rxui, to_ref, effect, new_scope
447
+
448
+ a = to_ref(0.0)
449
+
450
+ scope1 = new_scope()
451
+
452
+ @scope1.run
453
+ def _():
454
+ @effect
455
+ def _():
456
+ print(f"scope 1:{a.value}")
457
+
458
+
459
+ rxui.number(value=a)
460
+ rxui.button("dispose scope 1", on_click=scope1.dispose)
461
+ ```
462
+
463
+ ---
464
+
465
+
466
+ ## 组件功能
467
+
468
+ ### vmodel
469
+ 在表单输入元素或组件上创建双向绑定。
470
+
471
+ 简单值类型的 `ref` 默认支持双向绑定
472
+ ```python
473
+ from ex4nicegui import rxui, to_ref, deep_ref
474
+
475
+ data = to_ref("init")
476
+
477
+ rxui.label(lambda: f"{data.value=}")
478
+ # 默认就是双向绑定
479
+ rxui.input(value=data)
480
+ ```
481
+
482
+ - 简单值类型一般是 `str`,`int` 等不可变值类型
483
+
484
+ 当使用复杂数据结构时,会使用 `deep_ref` 保持嵌套值的响应性
485
+ ```python
486
+ data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
487
+
488
+ rxui.label(lambda: f"{data.value=!s}")
489
+
490
+ # 当前版本没有任何绑定效果.或许未来的版本可以解决
491
+ rxui.input(value=data.value["a"])
492
+
493
+ # 只读绑定.其他途径修改了 `data.value["a"]` ,此输入框会同步,但反过来不行
494
+ rxui.input(value=lambda: data.value["a"])
495
+
496
+ # 要使用 vmodel 才能双向绑定
497
+ rxui.input(value=rxui.vmodel(data.value["a"]))
498
+ ```
499
+
500
+ - 第一个输入框将完全失去响应性,因为代码等价于直接传入一个数值`1`
501
+ - 第二个输入框由于使用函数,将得到读取响应性(第三个输入框输入值,将得到同步)
502
+ - 第三个输入框,使用 `rxui.vmodel` 包裹,即可实现双向绑定
503
+
504
+ 多数在配合 `vfor` 时使用 `vmodel`,可参考 [todo list 案例](./examples/todomvc/)
505
+
506
+
507
+ ### vfor
508
+ 基于列表响应式数据,渲染列表组件。每项组件按需更新。数据项支持字典或任意类型对象
509
+
510
+ ```python
511
+ from nicegui import ui
512
+ from ex4nicegui.reactive import rxui
513
+ from ex4nicegui import deep_ref, ref_computed
514
+ from typing import Dict
515
+
516
+ # refs
517
+ items = deep_ref(
518
+ [
519
+ {"id": 1, "message": "foo", "done": False},
520
+ {"id": 2, "message": "bar", "done": True},
521
+ ]
522
+ )
523
+
524
+ # ref_computeds
525
+ @ref_computed
526
+ def done_count_info():
527
+ return f"done count:{sum(item['done'] for item in items.value)}"
528
+
529
+ # method
530
+ def check():
531
+ for item in items.value:
532
+ item["done"] = not item["done"]
533
+
534
+
535
+ # ui
536
+ rxui.label(done_count_info)
537
+ ui.button("check", on_click=check)
538
+
539
+
540
+ @rxui.vfor(items,key='id')
541
+ def _(store: rxui.VforStore[Dict]):
542
+ # 函数中构建每一行数据的界面
543
+ item = store.get() # 通过 store.get 获取对应行的响应式对象(相当于每行的数据 to_ref(...))
544
+ mes = rxui.vmodel(item.value['message']) # 复杂结构默认没有双向绑定,需要使用 `vmodel`
545
+
546
+ # 输入框输入内容,可以看到单选框的标题同步变化
547
+ with ui.card():
548
+ with ui.row():
549
+ rxui.input(value=mes)
550
+ rxui.label(lambda: f"{mes.value=!s}")
551
+ rxui.checkbox(text=mes, value=rxui.vmodel(item.value['done']))
552
+
553
+ ```
554
+
555
+ - `rxui.vfor` 装饰器到自定义函数
556
+ - 第一个参数传入响应式列表。列表中每一项可以是字典或其他对象(`dataclasses` 等等)
557
+ - 第二个参数 `key`: 为了可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你可以为每个元素对应的块提供一个唯一的 key 。默认情况使用列表元素索引。
558
+ - 自定义函数带有一个参数。通过 `store.get` 可以获取当前行的响应式对象
559
+
560
+ > vfor 渲染的项目,只有在新增数据时,才会创建
561
+
562
+
563
+ ---
564
+
565
+ ### 绑定类名
566
+
567
+ 所有的组件类提供 `bind_classes` 用于绑定 `class`,支持三种不同的数据结构。
568
+
569
+ 绑定字典
570
+
571
+ ```python
572
+ bg_color = to_ref(False)
573
+ has_error = to_ref(False)
574
+
575
+ rxui.label("test").bind_classes({"bg-blue": bg_color, "text-red": has_error})
576
+
577
+ rxui.switch("bg_color", value=bg_color)
578
+ rxui.switch("has_error", value=has_error)
579
+ ```
580
+
581
+ 字典键值为类名,对应值为 bool 的响应式变量。当响应式值为 `True`,类名应用到组件 class
582
+
583
+
584
+ ---
585
+
586
+ 绑定返回值为字典的响应式变量
587
+
588
+ ```python
589
+ bg_color = to_ref(False)
590
+ has_error = to_ref(False)
591
+
592
+ class_obj = ref_computed(
593
+ lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
594
+ )
595
+
596
+ rxui.switch("bg_color", value=bg_color)
597
+ rxui.switch("has_error", value=has_error)
598
+ rxui.label("bind to ref_computed").bind_classes(class_obj)
599
+ # or direct function passing
600
+ rxui.label("bind to ref_computed").bind_classes(
601
+ lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
602
+ )
603
+ ```
604
+
605
+ ---
606
+
607
+ 绑定为列表
608
+
609
+ ```python
610
+ bg_color = to_ref("red")
611
+ bg_color_class = ref_computed(lambda: f"bg-{bg_color.value}")
612
+
613
+ text_color = to_ref("green")
614
+ text_color_class = ref_computed(lambda: f"text-{text_color.value}")
615
+
616
+ rxui.select(["red", "green", "yellow"], label="bg color", value=bg_color)
617
+ rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
618
+
619
+ rxui.label("binding to arrays").bind_classes([bg_color_class, text_color_class])
620
+
621
+ ```
622
+
623
+ 列表中每个元素为返回类名的响应式变量
624
+
625
+ ---
626
+
627
+ ### bind-style
628
+
629
+ ```python
630
+ from nicegui import ui
631
+ from ex4nicegui.reactive import rxui
632
+ from ex4nicegui.utils.signals import to_ref
633
+
634
+
635
+ bg_color = to_ref("blue")
636
+ text_color = to_ref("red")
637
+
638
+ rxui.label("test").bind_style(
639
+ {
640
+ "background-color": bg_color,
641
+ "color": text_color,
642
+ }
643
+ )
644
+
645
+ rxui.select(["blue", "green", "yellow"], label="bg color", value=bg_color)
646
+ rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
647
+ ```
648
+
649
+ `bind_style` 传入字典,`key` 为样式名字,`value` 为样式值,响应式字符串
650
+
651
+ ---
652
+
653
+ ### bind_prop
654
+
655
+ 绑定单个属性
656
+
657
+ ```python
658
+
659
+ label = to_ref("hello")
660
+
661
+ rxui.button("").bind_prop("label", label)
662
+ # 允许使用函数
663
+ rxui.button("").bind_prop(
664
+ "label", lambda: f"{label.value} world"
665
+ )
666
+
667
+ rxui.input(value=label)
668
+ ```
669
+
670
+
671
+ ---
672
+
673
+ ### rxui.echarts
674
+ 使用 echarts 制作图表
675
+
676
+ ---
677
+
678
+ #### rxui.echarts.from_javascript
679
+ 从 javascript 代码创建 echart
680
+
681
+ ```python
682
+ from pathlib import Path
683
+
684
+ rxui.echarts.from_javascript(Path("code.js"))
685
+ # or
686
+ rxui.echarts.from_javascript(
687
+ """
688
+ (myChart) => {
689
+
690
+ option = {
691
+ xAxis: {
692
+ type: 'category',
693
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
694
+ },
695
+ yAxis: {
696
+ type: 'value'
697
+ },
698
+ series: [
699
+ {
700
+ data: [120, 200, 150, 80, 70, 110, 130],
701
+ type: 'bar'
702
+ }
703
+ ]
704
+ };
705
+
706
+ myChart.setOption(option);
707
+ }
708
+ """
709
+ )
710
+ ```
711
+
712
+ - 函数第一个参数为 echart 实例对象.你需要在函数中通过 `setOption` 完成图表配置
713
+
714
+ 函数也有第二个参数,为 `echarts` 全局对象,你可以通过 `echarts.registerMap` 注册地图。
715
+
716
+ ```python
717
+ rxui.echarts.from_javascript(
718
+ """
719
+ (chart,echarts) =>{
720
+
721
+ fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
722
+ .then(response => response.json())
723
+ .then(data => {
724
+ echarts.registerMap('test_map', data);
725
+
726
+ chart.setOption({
727
+ geo: {
728
+ map: 'test_map',
729
+ roam: true,
730
+ },
731
+ tooltip: {},
732
+ legend: {},
733
+ series: [],
734
+ });
735
+ });
736
+ }
737
+ """
738
+ )
739
+ ```
740
+
741
+ ---
742
+
743
+ #### rxui.echarts.register_map
744
+ 注册地图.
745
+
746
+ ```python
747
+ rxui.echarts.register_map(
748
+ "china", "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
749
+ )
750
+
751
+ rxui.echarts(
752
+ {
753
+ "geo": {
754
+ "map": "china",
755
+ "roam": True,
756
+ },
757
+ "tooltip": {},
758
+ "legend": {},
759
+ "series": [],
760
+ }
761
+ )
762
+ ```
763
+
764
+ - 参数 `map_name` 为自定义的地图名字。注意在图表配置中 `map` 必需对应注册的名字
765
+ - 参数 `src` 为有效的地图数据网络链接。
766
+
767
+
768
+ 如果是 svg 数据,需要设置参数 `type="svg"`
769
+ ```python
770
+ rxui.echarts.register_map("svg-rect", "/test/svg", type="svg")
771
+ ```
772
+
773
+
774
+
775
+ 你也可以直接提供本地地图数据的json文件路径对象(Path)
776
+ ```python
777
+ from pathlib import Path
778
+
779
+ rxui.echarts.register_map(
780
+ "china", Path("map-data.json")
781
+ )
782
+ ```
783
+
784
+ ---
785
+
786
+ ### gsap
787
+ js 动画库. [gsap文档](https://gsap.com/docs/v3/)
788
+
789
+ ```python
790
+ from nicegui import ui
791
+ from ex4nicegui import gsap
792
+ ```
793
+
794
+ #### gsap.from_
795
+
796
+ 设置起始属性,动画将从设置的属性过渡到原始位置
797
+
798
+ ```python
799
+
800
+ ui.label("test from").classes("target")
801
+ gsap.from_(".target", {"x": 50,'duration':1})
802
+
803
+ ```
804
+
805
+ 画面加载后,文本起始位置在往右偏移 50px 处,在 1秒 内移动到原始位置上
806
+
807
+ - 参数 `targets` 为 css 选择器
808
+ - 参数 `vars` 为属性值,具体参考 gsap 文档
809
+
810
+ ---
811
+
812
+ #### gsap.to
813
+
814
+ 设置结束属性,动画将从原始属性过渡到设置的属性
815
+
816
+ ```python
817
+
818
+ ui.label("test to").classes("target")
819
+ gsap.to(".target", {"x": 50,'duration':1})
820
+
821
+ ```
822
+
823
+ 画面加载后,文本在 1秒 内,从原始位置往后移动 50px
824
+
825
+ - 参数 `targets` 为 css 选择器
826
+ - 参数 `vars` 为属性值,具体参考 gsap 文档
827
+
828
+ ---
829
+ #### gsap.run_script
830
+
831
+ 通过编写 js 设置动画
832
+
833
+ ```python
834
+
835
+ gsap.run_script(
836
+ r"""function setGsap(gsap) {
837
+ gsap.to('.target',{"duration": 0.3,y:60})
838
+ }
839
+ """)
840
+ ```
841
+
842
+ - 参数 `script` 可以为文本或 js 后缀的文件 `Path`
843
+ - 定义的 js 函数名字并不影响运行,第一个参数为 gsap 对象
844
+
845
+ ---
846
+
847
+ ### tab_panels
848
+
849
+ 相比较于 `nicegui.ui.tab_panels` , `rxui.tab_panels` 没有参数 `tabs`。在数据响应式机制下,`tabs` 与 `tab_panels` 联动只需要通过参数 `value` 即可。
850
+
851
+ ```python
852
+ from nicegui import ui
853
+ from ex4nicegui import rxui, to_ref
854
+
855
+ names = ["Tab 1", "Tab 2", "Tab 3"]
856
+ current_tab = to_ref(names[0])
857
+
858
+ with rxui.tabs(current_tab):
859
+ for name in names:
860
+ rxui.tab(name)
861
+
862
+ with rxui.tab_panels(current_tab):
863
+ for name in names:
864
+ with rxui.tab_panel(name):
865
+ ui.label(f"Content of {name}")
866
+ ```
867
+
868
+ 这是因为,数据响应机制下,组件联动是通过中间数据层(`to_ref`)实现的。因此,`tab_panels` 可以与其他组件联动(只需要保证使用同样的 `ref` 对象即可)
869
+
870
+ ```python
871
+ names = ["Tab 1", "Tab 2", "Tab 3"]
872
+ current_tab = to_ref(names[0])
873
+
874
+
875
+ with rxui.tab_panels(current_tab):
876
+ for name in names:
877
+ with rxui.tab_panel(name):
878
+ ui.label(f"Content of {name}")
879
+
880
+ # tabs 不必在 panels 前面
881
+ with rxui.tabs(current_tab):
882
+ for name in names:
883
+ rxui.tab(name)
884
+
885
+ rxui.select(names, value=current_tab)
886
+ rxui.radio(names, value=current_tab).props("inline")
887
+
888
+ rxui.label(lambda: f"当前 tab 为:{current_tab.value}")
889
+ ```
890
+
891
+
892
+ ---
893
+
894
+ ## BI 模块
895
+
896
+ 以最精简的 apis 创建可交互的数据可视化报表
897
+
898
+ ![](./asset/bi_examples1.gif)
899
+
900
+ ```python
901
+ from nicegui import ui
902
+ import pandas as pd
903
+ import numpy as np
904
+ from ex4nicegui import bi
905
+ from ex4nicegui.reactive import rxui
906
+ from ex4nicegui import effect, effect_refreshable
907
+ from pyecharts.charts import Bar
908
+
909
+
910
+ # data ready
911
+ def gen_data():
912
+ np.random.seed(265)
913
+ field1 = ["a1", "a2", "a3", "a4"]
914
+ field2 = [f"name{i}" for i in range(1, 11)]
915
+ df = (
916
+ pd.MultiIndex.from_product([field1, field2], names=["cat", "name"])
917
+ .to_frame()
918
+ .reset_index(drop=True)
919
+ )
920
+ df[["idc1", "idc2"]] = np.random.randint(50, 1000, size=(len(df), 2))
921
+ return df
922
+
923
+
924
+ df = gen_data()
925
+
926
+ # 创建数据源
927
+ ds = bi.data_source(df)
928
+
929
+ # ui
930
+ ui.query(".nicegui-content").classes("items-stretch no-wrap")
931
+
932
+ with ui.row().classes("justify-evenly"):
933
+ # 基于数据源 `ds` 创建界面组件
934
+ ds.ui_select("cat").classes("min-w-[10rem]")
935
+ ds.ui_select("name").classes("min-w-[10rem]")
936
+
937
+
938
+ with ui.grid(columns=2):
939
+ # 使用字典配置图表
940
+ @ds.ui_echarts
941
+ def bar1(data: pd.DataFrame):
942
+ data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
943
+
944
+ return {
945
+ "xAxis": {"type": "value"},
946
+ "yAxis": {
947
+ "type": "category",
948
+ "data": data["name"].tolist(),
949
+ "inverse": True,
950
+ },
951
+ "legend": {"textStyle": {"color": "gray"}},
952
+ "series": [
953
+ {"type": "bar", "name": "idc1", "data": data["idc1"].tolist()},
954
+ {"type": "bar", "name": "idc2", "data": data["idc2"].tolist()},
955
+ ],
956
+ }
957
+
958
+ bar1.classes("h-[20rem]")
959
+
960
+ # 使用pyecharts配置图表
961
+ @ds.ui_echarts
962
+ def bar2(data: pd.DataFrame):
963
+ data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
964
+
965
+ return (
966
+ Bar()
967
+ .add_xaxis(data["name"].tolist())
968
+ .add_yaxis("idc1", data["idc1"].tolist())
969
+ .add_yaxis("idc2", data["idc2"].tolist())
970
+ )
971
+
972
+ bar2.classes("h-[20rem]")
973
+
974
+ # 绑定点击事件,即可实现跳转
975
+ @bar2.on_chart_click
976
+ def _(e: rxui.echarts.EChartsMouseEventArguments):
977
+ ui.open(f"/details/{e.name}", new_tab=True)
978
+
979
+
980
+ # 利用响应式机制,你可以随意组合原生 nicegui 组件
981
+ label_a1_total = ui.label("")
982
+
983
+
984
+ # ds 有变化,都会触发此函数
985
+ @effect
986
+ def _():
987
+ # filtered_data 为过滤后的 DataFrame
988
+ df = ds.filtered_data
989
+ total = df[df["cat"] == "a1"]["idc1"].sum()
990
+ label_a1_total.text = f"idc1 total(cat==a1):{total}"
991
+
992
+
993
+ # 你也可以使用 `effect_refreshable`,但需要注意函数中的组件每次都被重建
994
+ @effect_refreshable
995
+ def _():
996
+ df = ds.filtered_data
997
+ total = df[df["cat"] == "a2"]["idc1"].sum()
998
+ ui.label(f"idc1 total(cat==a2):{total}")
999
+
1000
+
1001
+ # 当点击图表系列时,跳转的页面
1002
+ @ui.page("/details/{name}")
1003
+ def details_page(name: str):
1004
+ ui.label("This table data will not change")
1005
+ ui.aggrid.from_pandas(ds.data.query(f'name=="{name}"'))
1006
+
1007
+ ui.label("This table will change when the homepage data changes. ")
1008
+
1009
+ @bi.data_source
1010
+ def new_ds():
1011
+ return ds.filtered_data[["name", "idc1", "idc2"]]
1012
+
1013
+ new_ds.ui_aggrid()
1014
+
1015
+
1016
+ ui.run()
1017
+ ```
1018
+
1019
+
1020
+
1021
+ ### 细节
1022
+
1023
+
1024
+
1025
+
1026
+ #### `bi.data_source`
1027
+ 数据源是 BI 模块的核心概念,所有数据的联动基于此展开。当前版本(0.4.3)中,有两种创建数据源的方式
1028
+
1029
+ 接收 `pandas` `DataFrame`:
1030
+ ```python
1031
+ from nicegui import ui
1032
+ from ex4nicegui import bi
1033
+ import pandas as pd
1034
+
1035
+ df = pd.DataFrame(
1036
+ {
1037
+ "name": list("aabcdf"),
1038
+ "cls": ["c1", "c2", "c1", "c1", "c3", None],
1039
+ "value": range(6),
1040
+ }
1041
+ )
1042
+
1043
+ ds = bi.data_source(df)
1044
+ ```
1045
+
1046
+ ---
1047
+ 有时候,我们希望基于另一个数据源创建新的数据源,此时可以使用装饰器创建联动数据源:
1048
+ ```python
1049
+ df = pd.DataFrame(
1050
+ {
1051
+ "name": list("aabcdf"),
1052
+ "cls": ["c1", "c2", "c1", "c1", "c3", None],
1053
+ "value": range(6),
1054
+ }
1055
+ )
1056
+
1057
+ ds = bi.data_source(df)
1058
+
1059
+ @bi.data_source
1060
+ def new_ds():
1061
+ # df is pd.DataFrame
1062
+ df = ds.filtered_data
1063
+ df=df.copy()
1064
+ df['value'] = df['value'] * 100
1065
+ return df
1066
+
1067
+ ds.ui_select('name')
1068
+ new_ds.ui_aggrid()
1069
+
1070
+ ```
1071
+
1072
+ 注意,由于 `new_ds` 中使用了 `ds.filtered_data` ,因此 `ds` 的变动会触发 `new_ds` 的联动变化,从而导致 `new_ds` 创建的表格组件产生变化
1073
+
1074
+ ---
1075
+ 通过 `ds.remove_filters` 方法,移除所有筛选状态:
1076
+ ```python
1077
+ ds = bi.data_source(df)
1078
+
1079
+ def on_remove_filters():
1080
+ ds.remove_filters()
1081
+
1082
+ ui.button("remove all filters", on_click=on_remove_filters)
1083
+
1084
+ ds.ui_select("name")
1085
+ ds.ui_aggrid()
1086
+ ```
1087
+ ---
1088
+
1089
+ 通过 `ds.reload` 方法,重设数据源:
1090
+ ```python
1091
+
1092
+ df = pd.DataFrame(
1093
+ {
1094
+ "name": list("aabcdf"),
1095
+ "cls": ["c1", "c2", "c1", "c1", "c3", None],
1096
+ "value": range(6),
1097
+ }
1098
+ )
1099
+
1100
+ new_df = pd.DataFrame(
1101
+ {
1102
+ "name": list("xxyyds"),
1103
+ "cls": ["cla1", "cla2", "cla3", "cla3", "cla3", None],
1104
+ "value": range(100, 106),
1105
+ }
1106
+ )
1107
+
1108
+ ds = bi.data_source(df)
1109
+
1110
+ def on_remove_filters():
1111
+ ds.reload(new_df)
1112
+
1113
+ ui.button("reload data", on_click=on_remove_filters)
1114
+
1115
+ ds.ui_select("name")
1116
+ ds.ui_aggrid()
1117
+ ```
1118
+
1119
+ ---
1120
+ #### ui_select
1121
+
1122
+ ```python
1123
+ from nicegui import ui
1124
+ from ex4nicegui import bi
1125
+ import pandas as pd
1126
+
1127
+ df = pd.DataFrame(
1128
+ {
1129
+ "name": list("aabcdf"),
1130
+ "cls": ["c1", "c2", "c1", "c1", "c3", None],
1131
+ "value": range(6),
1132
+ }
1133
+ )
1134
+
1135
+ ds = bi.data_source(df)
1136
+
1137
+ ds.ui_select("name")
1138
+ ```
1139
+
1140
+ 第一个参数 column 指定数据源的列名
1141
+
1142
+ ---
1143
+ 通过参数 `sort_options` 设置选项顺序:
1144
+ ```python
1145
+ ds.ui_select("name", sort_options={"value": "desc", "name": "asc"})
1146
+
1147
+ ```
1148
+
1149
+ ---
1150
+ 参数 `exclude_null_value` 设置是否排除空值:
1151
+ ```python
1152
+ df = pd.DataFrame(
1153
+ {
1154
+ "cls": ["c1", "c2", "c1", "c1", "c3", None],
1155
+ }
1156
+ )
1157
+
1158
+ ds = bi.data_source(df)
1159
+ ds.ui_select("cls", exclude_null_value=True)
1160
+ ```
1161
+
1162
+ ---
1163
+
1164
+ 你可以通过关键字参数,设置原生 nicegui select 组件的参数.
1165
+
1166
+ 通过 value 属性,设置默认值:
1167
+ ```python
1168
+ ds.ui_select("cls",value=['c1','c2'])
1169
+ ds.ui_select("cls",multiple=False,value='c1')
1170
+
1171
+ ```
1172
+
1173
+ 多选时(参数 `multiple` 默认为 True),`value` 需要指定为 list
1174
+
1175
+ 单选时,`value` 设置为非 list
1176
+
1177
+ ---
1178
+
1179
+ #### ui_table
1180
+
1181
+ 表格
1182
+
1183
+ ```python
1184
+ from nicegui import ui
1185
+ from ex4nicegui import bi
1186
+ import pandas as pd
1187
+
1188
+ data = pd.DataFrame({"name": ["f", "a", "c", "b"], "age": [1, 2, 3, 1]})
1189
+ ds = bi.data_source(data)
1190
+
1191
+ ds.ui_table(
1192
+ columns=[
1193
+ {"label": "new colA", "field": "colA", "sortable": True},
1194
+ ]
1195
+ )
1196
+
1197
+ ```
1198
+
1199
+ - columns 与 nicegui `ui.table` 一致。其中 键值 `field` 对应数据源的列名,如果不存在,则该配置不会生效
1200
+ - rows 参数不会生效。因为表格的数据源始终由 data source 控制
1201
+
1202
+ ---
1203
+
1204
+ #### ui_aggrid
1205
+
1206
+
1207
+ ```python
1208
+ from nicegui import ui
1209
+ from ex4nicegui import bi
1210
+ import pandas as pd
1211
+
1212
+ data = pd.DataFrame(
1213
+ {
1214
+ "colA": list("abcde"),
1215
+ "colB": [f"n{idx}" for idx in range(5)],
1216
+ "colC": list(range(5)),
1217
+ }
1218
+ )
1219
+ df = pd.DataFrame(data)
1220
+
1221
+ source = bi.data_source(df)
1222
+
1223
+ source.ui_aggrid(
1224
+ options={
1225
+ "columnDefs": [
1226
+ {"headerName": "xx", "field": "no exists"},
1227
+ {"headerName": "new colA", "field": "colA"},
1228
+ {
1229
+ "field": "colC",
1230
+ "cellClassRules": {
1231
+ "bg-red-300": "x < 3",
1232
+ "bg-green-300": "x >= 3",
1233
+ },
1234
+ },
1235
+ ],
1236
+ "rowData": [{"colX": [1, 2, 3, 4, 5]}],
1237
+ }
1238
+ )
1239
+ ```
1240
+
1241
+ - 参数 options 与 nicegui `ui.aggrid` 一致。其中 `columnDefs` 中的键值 `field` 对应数据源的列名,如果不存在,则该配置不会生效
1242
+ - `rowData` 键值不会生效。因为表格的数据源始终由 data source 控制
1243
+
1244
+
1245
+