Name | ex4nicegui JSON |
Version |
0.6.6
JSON |
| download |
home_page | None |
Summary | Extension library based on nicegui, providing data responsive,BI functionality modules |
upload_time | 2024-05-15 16:43:08 |
maintainer | None |
docs_url | None |
author | carson_jia |
requires_python | >=3.8 |
license | MIT license |
keywords |
nicegui
ex4nicegui
webui
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# ex4nicegui
[δΈζ README](./README.md)
- [Install](#-install)
- [Usage](#-usage)
- [Features](#-features)
- [BI Module](#bi-module)
An extension library for [nicegui](https://github.com/zauberzeug/nicegui). It has built-in responsive components and fully implements data-responsive interface programming.
## π¦ Install
```
pip install ex4nicegui -U
```
## examples
- [basic](./examples/basic/)
- [todo list mvc](./examples/todomvc/)
## π¦ Usage
```python
from nicegui import ui
from ex4nicegui import ref_computed, effect, to_ref
from ex4nicegui.reactive import rxui
# Define responsive data
r_input = to_ref("")
# Pass in the responsive data according to the nicegui usage method.
rxui.input(value=r_input)
rxui.label(r_input)
ui.run()
```
![](./asset/sync_input.gif)
## π Features
### echarts components
```python
from nicegui import ui
from ex4nicegui import ref_computed, effect, to_ref
from ex4nicegui.reactive import rxui
r_input = to_ref("")
# ref_computed Creates a read-only reactive variable
# Functions can use any other reactive variables automatically when they are associated
@ref_computed
def cp_echarts_opts():
return {
"title": {"text": r_input.value}, #In the dictionary, use any reactive variable. Get the value by .value
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
"yAxis": {"type": "value"},
"series": [
{
"data": [120, 200, 150, 80, 70, 110, 130],
"type": "bar",
"showBackground": True,
"backgroundStyle": {"color": "rgba(180, 180, 180, 0.2)"},
}
],
}
input = rxui.input("Enter the content, and the chart title will be synchronized", value=r_input)
# Get the native nicegui component object through the element attribute of the responsive component object
input.element.classes("w-full")
rxui.echarts(cp_echarts_opts).classes('h-[20rem]')
ui.run()
```
![](./asset/asyc_echarts_title.gif)
### echarts mouse events
the `on` function parameters `event_name` and `query` to view the[echarts English Documentation](https://echarts.apache.org/handbook/en/concepts/event/)
The following example binds the mouse click event
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
opts = {
"xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
"yAxis": {
"type": "category",
"data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
},
"series": [
{
"name": "first",
"type": "bar",
"data": [18203, 23489, 29034, 104970, 131744, 630230],
},
{
"name": "second",
"type": "bar",
"data": [19325, 23438, 31000, 121594, 134141, 681807],
},
],
}
bar = rxui.echarts(opts)
def on_click(e: rxui.echarts.EChartsMouseEventArguments):
ui.notify(f"on_click:{e.seriesName}:{e.name}:{e.value}")
bar.on("click", on_click)
```
The following example only triggers the mouseover event for a specified series.
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
opts = {
"xAxis": {"type": "value", "boundaryGap": [0, 0.01]},
"yAxis": {
"type": "category",
"data": ["Brazil", "Indonesia", "USA", "India", "China", "World"],
},
"series": [
{
"name": "first",
"type": "bar",
"data": [18203, 23489, 29034, 104970, 131744, 630230],
},
{
"name": "second",
"type": "bar",
"data": [19325, 23438, 31000, 121594, 134141, 681807],
},
],
}
bar = rxui.echarts(opts)
def on_first_series_mouseover(e: rxui.echarts.EChartsMouseEventArguments):
ui.notify(f"on_first_series_mouseover:{e.seriesName}:{e.name}:{e.value}")
bar.on("mouseover", on_first_series_mouseover, query={"seriesName": "first"})
ui.run()
```
---
## responsive
```python
from ex4nicegui import (
to_ref,
ref_computed,
on,
effect,
effect_refreshable,
batch,
event_batch,
deep_ref,
)
```
Commonly used `to_ref`,`deep_ref`,`effect`,`ref_computed`,`on`
### `to_ref`
Defines responsive objects, read and written by `.value`.
```python
a = to_ref(1)
b = to_ref("text")
a.value =2
b.value = 'new text'
print(a.value)
```
When the value is a complex object, responsiveness of nested objects is not maintained by default.
```python
a = to_ref([1,2])
@effect
def _().
print(len(a.value))
# doesn't trigger the effect
a.value.append(10)
# the whole substitution will be triggered
a.value = [1,2,10]
```
Depth responsiveness is obtained when `is_deep` is set to `True`.
```python
a = to_ref([1,2],is_deep=True)
@effect
def _():
print('len:',len(a.value))
# print 3
a.value.append(10)
```
> `deep_ref` is equivalent to `to_ref` if `is_deep` is set to `True`.
---
### `deep_ref`
Equivalent to `to_ref` when `is_deep` is set to `True`.
Especially useful when the data source is a list, dictionary or custom class. Objects obtained via `.value` are proxies
```python
data = [1,2,3]
data_ref = deep_ref(data)
assert data_ref.value is not data
```
You can get the raw object with `to_raw`.
```python
from ex4nicegui import to_raw, deep_ref
data = [1, 2, 3]
data_ref = deep_ref(data)
assert data_ref.value is not data
assert to_raw(data_ref.value) is data
```
---
### `effect`
Accepts a function and automatically monitors changes to the responsive objects used in the function to automatically execute the function.
```python
a = to_ref(1)
b = to_ref("text")
@effect
def auto_run_when_ref_value():
print(f"a:{a.value}")
def change_value():
a.value = 2
b.value = "new text"
ui.button("change", on_click=change_value)
```
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.
> 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
---
### `ref_computed`
As with `effect`, `ref_computed` can also return results from functions. Typically used for secondary computation from `to_ref`.
```python
a = to_ref(1)
a_square = ref_computed(lambda: a.value * 2)
@effect
def effect1():
print(f"a_square:{a_square.value}")
def change_value():
a.value = 2
ui.button("change", on_click=change_value)
```
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
> `ref_computed` is read-only `to_ref`
If you prefer to organize your code by class, ``ref_computed`` also supports acting on instance methods
```python
class MyState.
def __init__(self) -> None.
self.r_text = to_ref("")
@ref_computed
def post_text(self): return self.r_text.value
return self.r_text.value + "post"
state = MyState()
rxui.input(value=state.r_text)
rxui.label(state.post_text)
```
---
### `async_computed`
Use `async_computed` when asynchronous functions are required for computed.
```python
# Simulate asynchronous functions that take a long time to execute
async def long_time_query(input: str):
await asyncio.sleep(2)
num = random.randint(20, 100)
return f"query result[{input=}]:{num=}"
search = to_ref("")
evaluating = to_ref(False)
@async_computed(search, evaluating=evaluating, init="")
async def search_result():
return await long_time_query(search.value)
rxui.lazy_input(value=search)
rxui.label(
lambda: "Query in progress" if evaluating.value else "Enter content in input box above and return to search"
)
rxui.label(search_result)
```
- The first argument to `async_computed` must explicitly specify the responsive data to be monitored. Multiple responses can be specified using a list.
- Parameter `evaluating` is responsive data of type bool, which is `True` while the asynchronous function is executing, and `False` after the computation is finished.
- Parameter `init` specifies the initial result
---
### `on`
Similar to `effect`, but `on` needs to explicitly specify the responsive object to monitor.
```python
a1 = to_ref(1)
a2 = to_ref(10)
b = to_ref("text")
@on(a1)
def watch_a1_only():
print(f"watch_a1_only ... a1:{a1.value},a2:{a2.value}")
@on([a1, b], onchanges=True)
def watch_a1_and_b():
print(f"watch_a1_and_b ... a1:{a1.value},a2:{a2.value},b:{b.value}")
def change_a1():
a1.value += 1
ui.notify("change_a1")
ui.button("change a1", on_click=change_a1)
def change_a2():
a2.value += 1
ui.notify("change_a2")
ui.button("change a2", on_click=change_a2)
def change_b():
b.value += "x"
ui.notify("change_b")
ui.button("change b", on_click=change_b)
```
- If the parameter `onchanges` is True (the default value is False), the specified function will not be executed at binding time.
> 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
---
### `new_scope`
By default, all watch functions are automatically destroyed when the client connection is disconnected. For finer-grained control, the `new_scope` function can be utilized.
```python
from nicegui import ui
from ex4nicegui import rxui, to_ref, effect, new_scope
a = to_ref(0.0)
scope1 = new_scope()
@scope1.run
def _():
@effect
def _():
print(f"scope 1:{a.value}")
rxui.number(value=a)
rxui.button("dispose scope 1", on_click=scope1.dispose)
```
---
## functionality
### vmodel
Create two-way bindings on form input elements or components.
Bidirectional bindings are supported by default for `ref` simple value types
```python
from ex4nicegui import rxui, to_ref, deep_ref
data = to_ref("init")
rxui.label(lambda: f"{data.value=}")
# two-way binding
rxui.input(value=data)
```
- Simple value types are generally immutable values such as `str`, `int`, etc.
When using complex data structures, `deep_ref` is used to keep nested values responsive.
```python
data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
rxui.label(lambda: f"{data.value=!s}")
# No binding effect
rxui.input(value=data.value["a"])
# readonly binding
rxui.input(value=lambda: data.value["a"])
# two-way binding
rxui.input(value=rxui.vmodel(data.value["a"]))
```
- The first input box will be completely unresponsive because the code is equivalent to passing in a value `1` directly
- The second input box will get read responsiveness due to the use of a function.
- The third input box, wrapped in `rxui.vmodel`, can be bi-directionally bound.
Most use `vmodel` with `vfor`, see [todo list examples](./examples/todomvc/)
---
### vfor
Render list components based on list responsive data. Each component is updated on demand. Data items support dictionaries or objects of any type
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
from ex4nicegui import deep_ref, ref_computed
from typing import Dict
# refs
items = deep_ref(
[
{"id": 1, "message": "foo", "done": False},
{"id": 2, "message": "bar", "done": True},
]
)
# ref_computeds
@ref_computed
def done_count_info():
return f"done count:{sum(item['done'] for item in items.value)}"
# method
def check():
for item in items.value:
item["done"] = not item["done"]
# ui
rxui.label(done_count_info)
ui.button("check", on_click=check)
@rxui.vfor(items,key='id')
def _(store: rxui.VforStore[Dict]):
# function to build the interface for each row of data
item = store.get() # Get responsive object with `store.get`
mes = rxui.vmodel(item.value['message'])
# Enter the content of the input box,
# you can see the title of the radio box changes synchronously
with ui.card():
with ui.row():
rxui.input(value=mes)
rxui.label(lambda: f"{mes.value=!s}")
rxui.checkbox(text=mes, value=rxui.vmodel(item.value['done']))
```
- `rxui.vfor` decorator to custom function
- The first argument is passed to the responsive list. Each item in the list can be a dictionary or other object (`dataclasses` etc.)
- 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.
- The custom function takes one argument. The current row's can be retrieved via `store.get`, which is a responsive object.
> vfor are created only when new data is added.
---
### Bind class names
All component classes provide `bind_classes` for binding `class`, supporting three different data structures.
Bind dictionaries
```python
bg_color = to_ref(False)
has_error = to_ref(False)
rxui.label("test").bind_classes({"bg-blue": bg_color, "text-red": has_error})
rxui.switch("bg_color", value=bg_color)
rxui.switch("has_error", value=has_error)
```
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`.
---
Bind a responsive variable whose return value is a dictionary.
```python
bg_color = to_ref(False)
has_error = to_ref(False)
class_obj = ref_computed(
lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
)
rxui.switch("bg_color", value=bg_color)
rxui.switch("has_error", value=has_error)
rxui.label("bind to ref_computed").bind_classes(class_obj)
# or direct function passing
rxui.label("bind to ref_computed").bind_classes(
lambda: {"bg-blue": bg_color.value, "text-red": has_error.value}
)
```
---
Bind to list
```python
bg_color = to_ref("red")
bg_color_class = ref_computed(lambda: f"bg-{bg_color.value}")
text_color = to_ref("green")
text_color_class = ref_computed(lambda: f"text-{text_color.value}")
rxui.select(["red", "green", "yellow"], label="bg color", value=bg_color)
rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
rxui.label("binding to arrays").bind_classes([bg_color_class, text_color_class])
```
Each element in the list is a responsive variable that returns the class name
---
### bind-style
```python
from nicegui import ui
from ex4nicegui.reactive import rxui
from ex4nicegui.utils.signals import to_ref
bg_color = to_ref("blue")
text_color = to_ref("red")
rxui.label("test").bind_style(
{
"background-color": bg_color,
"color": text_color,
}
)
rxui.select(["blue", "green", "yellow"], label="bg color", value=bg_color)
rxui.select(["red", "green", "yellow"], label="text color", value=text_color)
```
`bind_style` passed into dictionary, `key` is style name, `value` is style value, responsive string
---
### rxui.echarts
Charting with echarts
---
#### rxui.echarts.from_javascript
Create echart from javascript code
```python
from pathlib import Path
rxui.echarts.from_javascript(Path("code.js"))
# or
rxui.echarts.from_javascript(
"""
(myChart) => {
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}
]
};
myChart.setOption(option);
}
"""
)
```
- The first parameter of the function is the echart instance object. You need to configure the chart in the function with `setOption`.
---
#### rxui.echarts.register_map
Register a map.
```python
rxui.echarts.register_map(
"china", "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
)
rxui.echarts(
{
"geo": {
"map": "china",
"roam": True,
},
"tooltip": {},
"legend": {},
"series": [], }
}
)
```
- The parameter `map_name` is a customized map name. Note that `map` must correspond to a registered name in the chart configuration.
- The parameter `src` is a valid network link to the map data.
You can also provide the path to the local json file for the map data.
```python
from pathlib import Path
rxui.echarts.register_map(
"china", Path("map-data.json")
)
```
---
### gsap
js animation library. [gsap documentation](https://gsap.com/docs/v3/)
```python
from nicegui import ui
from ex4nicegui import gsap
```
#### gsap.from_
Set the start property, the animation will transition from the set property to the original position
```python
ui.label("test from").classes("target")
gsap.from_(".target", {"x": 50,'duration':1})
```
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.
- Arguments `targets` are css selectors.
- Arguments `vars` are attribute values
---
#### gsap.to
Set the end property, the animation will transition from the original property to the set property.
```python
ui.label("test to").classes("target")
gsap.to(".target", {"x": 50,'duration':1})
```
After loading the screen, the text will be moved back 50px from the original position within 1 second.
- Arguments `targets` are css selectors.
- Arguments `vars` are attribute values
---
#### gsap.run_script
Setting up animations by writing js
```python
gsap.run_script(
r"""function setGsap(gsap) {
gsap.to('.target',{"duration": 0.3,y:60})
}
""")
```
- The parameter `script` can be text or a file with a js extension `Path`.
- The name of the defined js function doesn't matter, the first argument is a gsap object.
---
## BI Module
Create an interactive data visualization report using the minimal API.
![](./asset/bi_examples1.gif)
```python
from nicegui import ui
import pandas as pd
import numpy as np
from ex4nicegui import bi
from ex4nicegui.reactive import rxui
from ex4nicegui import effect, effect_refreshable
from pyecharts.charts import Bar
# data ready
def gen_data():
np.random.seed(265)
field1 = ["a1", "a2", "a3", "a4"]
field2 = [f"name{i}" for i in range(1, 11)]
df = (
pd.MultiIndex.from_product([field1, field2], names=["cat", "name"])
.to_frame()
.reset_index(drop=True)
)
df[["idc1", "idc2"]] = np.random.randint(50, 1000, size=(len(df), 2))
return df
df = gen_data()
# Create a data source.
ds = bi.data_source(df)
# ui
ui.query(".nicegui-content").classes("items-stretch no-wrap")
with ui.row().classes("justify-evenly"):
# Create components based on the data source `ds`.
ds.ui_select("cat").classes("min-w-[10rem]")
ds.ui_select("name").classes("min-w-[10rem]")
with ui.grid(columns=2):
# Configure the chart using a dictionary.
@ds.ui_echarts
def bar1(data: pd.DataFrame):
data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
return {
"xAxis": {"type": "value"},
"yAxis": {
"type": "category",
"data": data["name"].tolist(),
"inverse": True,
},
"legend": {"textStyle": {"color": "gray"}},
"series": [
{"type": "bar", "name": "idc1", "data": data["idc1"].tolist()},
{"type": "bar", "name": "idc2", "data": data["idc2"].tolist()},
],
}
bar1.classes("h-[20rem]")
# Configure the chart using pyecharts.
@ds.ui_echarts
def bar2(data: pd.DataFrame):
data = data.groupby("name").agg({"idc1": "sum", "idc2": "sum"}).reset_index()
return (
Bar()
.add_xaxis(data["name"].tolist())
.add_yaxis("idc1", data["idc1"].tolist())
.add_yaxis("idc2", data["idc2"].tolist())
)
bar2.classes("h-[20rem]")
# Bind the click event to achieve navigation.
@bar2.on_chart_click
def _(e: rxui.echarts.EChartsMouseEventArguments):
ui.open(f"/details/{e.name}", new_tab=True)
# with response mechanisms, you can freely combine native nicegui components.
label_a1_total = ui.label("")
# this function will be triggered when ds changed.
@effect
def _():
# prop `filtered_data` is the filtered DataFrame.
df = ds.filtered_data
total = df[df["cat"] == "a1"]["idc1"].sum()
label_a1_total.text = f"idc1 total(cat==a1):{total}"
# you can also use `effect_refreshable`, but you need to note that the components in the function are rebuilt each time.
@effect_refreshable
def _():
df = ds.filtered_data
total = df[df["cat"] == "a2"]["idc1"].sum()
ui.label(f"idc1 total(cat==a2):{total}")
# the page to be navigated when clicking on the chart series.
@ui.page("/details/{name}")
def details_page(name: str):
ui.label("This table data will not change")
ui.aggrid.from_pandas(ds.data.query(f'name=="{name}"'))
ui.label("This table will change when the homepage data changes. ")
@bi.data_source
def new_ds():
return ds.filtered_data[["name", "idc1", "idc2"]]
new_ds.ui_aggrid()
ui.run()
```
### Details
#### `bi.data_source`
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.
Receive `pandas`'s `DataFrame`:
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
ds = bi.data_source(df)
```
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:
```python
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
ds = bi.data_source(df)
@bi.data_source
def new_ds():
# df is pd.DataFrame
df = ds.filtered_data
df=df.copy()
df['value'] = df['value'] * 100
return df
ds.ui_select('name')
new_ds.ui_aggrid()
```
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.
---
Remove all filter states through the `ds.remove_filters` method:
```python
ds = bi.data_source(df)
def on_remove_filters():
ds.remove_filters()
ui.button("remove all filters", on_click=on_remove_filters)
ds.ui_select("name")
ds.ui_aggrid()
```
---
Reset the data source through the `ds.reload` method:
```python
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
new_df = pd.DataFrame(
{
"name": list("xxyyds"),
"cls": ["cla1", "cla2", "cla3", "cla3", "cla3", None],
"value": range(100, 106),
}
)
ds = bi.data_source(df)
def on_remove_filters():
ds.reload(new_df)
ui.button("reload data", on_click=on_remove_filters)
ds.ui_select("name")
ds.ui_aggrid()
```
---
#### ui_select
Dropdown Select Box
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
df = pd.DataFrame(
{
"name": list("aabcdf"),
"cls": ["c1", "c2", "c1", "c1", "c3", None],
"value": range(6),
}
)
ds = bi.data_source(df)
ds.ui_select("name")
```
The first parameter column specifies the column name of the data source.
---
Set the order of options using the parameter `sort_options`:
```python
ds.ui_select("name", sort_options={"value": "desc", "name": "asc"})
```
---
Set whether to exclude null values using the parameter `exclude_null_value`:
```python
df = pd.DataFrame(
{
"cls": ["c1", "c2", "c1", "c1", "c3", None],
}
)
ds = bi.data_source(df)
ds.ui_select("cls", exclude_null_value=True)
```
---
You can set the parameters of the native nicegui select component through keyword arguments.
Set default values through the value attribute:
```python
ds.ui_select("cls",value=['c1','c2'])
ds.ui_select("cls",multiple=False,value='c1')
```
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.
---
#### ui_table
Table
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
data = pd.DataFrame({"name": ["f", "a", "c", "b"], "age": [1, 2, 3, 1]})
ds = bi.data_source(data)
ds.ui_table(
columns=[
{"label": "new colA", "field": "colA", "sortable": True},
]
)
```
- 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
- The `rows` parameter will not take effect. Because the data source of the table is always controlled by the data source.
---
#### ui_aggrid
```python
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
data = pd.DataFrame(
{
"colA": list("abcde"),
"colB": [f"n{idx}" for idx in range(5)],
"colC": list(range(5)),
}
)
df = pd.DataFrame(data)
source = bi.data_source(df)
source.ui_aggrid(
options={
"columnDefs": [
{"headerName": "xx", "field": "no exists"},
{"headerName": "new colA", "field": "colA"},
{
"field": "colC",
"cellClassRules": {
"bg-red-300": "x < 3",
"bg-green-300": "x >= 3",
},
},
],
"rowData": [{"colX": [1, 2, 3, 4, 5]}],
}
)
```
- 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.
- The `rowData` key value will not take effect. Because the data source of the table is always controlled by the data source.
Raw data
{
"_id": null,
"home_page": null,
"name": "ex4nicegui",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "nicegui, ex4nicegui, webui",
"author": "carson_jia",
"author_email": "568166495@qq.com",
"download_url": "https://files.pythonhosted.org/packages/22/35/cf3aa27af3105d619e449cb9ecd642e66f51e4ab65ef21553c3c3d5a6178/ex4nicegui-0.6.6.tar.gz",
"platform": null,
"description": "# ex4nicegui\r\n[\u4e2d\u6587 README](./README.md)\r\n\r\n- [Install](#-install)\r\n- [Usage](#-usage)\r\n- [Features](#-features)\r\n- [BI Module](#bi-module)\r\n\r\nAn extension library for [nicegui](https://github.com/zauberzeug/nicegui). It has built-in responsive components and fully implements data-responsive interface programming.\r\n\r\n\r\n## \ud83d\udce6 Install\r\n\r\n```\r\npip install ex4nicegui -U\r\n```\r\n\r\n## examples\r\n- [basic](./examples/basic/)\r\n- [todo list mvc](./examples/todomvc/)\r\n\r\n\r\n## \ud83e\udd84 Usage\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import ref_computed, effect, to_ref\r\nfrom ex4nicegui.reactive import rxui\r\n\r\n# Define responsive data\r\nr_input = to_ref(\"\")\r\n\r\n# Pass in the responsive data according to the nicegui usage method.\r\nrxui.input(value=r_input)\r\nrxui.label(r_input)\r\n\r\nui.run()\r\n```\r\n![](./asset/sync_input.gif)\r\n\r\n\r\n## \ud83d\ude80 Features\r\n\r\n### echarts components\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import ref_computed, effect, to_ref\r\nfrom ex4nicegui.reactive import rxui\r\n\r\nr_input = to_ref(\"\")\r\n\r\n\r\n# ref_computed Creates a read-only reactive variable\r\n# Functions can use any other reactive variables automatically when they are associated\r\n@ref_computed\r\ndef cp_echarts_opts():\r\n return {\r\n \"title\": {\"text\": r_input.value}, #In the dictionary, use any reactive variable. Get the value by .value\r\n \"xAxis\": {\r\n \"type\": \"category\",\r\n \"data\": [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"],\r\n },\r\n \"yAxis\": {\"type\": \"value\"},\r\n \"series\": [\r\n {\r\n \"data\": [120, 200, 150, 80, 70, 110, 130],\r\n \"type\": \"bar\",\r\n \"showBackground\": True,\r\n \"backgroundStyle\": {\"color\": \"rgba(180, 180, 180, 0.2)\"},\r\n }\r\n ],\r\n }\r\n\r\n\r\ninput = rxui.input(\"Enter the content, and the chart title will be synchronized\", value=r_input)\r\n\r\n# Get the native nicegui component object through the element attribute of the responsive component object\r\ninput.element.classes(\"w-full\")\r\n\r\nrxui.echarts(cp_echarts_opts).classes('h-[20rem]')\r\n\r\nui.run()\r\n```\r\n![](./asset/asyc_echarts_title.gif)\r\n\r\n\r\n### echarts mouse events\r\n\r\nthe `on` function parameters `event_name` and `query` to view the[echarts English Documentation](https://echarts.apache.org/handbook/en/concepts/event/)\r\n\r\n\r\nThe following example binds the mouse click event\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui.reactive import rxui\r\n\r\nopts = {\r\n \"xAxis\": {\"type\": \"value\", \"boundaryGap\": [0, 0.01]},\r\n \"yAxis\": {\r\n \"type\": \"category\",\r\n \"data\": [\"Brazil\", \"Indonesia\", \"USA\", \"India\", \"China\", \"World\"],\r\n },\r\n \"series\": [\r\n {\r\n \"name\": \"first\",\r\n \"type\": \"bar\",\r\n \"data\": [18203, 23489, 29034, 104970, 131744, 630230],\r\n },\r\n {\r\n \"name\": \"second\",\r\n \"type\": \"bar\",\r\n \"data\": [19325, 23438, 31000, 121594, 134141, 681807],\r\n },\r\n ],\r\n}\r\n\r\nbar = rxui.echarts(opts)\r\n\r\ndef on_click(e: rxui.echarts.EChartsMouseEventArguments):\r\n ui.notify(f\"on_click:{e.seriesName}:{e.name}:{e.value}\")\r\n\r\nbar.on(\"click\", on_click)\r\n```\r\n\r\n\r\nThe following example only triggers the mouseover event for a specified series.\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui.reactive import rxui\r\n\r\nopts = {\r\n \"xAxis\": {\"type\": \"value\", \"boundaryGap\": [0, 0.01]},\r\n \"yAxis\": {\r\n \"type\": \"category\",\r\n \"data\": [\"Brazil\", \"Indonesia\", \"USA\", \"India\", \"China\", \"World\"],\r\n },\r\n \"series\": [\r\n {\r\n \"name\": \"first\",\r\n \"type\": \"bar\",\r\n \"data\": [18203, 23489, 29034, 104970, 131744, 630230],\r\n },\r\n {\r\n \"name\": \"second\",\r\n \"type\": \"bar\",\r\n \"data\": [19325, 23438, 31000, 121594, 134141, 681807],\r\n },\r\n ],\r\n}\r\n\r\nbar = rxui.echarts(opts)\r\n\r\ndef on_first_series_mouseover(e: rxui.echarts.EChartsMouseEventArguments):\r\n ui.notify(f\"on_first_series_mouseover:{e.seriesName}:{e.name}:{e.value}\")\r\n\r\n\r\nbar.on(\"mouseover\", on_first_series_mouseover, query={\"seriesName\": \"first\"})\r\n\r\nui.run()\r\n```\r\n---\r\n\r\n\r\n## responsive\r\n\r\n```python\r\nfrom ex4nicegui import (\r\n to_ref,\r\n ref_computed,\r\n on,\r\n effect,\r\n effect_refreshable,\r\n batch,\r\n event_batch,\r\n deep_ref,\r\n)\r\n```\r\nCommonly used `to_ref`,`deep_ref`,`effect`,`ref_computed`,`on`\r\n\r\n### `to_ref`\r\nDefines responsive objects, read and written by `.value`.\r\n```python\r\na = to_ref(1)\r\nb = to_ref(\"text\")\r\n\r\na.value =2\r\nb.value = 'new text'\r\n\r\nprint(a.value)\r\n```\r\n\r\nWhen the value is a complex object, responsiveness of nested objects is not maintained by default.\r\n\r\n```python\r\na = to_ref([1,2])\r\n\r\n@effect\r\ndef _().\r\n print(len(a.value))\r\n\r\n# doesn't trigger the effect\r\na.value.append(10)\r\n\r\n# the whole substitution will be triggered\r\na.value = [1,2,10]\r\n```\r\n\r\nDepth responsiveness is obtained when `is_deep` is set to `True`.\r\n\r\n```python\r\na = to_ref([1,2],is_deep=True)\r\n\r\n@effect\r\ndef _():\r\n print('len:',len(a.value))\r\n\r\n# print 3\r\na.value.append(10)\r\n\r\n```\r\n\r\n> `deep_ref` is equivalent to `to_ref` if `is_deep` is set to `True`.\r\n---\r\n\r\n### `deep_ref`\r\nEquivalent to `to_ref` when `is_deep` is set to `True`.\r\n\r\nEspecially useful when the data source is a list, dictionary or custom class. Objects obtained via `.value` are proxies\r\n\r\n```python\r\ndata = [1,2,3]\r\ndata_ref = deep_ref(data)\r\n\r\nassert data_ref.value is not data\r\n```\r\n\r\nYou can get the raw object with `to_raw`.\r\n```python\r\nfrom ex4nicegui import to_raw, deep_ref\r\n\r\ndata = [1, 2, 3]\r\ndata_ref = deep_ref(data)\r\n\r\nassert data_ref.value is not data\r\nassert to_raw(data_ref.value) is data\r\n```\r\n\r\n---\r\n\r\n### `effect`\r\nAccepts a function and automatically monitors changes to the responsive objects used in the function to automatically execute the function.\r\n\r\n```python\r\na = to_ref(1)\r\nb = to_ref(\"text\")\r\n\r\n\r\n@effect\r\ndef auto_run_when_ref_value():\r\n print(f\"a:{a.value}\")\r\n\r\n\r\ndef change_value():\r\n a.value = 2\r\n b.value = \"new text\"\r\n\r\n\r\nui.button(\"change\", on_click=change_value)\r\n```\r\n\r\nThe 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.\r\n\r\n> 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\r\n\r\n---\r\n\r\n### `ref_computed`\r\nAs with `effect`, `ref_computed` can also return results from functions. Typically used for secondary computation from `to_ref`.\r\n\r\n```python\r\na = to_ref(1)\r\na_square = ref_computed(lambda: a.value * 2)\r\n\r\n\r\n@effect\r\ndef effect1():\r\n print(f\"a_square:{a_square.value}\")\r\n\r\n\r\ndef change_value():\r\n a.value = 2\r\n\r\n\r\nui.button(\"change\", on_click=change_value)\r\n```\r\n\r\nWhen 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\r\n\r\n> `ref_computed` is read-only `to_ref`\r\n\r\nIf you prefer to organize your code by class, ``ref_computed`` also supports acting on instance methods\r\n\r\n```python\r\nclass MyState.\r\n def __init__(self) -> None.\r\n self.r_text = to_ref(\"\")\r\n\r\n @ref_computed\r\n def post_text(self): return self.r_text.value\r\n return self.r_text.value + \"post\"\r\n\r\nstate = MyState()\r\n\r\nrxui.input(value=state.r_text)\r\nrxui.label(state.post_text)\r\n```\r\n\r\n---\r\n\r\n### `async_computed`\r\nUse `async_computed` when asynchronous functions are required for computed.\r\n\r\n```python\r\n\r\n# Simulate asynchronous functions that take a long time to execute\r\nasync def long_time_query(input: str):\r\n await asyncio.sleep(2)\r\n num = random.randint(20, 100)\r\n return f\"query result[{input=}]:{num=}\"\r\n\r\n\r\nsearch = to_ref(\"\")\r\nevaluating = to_ref(False)\r\n\r\n@async_computed(search, evaluating=evaluating, init=\"\")\r\nasync def search_result():\r\n return await long_time_query(search.value)\r\n\r\nrxui.lazy_input(value=search)\r\n\r\nrxui.label(\r\n lambda: \"Query in progress\" if evaluating.value else \"Enter content in input box above and return to search\"\r\n)\r\nrxui.label(search_result)\r\n\r\n```\r\n\r\n- The first argument to `async_computed` must explicitly specify the responsive data to be monitored. Multiple responses can be specified using a list.\r\n- Parameter `evaluating` is responsive data of type bool, which is `True` while the asynchronous function is executing, and `False` after the computation is finished.\r\n- Parameter `init` specifies the initial result\r\n\r\n---\r\n\r\n### `on`\r\nSimilar to `effect`, but `on` needs to explicitly specify the responsive object to monitor.\r\n\r\n```python\r\n\r\na1 = to_ref(1)\r\na2 = to_ref(10)\r\nb = to_ref(\"text\")\r\n\r\n\r\n@on(a1)\r\ndef watch_a1_only():\r\n print(f\"watch_a1_only ... a1:{a1.value},a2:{a2.value}\")\r\n\r\n\r\n@on([a1, b], onchanges=True)\r\ndef watch_a1_and_b():\r\n print(f\"watch_a1_and_b ... a1:{a1.value},a2:{a2.value},b:{b.value}\")\r\n\r\n\r\ndef change_a1():\r\n a1.value += 1\r\n ui.notify(\"change_a1\")\r\n\r\n\r\nui.button(\"change a1\", on_click=change_a1)\r\n\r\n\r\ndef change_a2():\r\n a2.value += 1\r\n ui.notify(\"change_a2\")\r\n\r\n\r\nui.button(\"change a2\", on_click=change_a2)\r\n\r\n\r\ndef change_b():\r\n b.value += \"x\"\r\n ui.notify(\"change_b\")\r\n\r\n\r\nui.button(\"change b\", on_click=change_b)\r\n\r\n```\r\n\r\n- If the parameter `onchanges` is True (the default value is False), the specified function will not be executed at binding time.\r\n\r\n> 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\r\n\r\n---\r\n\r\n### `new_scope`\r\n\r\nBy default, all watch functions are automatically destroyed when the client connection is disconnected. For finer-grained control, the `new_scope` function can be utilized.\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import rxui, to_ref, effect, new_scope\r\n\r\na = to_ref(0.0)\r\n\r\nscope1 = new_scope()\r\n\r\n@scope1.run\r\ndef _():\r\n @effect\r\n def _():\r\n print(f\"scope 1:{a.value}\")\r\n\r\n\r\nrxui.number(value=a)\r\nrxui.button(\"dispose scope 1\", on_click=scope1.dispose)\r\n```\r\n\r\n---\r\n\r\n## functionality\r\n\r\n### vmodel\r\nCreate two-way bindings on form input elements or components.\r\n\r\nBidirectional bindings are supported by default for `ref` simple value types\r\n```python\r\nfrom ex4nicegui import rxui, to_ref, deep_ref\r\n\r\ndata = to_ref(\"init\")\r\n\r\nrxui.label(lambda: f\"{data.value=}\")\r\n# two-way binding \r\nrxui.input(value=data)\r\n```\r\n- Simple value types are generally immutable values such as `str`, `int`, etc.\r\n\r\nWhen using complex data structures, `deep_ref` is used to keep nested values responsive.\r\n```python\r\ndata = deep_ref({\"a\": 1, \"b\": [1, 2, 3, 4]})\r\n\r\nrxui.label(lambda: f\"{data.value=!s}\")\r\n\r\n# No binding effect\r\nrxui.input(value=data.value[\"a\"])\r\n\r\n# readonly binding\r\nrxui.input(value=lambda: data.value[\"a\"])\r\n\r\n# two-way binding\r\nrxui.input(value=rxui.vmodel(data.value[\"a\"]))\r\n```\r\n\r\n- The first input box will be completely unresponsive because the code is equivalent to passing in a value `1` directly\r\n- The second input box will get read responsiveness due to the use of a function.\r\n- The third input box, wrapped in `rxui.vmodel`, can be bi-directionally bound.\r\n\r\nMost use `vmodel` with `vfor`, see [todo list examples](./examples/todomvc/)\r\n\r\n---\r\n\r\n### vfor\r\nRender list components based on list responsive data. Each component is updated on demand. Data items support dictionaries or objects of any type\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui.reactive import rxui\r\nfrom ex4nicegui import deep_ref, ref_computed\r\nfrom typing import Dict\r\n\r\n# refs\r\nitems = deep_ref(\r\n [\r\n {\"id\": 1, \"message\": \"foo\", \"done\": False},\r\n {\"id\": 2, \"message\": \"bar\", \"done\": True},\r\n ]\r\n)\r\n\r\n# ref_computeds\r\n@ref_computed\r\ndef done_count_info():\r\n return f\"done count:{sum(item['done'] for item in items.value)}\"\r\n\r\n# method\r\ndef check():\r\n for item in items.value:\r\n item[\"done\"] = not item[\"done\"]\r\n\r\n\r\n# ui\r\nrxui.label(done_count_info)\r\nui.button(\"check\", on_click=check)\r\n\r\n\r\n@rxui.vfor(items,key='id')\r\ndef _(store: rxui.VforStore[Dict]):\r\n # function to build the interface for each row of data\r\n item = store.get() # Get responsive object with `store.get`\r\n mes = rxui.vmodel(item.value['message'])\r\n\r\n # Enter the content of the input box, \r\n # you can see the title of the radio box changes synchronously\r\n with ui.card():\r\n with ui.row():\r\n rxui.input(value=mes)\r\n rxui.label(lambda: f\"{mes.value=!s}\")\r\n rxui.checkbox(text=mes, value=rxui.vmodel(item.value['done']))\r\n\r\n```\r\n\r\n- `rxui.vfor` decorator to custom function\r\n - The first argument is passed to the responsive list. Each item in the list can be a dictionary or other object (`dataclasses` etc.)\r\n - 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.\r\n- The custom function takes one argument. The current row's can be retrieved via `store.get`, which is a responsive object.\r\n\r\n\r\n> vfor are created only when new data is added.\r\n\r\n\r\n---\r\n\r\n\r\n### Bind class names\r\n\r\nAll component classes provide `bind_classes` for binding `class`, supporting three different data structures.\r\n\r\nBind dictionaries\r\n\r\n```python\r\nbg_color = to_ref(False)\r\nhas_error = to_ref(False)\r\n\r\nrxui.label(\"test\").bind_classes({\"bg-blue\": bg_color, \"text-red\": has_error})\r\n\r\nrxui.switch(\"bg_color\", value=bg_color)\r\nrxui.switch(\"has_error\", value=has_error)\r\n```\r\n\r\nDictionary 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`.\r\n\r\n---\r\n\r\nBind a responsive variable whose return value is a dictionary.\r\n\r\n```python\r\nbg_color = to_ref(False)\r\nhas_error = to_ref(False)\r\n\r\nclass_obj = ref_computed(\r\n lambda: {\"bg-blue\": bg_color.value, \"text-red\": has_error.value}\r\n)\r\n\r\nrxui.switch(\"bg_color\", value=bg_color)\r\nrxui.switch(\"has_error\", value=has_error)\r\nrxui.label(\"bind to ref_computed\").bind_classes(class_obj)\r\n\r\n# or direct function passing\r\nrxui.label(\"bind to ref_computed\").bind_classes(\r\n lambda: {\"bg-blue\": bg_color.value, \"text-red\": has_error.value}\r\n)\r\n```\r\n\r\n---\r\n\r\nBind to list\r\n\r\n```python\r\nbg_color = to_ref(\"red\")\r\nbg_color_class = ref_computed(lambda: f\"bg-{bg_color.value}\")\r\n\r\ntext_color = to_ref(\"green\")\r\ntext_color_class = ref_computed(lambda: f\"text-{text_color.value}\")\r\n\r\nrxui.select([\"red\", \"green\", \"yellow\"], label=\"bg color\", value=bg_color)\r\nrxui.select([\"red\", \"green\", \"yellow\"], label=\"text color\", value=text_color)\r\n\r\nrxui.label(\"binding to arrays\").bind_classes([bg_color_class, text_color_class])\r\n\r\n```\r\n\r\nEach element in the list is a responsive variable that returns the class name\r\n\r\n---\r\n\r\n### bind-style\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui.reactive import rxui\r\nfrom ex4nicegui.utils.signals import to_ref\r\n\r\n\r\nbg_color = to_ref(\"blue\")\r\ntext_color = to_ref(\"red\")\r\n\r\nrxui.label(\"test\").bind_style(\r\n {\r\n \"background-color\": bg_color,\r\n \"color\": text_color,\r\n }\r\n)\r\n\r\nrxui.select([\"blue\", \"green\", \"yellow\"], label=\"bg color\", value=bg_color)\r\nrxui.select([\"red\", \"green\", \"yellow\"], label=\"text color\", value=text_color)\r\n```\r\n\r\n`bind_style` passed into dictionary, `key` is style name, `value` is style value, responsive string\r\n\r\n---\r\n\r\n### rxui.echarts\r\nCharting with echarts\r\n\r\n---\r\n\r\n#### rxui.echarts.from_javascript\r\nCreate echart from javascript code\r\n\r\n```python\r\nfrom pathlib import Path\r\n\r\nrxui.echarts.from_javascript(Path(\"code.js\"))\r\n# or\r\nrxui.echarts.from_javascript(\r\n \"\"\"\r\n(myChart) => {\r\n\r\n option = {\r\n xAxis: {\r\n type: 'category',\r\n data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\r\n },\r\n yAxis: {\r\n type: 'value'\r\n },\r\n series: [\r\n {\r\n data: [120, 200, 150, 80, 70, 110, 130],\r\n type: 'bar'\r\n }\r\n ]\r\n };\r\n\r\n myChart.setOption(option);\r\n}\r\n\"\"\"\r\n)\r\n```\r\n\r\n- The first parameter of the function is the echart instance object. You need to configure the chart in the function with `setOption`.\r\n\r\n---\r\n\r\n#### rxui.echarts.register_map\r\nRegister a map.\r\n\r\n```python\r\nrxui.echarts.register_map(\r\n \"china\", \"https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json\"\r\n)\r\n\r\nrxui.echarts(\r\n {\r\n \"geo\": {\r\n \"map\": \"china\",\r\n \"roam\": True,\r\n },\r\n \"tooltip\": {},\r\n \"legend\": {},\r\n \"series\": [], }\r\n }\r\n)\r\n```\r\n\r\n- The parameter `map_name` is a customized map name. Note that `map` must correspond to a registered name in the chart configuration.\r\n- The parameter `src` is a valid network link to the map data.\r\n\r\nYou can also provide the path to the local json file for the map data.\r\n```python\r\nfrom pathlib import Path\r\n\r\nrxui.echarts.register_map(\r\n \"china\", Path(\"map-data.json\")\r\n)\r\n```\r\n\r\n---\r\n\r\n### gsap\r\n\r\njs animation library. [gsap documentation](https://gsap.com/docs/v3/)\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import gsap\r\n```\r\n\r\n#### gsap.from_\r\n\r\nSet the start property, the animation will transition from the set property to the original position\r\n\r\n```python\r\n\r\nui.label(\"test from\").classes(\"target\")\r\ngsap.from_(\".target\", {\"x\": 50,'duration':1})\r\n\r\n```\r\n\r\nAfter 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.\r\n\r\n- Arguments `targets` are css selectors.\r\n- Arguments `vars` are attribute values\r\n\r\n---\r\n\r\n#### gsap.to\r\n\r\nSet the end property, the animation will transition from the original property to the set property.\r\n\r\n```python\r\n\r\nui.label(\"test to\").classes(\"target\")\r\ngsap.to(\".target\", {\"x\": 50,'duration':1})\r\n\r\n```\r\n\r\nAfter loading the screen, the text will be moved back 50px from the original position within 1 second.\r\n\r\n- Arguments `targets` are css selectors.\r\n- Arguments `vars` are attribute values\r\n\r\n---\r\n\r\n#### gsap.run_script\r\n\r\nSetting up animations by writing js\r\n\r\n```python\r\n\r\ngsap.run_script(\r\n r\"\"\"function setGsap(gsap) {\r\n gsap.to('.target',{\"duration\": 0.3,y:60})\r\n}\r\n\"\"\")\r\n```\r\n\r\n- The parameter `script` can be text or a file with a js extension `Path`.\r\n- The name of the defined js function doesn't matter, the first argument is a gsap object.\r\n\r\n---\r\n\r\n## BI Module\r\n\r\nCreate an interactive data visualization report using the minimal API.\r\n\r\n![](./asset/bi_examples1.gif)\r\n\r\n```python\r\nfrom nicegui import ui\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom ex4nicegui import bi\r\nfrom ex4nicegui.reactive import rxui\r\nfrom ex4nicegui import effect, effect_refreshable\r\nfrom pyecharts.charts import Bar\r\n\r\n\r\n# data ready\r\ndef gen_data():\r\n np.random.seed(265)\r\n field1 = [\"a1\", \"a2\", \"a3\", \"a4\"]\r\n field2 = [f\"name{i}\" for i in range(1, 11)]\r\n df = (\r\n pd.MultiIndex.from_product([field1, field2], names=[\"cat\", \"name\"])\r\n .to_frame()\r\n .reset_index(drop=True)\r\n )\r\n df[[\"idc1\", \"idc2\"]] = np.random.randint(50, 1000, size=(len(df), 2))\r\n return df\r\n\r\n\r\ndf = gen_data()\r\n\r\n# Create a data source.\r\nds = bi.data_source(df)\r\n\r\n# ui\r\nui.query(\".nicegui-content\").classes(\"items-stretch no-wrap\")\r\n\r\nwith ui.row().classes(\"justify-evenly\"):\r\n # Create components based on the data source `ds`.\r\n ds.ui_select(\"cat\").classes(\"min-w-[10rem]\")\r\n ds.ui_select(\"name\").classes(\"min-w-[10rem]\")\r\n\r\n\r\nwith ui.grid(columns=2):\r\n # Configure the chart using a dictionary.\r\n @ds.ui_echarts\r\n def bar1(data: pd.DataFrame):\r\n data = data.groupby(\"name\").agg({\"idc1\": \"sum\", \"idc2\": \"sum\"}).reset_index()\r\n\r\n return {\r\n \"xAxis\": {\"type\": \"value\"},\r\n \"yAxis\": {\r\n \"type\": \"category\",\r\n \"data\": data[\"name\"].tolist(),\r\n \"inverse\": True,\r\n },\r\n \"legend\": {\"textStyle\": {\"color\": \"gray\"}},\r\n \"series\": [\r\n {\"type\": \"bar\", \"name\": \"idc1\", \"data\": data[\"idc1\"].tolist()},\r\n {\"type\": \"bar\", \"name\": \"idc2\", \"data\": data[\"idc2\"].tolist()},\r\n ],\r\n }\r\n\r\n bar1.classes(\"h-[20rem]\")\r\n\r\n # Configure the chart using pyecharts.\r\n @ds.ui_echarts\r\n def bar2(data: pd.DataFrame):\r\n data = data.groupby(\"name\").agg({\"idc1\": \"sum\", \"idc2\": \"sum\"}).reset_index()\r\n\r\n return (\r\n Bar()\r\n .add_xaxis(data[\"name\"].tolist())\r\n .add_yaxis(\"idc1\", data[\"idc1\"].tolist())\r\n .add_yaxis(\"idc2\", data[\"idc2\"].tolist())\r\n )\r\n\r\n bar2.classes(\"h-[20rem]\")\r\n\r\n # Bind the click event to achieve navigation.\r\n @bar2.on_chart_click\r\n def _(e: rxui.echarts.EChartsMouseEventArguments):\r\n ui.open(f\"/details/{e.name}\", new_tab=True)\r\n\r\n\r\n# with response mechanisms, you can freely combine native nicegui components.\r\nlabel_a1_total = ui.label(\"\")\r\n\r\n\r\n# this function will be triggered when ds changed.\r\n@effect\r\ndef _():\r\n # prop `filtered_data` is the filtered DataFrame.\r\n df = ds.filtered_data\r\n total = df[df[\"cat\"] == \"a1\"][\"idc1\"].sum()\r\n label_a1_total.text = f\"idc1 total(cat==a1):{total}\"\r\n\r\n\r\n# you can also use `effect_refreshable`, but you need to note that the components in the function are rebuilt each time.\r\n@effect_refreshable\r\ndef _():\r\n df = ds.filtered_data\r\n total = df[df[\"cat\"] == \"a2\"][\"idc1\"].sum()\r\n ui.label(f\"idc1 total(cat==a2):{total}\")\r\n\r\n\r\n# the page to be navigated when clicking on the chart series.\r\n@ui.page(\"/details/{name}\")\r\ndef details_page(name: str):\r\n ui.label(\"This table data will not change\")\r\n ui.aggrid.from_pandas(ds.data.query(f'name==\"{name}\"'))\r\n\r\n ui.label(\"This table will change when the homepage data changes. \")\r\n\r\n @bi.data_source\r\n def new_ds():\r\n return ds.filtered_data[[\"name\", \"idc1\", \"idc2\"]]\r\n\r\n new_ds.ui_aggrid()\r\n\r\n\r\nui.run()\r\n```\r\n\r\n### Details\r\n\r\n#### `bi.data_source`\r\nThe 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.\r\n\r\nReceive `pandas`'s `DataFrame`:\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import bi\r\nimport pandas as pd\r\n\r\ndf = pd.DataFrame(\r\n {\r\n \"name\": list(\"aabcdf\"),\r\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\r\n \"value\": range(6),\r\n }\r\n)\r\n\r\nds = bi.data_source(df)\r\n```\r\n\r\nSometimes, 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:\r\n```python\r\ndf = pd.DataFrame(\r\n {\r\n \"name\": list(\"aabcdf\"),\r\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\r\n \"value\": range(6),\r\n }\r\n)\r\n\r\nds = bi.data_source(df)\r\n\r\n@bi.data_source\r\ndef new_ds():\r\n # df is pd.DataFrame \r\n df = ds.filtered_data\r\n df=df.copy()\r\n df['value'] = df['value'] * 100\r\n return df\r\n\r\nds.ui_select('name')\r\nnew_ds.ui_aggrid()\r\n```\r\n\r\nNote 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.\r\n\r\n---\r\n\r\nRemove all filter states through the `ds.remove_filters` method:\r\n```python\r\nds = bi.data_source(df)\r\n\r\ndef on_remove_filters():\r\n ds.remove_filters()\r\n\r\nui.button(\"remove all filters\", on_click=on_remove_filters)\r\n\r\nds.ui_select(\"name\")\r\nds.ui_aggrid()\r\n```\r\n---\r\n\r\nReset the data source through the `ds.reload` method:\r\n```python\r\n\r\ndf = pd.DataFrame(\r\n {\r\n \"name\": list(\"aabcdf\"),\r\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\r\n \"value\": range(6),\r\n }\r\n)\r\n\r\nnew_df = pd.DataFrame(\r\n {\r\n \"name\": list(\"xxyyds\"),\r\n \"cls\": [\"cla1\", \"cla2\", \"cla3\", \"cla3\", \"cla3\", None],\r\n \"value\": range(100, 106),\r\n }\r\n)\r\n\r\nds = bi.data_source(df)\r\n\r\ndef on_remove_filters():\r\n ds.reload(new_df)\r\n\r\nui.button(\"reload data\", on_click=on_remove_filters)\r\n\r\nds.ui_select(\"name\")\r\nds.ui_aggrid()\r\n```\r\n\r\n---\r\n#### ui_select\r\n\r\nDropdown Select Box\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import bi\r\nimport pandas as pd\r\n\r\ndf = pd.DataFrame(\r\n {\r\n \"name\": list(\"aabcdf\"),\r\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\r\n \"value\": range(6),\r\n }\r\n)\r\n\r\nds = bi.data_source(df)\r\n\r\nds.ui_select(\"name\")\r\n```\r\n\r\nThe first parameter column specifies the column name of the data source.\r\n\r\n---\r\nSet the order of options using the parameter `sort_options`:\r\n```python\r\nds.ui_select(\"name\", sort_options={\"value\": \"desc\", \"name\": \"asc\"})\r\n```\r\n\r\n---\r\nSet whether to exclude null values using the parameter `exclude_null_value`:\r\n```python\r\ndf = pd.DataFrame(\r\n {\r\n \"cls\": [\"c1\", \"c2\", \"c1\", \"c1\", \"c3\", None],\r\n }\r\n)\r\n\r\nds = bi.data_source(df)\r\nds.ui_select(\"cls\", exclude_null_value=True)\r\n```\r\n\r\n---\r\nYou can set the parameters of the native nicegui select component through keyword arguments.\r\n\r\nSet default values through the value attribute:\r\n```python\r\nds.ui_select(\"cls\",value=['c1','c2'])\r\nds.ui_select(\"cls\",multiple=False,value='c1')\r\n```\r\nFor 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.\r\n\r\n---\r\n\r\n\r\n#### ui_table\r\n\r\nTable\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import bi\r\nimport pandas as pd\r\n\r\ndata = pd.DataFrame({\"name\": [\"f\", \"a\", \"c\", \"b\"], \"age\": [1, 2, 3, 1]})\r\nds = bi.data_source(data)\r\n\r\nds.ui_table(\r\n columns=[\r\n {\"label\": \"new colA\", \"field\": \"colA\", \"sortable\": True},\r\n ]\r\n)\r\n\r\n```\r\n\r\n- 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\r\n- The `rows` parameter will not take effect. Because the data source of the table is always controlled by the data source.\r\n\r\n---\r\n\r\n#### ui_aggrid\r\n\r\n\r\n```python\r\nfrom nicegui import ui\r\nfrom ex4nicegui import bi\r\nimport pandas as pd\r\n\r\ndata = pd.DataFrame(\r\n {\r\n \"colA\": list(\"abcde\"),\r\n \"colB\": [f\"n{idx}\" for idx in range(5)],\r\n \"colC\": list(range(5)),\r\n }\r\n)\r\ndf = pd.DataFrame(data)\r\n\r\nsource = bi.data_source(df)\r\n\r\nsource.ui_aggrid(\r\n options={\r\n \"columnDefs\": [\r\n {\"headerName\": \"xx\", \"field\": \"no exists\"},\r\n {\"headerName\": \"new colA\", \"field\": \"colA\"},\r\n {\r\n \"field\": \"colC\",\r\n \"cellClassRules\": {\r\n \"bg-red-300\": \"x < 3\",\r\n \"bg-green-300\": \"x >= 3\",\r\n },\r\n },\r\n ],\r\n \"rowData\": [{\"colX\": [1, 2, 3, 4, 5]}],\r\n }\r\n)\r\n```\r\n\r\n- 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.\r\n- The `rowData` key value will not take effect. Because the data source of the table is always controlled by the data source.\r\n\r\n\r\n",
"bugtrack_url": null,
"license": "MIT license",
"summary": "Extension library based on nicegui, providing data responsive,BI functionality modules",
"version": "0.6.6",
"project_urls": null,
"split_keywords": [
"nicegui",
" ex4nicegui",
" webui"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "1d9672d59aa110f532387836db4c3f4a7ee6bda0ec9e1048368d77c955aae8d0",
"md5": "7870e4790e8d76b4d6e2b6a090847ba4",
"sha256": "5cb268ac3e4dae1da6da05fd650f5aeab3fefe206195a4f71ab988a24c7da679"
},
"downloads": -1,
"filename": "ex4nicegui-0.6.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7870e4790e8d76b4d6e2b6a090847ba4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 341686,
"upload_time": "2024-05-15T16:43:04",
"upload_time_iso_8601": "2024-05-15T16:43:04.657121Z",
"url": "https://files.pythonhosted.org/packages/1d/96/72d59aa110f532387836db4c3f4a7ee6bda0ec9e1048368d77c955aae8d0/ex4nicegui-0.6.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "2235cf3aa27af3105d619e449cb9ecd642e66f51e4ab65ef21553c3c3d5a6178",
"md5": "0325249ab251665a845328952ea1d0b8",
"sha256": "e8486b1eb06167efa65591bd99c98582922d68c0c3024a24867190b71269f0a5"
},
"downloads": -1,
"filename": "ex4nicegui-0.6.6.tar.gz",
"has_sig": false,
"md5_digest": "0325249ab251665a845328952ea1d0b8",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 295193,
"upload_time": "2024-05-15T16:43:08",
"upload_time_iso_8601": "2024-05-15T16:43:08.638645Z",
"url": "https://files.pythonhosted.org/packages/22/35/cf3aa27af3105d619e449cb9ecd642e66f51e4ab65ef21553c3c3d5a6178/ex4nicegui-0.6.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-05-15 16:43:08",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "ex4nicegui"
}