ex4nicegui 0.3.0__py3-none-any.whl → 0.3.1__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.
- ex4nicegui/__init__.py +1 -1
- ex4nicegui/bi/__init__.py +11 -1
- ex4nicegui/bi/dataSource.py +66 -37
- ex4nicegui/bi/dataSourceFacade.py +43 -158
- ex4nicegui/bi/elements/__init__.py +0 -0
- ex4nicegui/bi/elements/containers.py +13 -0
- ex4nicegui/bi/elements/layouts.py +28 -0
- ex4nicegui/bi/elements/models.py +55 -0
- ex4nicegui/bi/elements/text.py +33 -0
- ex4nicegui/bi/elements/ui_aggrid.py +46 -0
- ex4nicegui/bi/elements/ui_date_picker.js +35 -0
- ex4nicegui/bi/elements/ui_date_picker.py +77 -0
- ex4nicegui/bi/elements/ui_echarts.py +72 -0
- ex4nicegui/bi/elements/ui_radio.py +60 -0
- ex4nicegui/bi/elements/ui_range.py +119 -0
- ex4nicegui/bi/elements/ui_select.py +105 -0
- ex4nicegui/bi/elements/ui_slider.py +59 -0
- ex4nicegui/bi/protocols.py +52 -5
- ex4nicegui/bi/types.py +2 -1
- ex4nicegui/reactive/EChartsComponent/ECharts.py +1 -0
- ex4nicegui/reactive/officials/base.py +12 -1
- ex4nicegui/reactive/officials/button.py +1 -1
- ex4nicegui/reactive/officials/drawer.py +1 -2
- ex4nicegui/utils/signals.py +4 -0
- {ex4nicegui-0.3.0.dist-info → ex4nicegui-0.3.1.dist-info}/METADATA +19 -19
- {ex4nicegui-0.3.0.dist-info → ex4nicegui-0.3.1.dist-info}/RECORD +29 -16
- {ex4nicegui-0.3.0.dist-info → ex4nicegui-0.3.1.dist-info}/WHEEL +1 -1
- {ex4nicegui-0.3.0.dist-info → ex4nicegui-0.3.1.dist-info}/LICENSE +0 -0
- {ex4nicegui-0.3.0.dist-info → ex4nicegui-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
from nicegui.client import Client
|
|
5
|
+
from nicegui.elements.select import Select
|
|
6
|
+
from ex4nicegui import to_ref, ref_computed
|
|
7
|
+
from ex4nicegui.utils.signals import Ref
|
|
8
|
+
from ex4nicegui.bi.dataSource import Filter
|
|
9
|
+
from .models import UiResult
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ex4nicegui.bi.dataSourceFacade import DataSourceFacade, DataSource
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UiDatePicker(ui.element, component="ui_date_picker.js"):
|
|
16
|
+
def __init__(self, date: str) -> None:
|
|
17
|
+
super().__init__()
|
|
18
|
+
self._props["id"] = self.id
|
|
19
|
+
self.value = date.replace("-", "/")
|
|
20
|
+
self._props["date"] = self.value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DatePickerResult(UiResult[UiDatePicker]):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
element: UiDatePicker,
|
|
27
|
+
dataSource: DataSource,
|
|
28
|
+
ref_value: Ref,
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__(element, dataSource)
|
|
31
|
+
self._ref_value = ref_value
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def value(self) -> str:
|
|
35
|
+
return self._ref_value.value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def ui_date_picker(
|
|
39
|
+
self: DataSourceFacade, column: str, *, value: Optional[str] = None, **kwargs
|
|
40
|
+
):
|
|
41
|
+
options = self._dataSource._idataSource.duplicates_column_values(self.data, column)
|
|
42
|
+
kwargs.update(
|
|
43
|
+
{
|
|
44
|
+
"options": options,
|
|
45
|
+
"label": column,
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
cp = UiDatePicker(value or "")
|
|
50
|
+
ref_value = to_ref(cp.value)
|
|
51
|
+
|
|
52
|
+
def onchange(e):
|
|
53
|
+
value = e.args
|
|
54
|
+
|
|
55
|
+
cp.value = value
|
|
56
|
+
ref_value.value = value # type: ignore
|
|
57
|
+
|
|
58
|
+
def data_filter(data):
|
|
59
|
+
if cp.value is None or not cp.value:
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
cond = None
|
|
63
|
+
cond = data[column] == cp.value
|
|
64
|
+
return data[cond]
|
|
65
|
+
|
|
66
|
+
self._dataSource.send_filter(cp.id, Filter(data_filter))
|
|
67
|
+
|
|
68
|
+
cp.on("update:value", onchange)
|
|
69
|
+
|
|
70
|
+
def on_source_update():
|
|
71
|
+
data = self._dataSource.get_filtered_data(cp)
|
|
72
|
+
# options = self._dataSource._idataSource.duplicates_column_values(data, column)
|
|
73
|
+
# value = cp.value
|
|
74
|
+
|
|
75
|
+
self._dataSource._register_component(cp.id, on_source_update)
|
|
76
|
+
|
|
77
|
+
return DatePickerResult(cp, self._dataSource, ref_value)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union, cast
|
|
3
|
+
from nicegui.events import UiEventArguments
|
|
4
|
+
from ex4nicegui.reactive import rxui
|
|
5
|
+
from ex4nicegui.reactive.EChartsComponent.ECharts import (
|
|
6
|
+
EChartsClickEventArguments,
|
|
7
|
+
echarts,
|
|
8
|
+
)
|
|
9
|
+
from nicegui import ui
|
|
10
|
+
from ex4nicegui.bi.dataSource import DataSource
|
|
11
|
+
from .models import UiResult
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ex4nicegui.bi.dataSourceFacade import DataSourceFacade
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EChartsResult(UiResult[echarts]):
|
|
18
|
+
def __init__(
|
|
19
|
+
self, element: echarts, dataSource: DataSource, chart_update: Callable
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(element, dataSource)
|
|
22
|
+
self.chart_update = chart_update
|
|
23
|
+
|
|
24
|
+
def on_chart_click(
|
|
25
|
+
self, handler: Optional[Callable[[EChartsClickEventArguments], Any]]
|
|
26
|
+
):
|
|
27
|
+
return self.element.on_chart_click(handler)
|
|
28
|
+
|
|
29
|
+
def on_chart_click_blank(
|
|
30
|
+
self, handler: Optional[Callable[[UiEventArguments], Any]]
|
|
31
|
+
):
|
|
32
|
+
return self.element.on_chart_click_blank(handler)
|
|
33
|
+
|
|
34
|
+
def cancel_linkage(self, *source: Union[ui.element, "UiResult"]):
|
|
35
|
+
super().cancel_linkage(*source)
|
|
36
|
+
self.element.update_options(
|
|
37
|
+
self.chart_update(self._dataSource.get_filtered_data(self.element))
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def ui_echarts(
|
|
42
|
+
self: DataSourceFacade,
|
|
43
|
+
fn: Callable[[Any], Union[Dict, "pyecharts.Base"]], # pyright: ignore
|
|
44
|
+
**kwargs,
|
|
45
|
+
):
|
|
46
|
+
def create_options(data):
|
|
47
|
+
options = fn(data)
|
|
48
|
+
if isinstance(options, Dict):
|
|
49
|
+
return options
|
|
50
|
+
|
|
51
|
+
import simplejson as json
|
|
52
|
+
from pyecharts.charts.chart import Base
|
|
53
|
+
|
|
54
|
+
if isinstance(options, Base):
|
|
55
|
+
return cast(Dict, json.loads(options.dump_options()))
|
|
56
|
+
|
|
57
|
+
raise TypeError(f"not support options type[{type(options)}]")
|
|
58
|
+
|
|
59
|
+
cp = rxui.echarts({})
|
|
60
|
+
ele_id = cp.element.id
|
|
61
|
+
|
|
62
|
+
def on_source_update():
|
|
63
|
+
data = self._dataSource.get_filtered_data(cp.element)
|
|
64
|
+
options = create_options(data)
|
|
65
|
+
cp.element.update_options(options)
|
|
66
|
+
|
|
67
|
+
info = self._dataSource._register_component(ele_id, on_source_update)
|
|
68
|
+
|
|
69
|
+
cp.element.update_options(
|
|
70
|
+
create_options(self._dataSource.get_filtered_data(cp.element))
|
|
71
|
+
)
|
|
72
|
+
return EChartsResult(cp.element, self._dataSource, create_options)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
from nicegui.elements.radio import Radio
|
|
5
|
+
from ex4nicegui import to_ref
|
|
6
|
+
from ex4nicegui.utils.signals import Ref
|
|
7
|
+
from ex4nicegui.bi.dataSource import DataSource, Filter
|
|
8
|
+
from .models import UiResult
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ex4nicegui.bi.dataSourceFacade import DataSourceFacade
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RadioResult(UiResult[ui.radio]):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
element: Radio,
|
|
18
|
+
dataSource: DataSource,
|
|
19
|
+
ref_value: Ref,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(element, dataSource)
|
|
22
|
+
self._ref_value = ref_value
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def value(self):
|
|
26
|
+
return self._ref_value.value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ui_radio(self: DataSourceFacade, column: str, **kwargs) -> RadioResult:
|
|
30
|
+
options = self._dataSource._idataSource.duplicates_column_values(self.data, column)
|
|
31
|
+
kwargs.update({"options": options})
|
|
32
|
+
|
|
33
|
+
cp = ui.radio(**kwargs)
|
|
34
|
+
ref_value = to_ref(cp.value)
|
|
35
|
+
|
|
36
|
+
def onchange(e):
|
|
37
|
+
cp.value = cp.options[e.args]
|
|
38
|
+
|
|
39
|
+
def data_filter(data):
|
|
40
|
+
if cp.value not in cp.options:
|
|
41
|
+
return data
|
|
42
|
+
cond = data[column] == cp.value
|
|
43
|
+
return data[cond]
|
|
44
|
+
|
|
45
|
+
self._dataSource.send_filter(cp.id, Filter(data_filter))
|
|
46
|
+
|
|
47
|
+
cp.on("update:modelValue", onchange)
|
|
48
|
+
|
|
49
|
+
def on_source_update():
|
|
50
|
+
data = self._dataSource.get_filtered_data(cp)
|
|
51
|
+
options = self._dataSource._idataSource.duplicates_column_values(data, column)
|
|
52
|
+
value = cp.value
|
|
53
|
+
if value not in options:
|
|
54
|
+
value = ""
|
|
55
|
+
|
|
56
|
+
cp.set_options(options, value=value)
|
|
57
|
+
|
|
58
|
+
self._dataSource._register_component(cp.id, on_source_update)
|
|
59
|
+
|
|
60
|
+
return RadioResult(cp, self._dataSource, ref_value)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from nicegui import ui
|
|
3
|
+
from ex4nicegui import to_ref
|
|
4
|
+
from ex4nicegui.utils.signals import Ref
|
|
5
|
+
from ex4nicegui.bi.dataSource import Filter
|
|
6
|
+
from .models import UiResult
|
|
7
|
+
from typing import Any, Callable, Optional, TYPE_CHECKING, cast
|
|
8
|
+
from typing_extensions import TypedDict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from nicegui.elements.mixins.disableable_element import DisableableElement
|
|
12
|
+
from nicegui.elements.mixins.value_element import ValueElement
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ex4nicegui.bi.dataSourceFacade import DataSourceFacade, DataSource
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QRangeValue(TypedDict):
|
|
20
|
+
min: float
|
|
21
|
+
max: float
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
TQRangeValue = Optional[QRangeValue]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QRange(ValueElement, DisableableElement):
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
min: float, # pylint: disable=redefined-builtin
|
|
32
|
+
max: float, # pylint: disable=redefined-builtin
|
|
33
|
+
step: float = 1.0,
|
|
34
|
+
value: TQRangeValue = None,
|
|
35
|
+
on_change: Optional[Callable[..., Any]] = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Slider
|
|
38
|
+
|
|
39
|
+
:param min: lower bound of the slider
|
|
40
|
+
:param max: upper bound of the slider
|
|
41
|
+
:param step: step size
|
|
42
|
+
:param value: initial value to set position of the slider
|
|
43
|
+
:param on_change: callback which is invoked when the user releases the slider
|
|
44
|
+
"""
|
|
45
|
+
super().__init__(
|
|
46
|
+
tag="q-range", value=value, on_value_change=on_change, throttle=0.05
|
|
47
|
+
)
|
|
48
|
+
self._props["min"] = min
|
|
49
|
+
self._props["max"] = max
|
|
50
|
+
self._props["step"] = step
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RangeResult(UiResult[QRange]):
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
element: QRange,
|
|
57
|
+
dataSource: DataSource,
|
|
58
|
+
ref_value: Ref,
|
|
59
|
+
) -> None:
|
|
60
|
+
super().__init__(element, dataSource)
|
|
61
|
+
self._ref_value = ref_value
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def value(self):
|
|
65
|
+
return cast(TQRangeValue, self._ref_value.value)
|
|
66
|
+
|
|
67
|
+
def drag_range(self):
|
|
68
|
+
self.element.props("drag-range")
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def ui_range(self: DataSourceFacade, column: str, **kwargs):
|
|
73
|
+
self._dataSource._idataSource.slider_check(self.data, column)
|
|
74
|
+
|
|
75
|
+
min, max = self._dataSource._idataSource.range_min_max(self.data, column)
|
|
76
|
+
kwargs.update({"min": min, "max": max, "value": {"min": min, "max": max}})
|
|
77
|
+
|
|
78
|
+
with ui.element("q-item").classes("w-full"):
|
|
79
|
+
with ui.element("q-item-section").props("avatar"):
|
|
80
|
+
ui.label(column)
|
|
81
|
+
with ui.element("q-item-section"):
|
|
82
|
+
cp = QRange(**kwargs).props("label")
|
|
83
|
+
|
|
84
|
+
ref_value = to_ref(cast(TQRangeValue, cp.value))
|
|
85
|
+
|
|
86
|
+
def onchange():
|
|
87
|
+
def data_filter(data):
|
|
88
|
+
cp_value = cast(TQRangeValue, cp.value)
|
|
89
|
+
|
|
90
|
+
if cp_value is None:
|
|
91
|
+
return data
|
|
92
|
+
cond = (data[column] >= cp_value["min"]) & (data[column] <= cp_value["max"])
|
|
93
|
+
return data[cond]
|
|
94
|
+
|
|
95
|
+
self._dataSource.send_filter(cp.id, Filter(data_filter))
|
|
96
|
+
|
|
97
|
+
cp.on("change", onchange)
|
|
98
|
+
|
|
99
|
+
def on_source_update():
|
|
100
|
+
data = self._dataSource.get_filtered_data(cp)
|
|
101
|
+
min, max = self._dataSource._idataSource.range_min_max(data, column)
|
|
102
|
+
if min is None or max is None:
|
|
103
|
+
cp.value = None
|
|
104
|
+
else:
|
|
105
|
+
new_value = cast(TQRangeValue, cp.value)
|
|
106
|
+
if new_value is not None:
|
|
107
|
+
new_value = new_value.copy()
|
|
108
|
+
|
|
109
|
+
if new_value["min"] < min:
|
|
110
|
+
new_value["min"] = min
|
|
111
|
+
if new_value["max"] > max:
|
|
112
|
+
new_value["max"] = max
|
|
113
|
+
|
|
114
|
+
cp.value = new_value
|
|
115
|
+
cp.update()
|
|
116
|
+
|
|
117
|
+
self._dataSource._register_component(cp.id, on_source_update)
|
|
118
|
+
|
|
119
|
+
return RangeResult(cp, self._dataSource, ref_value)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
from ex4nicegui import to_ref, ref_computed
|
|
5
|
+
from ex4nicegui.utils.signals import Ref
|
|
6
|
+
from ex4nicegui.bi.dataSource import Filter
|
|
7
|
+
from .models import UiResult
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ex4nicegui.bi.dataSourceFacade import DataSourceFacade, DataSource
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SelectResult(UiResult[ui.select]):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
element: ui.select,
|
|
17
|
+
dataSource: DataSource,
|
|
18
|
+
ref_value: Ref,
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(element, dataSource)
|
|
21
|
+
self._ref_value = ref_value
|
|
22
|
+
|
|
23
|
+
@ref_computed
|
|
24
|
+
def value_or_options():
|
|
25
|
+
if not self._ref_value.value:
|
|
26
|
+
return self.element.options
|
|
27
|
+
return self._ref_value.value
|
|
28
|
+
|
|
29
|
+
self._value_or_options = value_or_options
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def value(self):
|
|
33
|
+
return self.get_value()
|
|
34
|
+
|
|
35
|
+
def get_value(self, no_select_eq_all=True):
|
|
36
|
+
"""Get the selected items of the dropdown box
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
no_select_eq_all (bool, optional): When there is no selection, it is eq to selecting all. Defaults to True.
|
|
40
|
+
"""
|
|
41
|
+
if no_select_eq_all:
|
|
42
|
+
return self._value_or_options.value
|
|
43
|
+
|
|
44
|
+
return self._ref_value.value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def ui_select(
|
|
48
|
+
self: DataSourceFacade, column: str, *, clearable=True, multiple=True, **kwargs
|
|
49
|
+
) -> SelectResult:
|
|
50
|
+
options = self._dataSource._idataSource.duplicates_column_values(self.data, column)
|
|
51
|
+
kwargs.update(
|
|
52
|
+
{
|
|
53
|
+
"options": options,
|
|
54
|
+
"multiple": multiple,
|
|
55
|
+
"clearable": clearable,
|
|
56
|
+
"label": column,
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
cp = ui.select(**kwargs).props("use-chips outlined").classes("min-w-[8rem]")
|
|
61
|
+
ref_value = to_ref(cp.value)
|
|
62
|
+
|
|
63
|
+
def onchange(e):
|
|
64
|
+
value = None
|
|
65
|
+
if e.args:
|
|
66
|
+
if isinstance(e.args, list):
|
|
67
|
+
value = [arg["label"] for arg in e.args]
|
|
68
|
+
else:
|
|
69
|
+
value = e.args["label"]
|
|
70
|
+
|
|
71
|
+
cp.value = value
|
|
72
|
+
ref_value.value = value # type: ignore
|
|
73
|
+
|
|
74
|
+
def data_filter(data):
|
|
75
|
+
if cp.value is None or not cp.value:
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
cond = None
|
|
79
|
+
if isinstance(cp.value, list):
|
|
80
|
+
cond = data[column].isin(cp.value)
|
|
81
|
+
else:
|
|
82
|
+
cond = data[column] == cp.value
|
|
83
|
+
return data[cond]
|
|
84
|
+
|
|
85
|
+
self._dataSource.send_filter(cp.id, Filter(data_filter))
|
|
86
|
+
|
|
87
|
+
cp.on("update:modelValue", onchange)
|
|
88
|
+
|
|
89
|
+
def on_source_update():
|
|
90
|
+
data = self._dataSource.get_filtered_data(cp)
|
|
91
|
+
options = self._dataSource._idataSource.duplicates_column_values(data, column)
|
|
92
|
+
value = cp.value
|
|
93
|
+
|
|
94
|
+
# Make the value within the options
|
|
95
|
+
if isinstance(value, list):
|
|
96
|
+
value = list(set(value) & set(options))
|
|
97
|
+
else:
|
|
98
|
+
if value not in options:
|
|
99
|
+
value = ""
|
|
100
|
+
|
|
101
|
+
cp.set_options(options, value=value)
|
|
102
|
+
|
|
103
|
+
self._dataSource._register_component(cp.id, on_source_update)
|
|
104
|
+
|
|
105
|
+
return SelectResult(cp, self._dataSource, ref_value)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
from ex4nicegui import to_ref
|
|
5
|
+
from ex4nicegui.utils.signals import Ref
|
|
6
|
+
from ex4nicegui.bi.dataSource import Filter
|
|
7
|
+
from .models import UiResult
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ex4nicegui.bi.dataSourceFacade import DataSourceFacade, DataSource
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SliderResult(UiResult[ui.slider]):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
element: ui.slider,
|
|
17
|
+
dataSource: DataSource,
|
|
18
|
+
ref_value: Ref,
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(element, dataSource)
|
|
21
|
+
self._ref_value = ref_value
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def value(self):
|
|
25
|
+
return self._ref_value.value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ui_slider(self: DataSourceFacade, column: str, **kwargs):
|
|
29
|
+
self._dataSource._idataSource.slider_check(self.data, column)
|
|
30
|
+
|
|
31
|
+
min, max = self._dataSource._idataSource.slider_min_max(self.data, column)
|
|
32
|
+
kwargs.update({"min": min, "max": max})
|
|
33
|
+
|
|
34
|
+
cp = ui.slider(**kwargs).props("label label-always switch-label-side")
|
|
35
|
+
ref_value = to_ref(cp.value)
|
|
36
|
+
|
|
37
|
+
def onchange():
|
|
38
|
+
def data_filter(data):
|
|
39
|
+
if cp.value is None or cp.value < min:
|
|
40
|
+
return data
|
|
41
|
+
cond = data[column] == cp.value
|
|
42
|
+
return data[cond]
|
|
43
|
+
|
|
44
|
+
self._dataSource.send_filter(cp.id, Filter(data_filter))
|
|
45
|
+
|
|
46
|
+
cp.on("change", onchange)
|
|
47
|
+
|
|
48
|
+
def on_source_update():
|
|
49
|
+
data = self._dataSource.get_filtered_data(cp)
|
|
50
|
+
min, max = self._dataSource._idataSource.slider_min_max(data, column)
|
|
51
|
+
if min is None or max is None:
|
|
52
|
+
cp.value = None
|
|
53
|
+
else:
|
|
54
|
+
cp._props["min"] = min
|
|
55
|
+
cp._props["max"] = max
|
|
56
|
+
|
|
57
|
+
self._dataSource._register_component(cp.id, on_source_update)
|
|
58
|
+
|
|
59
|
+
return SliderResult(cp, self._dataSource, ref_value)
|
ex4nicegui/bi/protocols.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typing_extensions import Protocol
|
|
2
|
-
from typing import Callable, Dict, List, Optional, Tuple
|
|
2
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
3
3
|
|
|
4
4
|
from ex4nicegui.bi.types import _TFilterCallback
|
|
5
5
|
from .types import _TFilterCallback
|
|
@@ -7,10 +7,10 @@ from ex4nicegui.utils import common as utils_common
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class IDataSourceAble(Protocol):
|
|
10
|
-
def get_data(self):
|
|
10
|
+
def get_data(self) -> Any:
|
|
11
11
|
...
|
|
12
12
|
|
|
13
|
-
def apply_filters(self, data, filters: List[_TFilterCallback]):
|
|
13
|
+
def apply_filters(self, data, filters: List[_TFilterCallback]) -> Any:
|
|
14
14
|
...
|
|
15
15
|
|
|
16
16
|
def duplicates_column_values(self, data, column_name: str) -> List:
|
|
@@ -27,6 +27,14 @@ class IDataSourceAble(Protocol):
|
|
|
27
27
|
) -> Tuple[Optional[float], Optional[float]]:
|
|
28
28
|
...
|
|
29
29
|
|
|
30
|
+
def range_check(self, data, column_name: str) -> None:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def range_min_max(
|
|
34
|
+
self, data, column_name: str
|
|
35
|
+
) -> Tuple[Optional[float], Optional[float]]:
|
|
36
|
+
...
|
|
37
|
+
|
|
30
38
|
|
|
31
39
|
class DataFrameDataSourceAble(IDataSourceAble):
|
|
32
40
|
def __init__(self, df) -> None:
|
|
@@ -70,6 +78,24 @@ class DataFrameDataSourceAble(IDataSourceAble):
|
|
|
70
78
|
|
|
71
79
|
return min, max
|
|
72
80
|
|
|
81
|
+
def range_check(self, data, column_name: str) -> None:
|
|
82
|
+
from pandas.api.types import is_numeric_dtype
|
|
83
|
+
|
|
84
|
+
if not is_numeric_dtype(data[column_name]):
|
|
85
|
+
raise ValueError(f"column[{column_name}] must be numeric type")
|
|
86
|
+
|
|
87
|
+
def range_min_max(
|
|
88
|
+
self, data, column_name: str
|
|
89
|
+
) -> Tuple[Optional[float], Optional[float]]:
|
|
90
|
+
import numpy as np
|
|
91
|
+
|
|
92
|
+
min, max = data[column_name].min(), data[column_name].max()
|
|
93
|
+
|
|
94
|
+
if np.isnan(min) or np.isnan(max):
|
|
95
|
+
return None, None
|
|
96
|
+
|
|
97
|
+
return min, max
|
|
98
|
+
|
|
73
99
|
|
|
74
100
|
class CallableDataSourceAble(IDataSourceAble):
|
|
75
101
|
def __init__(self, fn: Callable) -> None:
|
|
@@ -89,14 +115,17 @@ class CallableDataSourceAble(IDataSourceAble):
|
|
|
89
115
|
return data[column_name].drop_duplicates().tolist()
|
|
90
116
|
|
|
91
117
|
def get_aggrid_options(self, data) -> Dict:
|
|
92
|
-
df = data
|
|
118
|
+
df = utils_common.convert_dataframe(data)
|
|
93
119
|
return {
|
|
94
120
|
"columnDefs": [{"field": col} for col in df.columns],
|
|
95
121
|
"rowData": df.to_dict("records"),
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
def slider_check(self, data, column_name: str) -> None:
|
|
99
|
-
|
|
125
|
+
from pandas.api.types import is_numeric_dtype
|
|
126
|
+
|
|
127
|
+
if not is_numeric_dtype(data[column_name]):
|
|
128
|
+
raise ValueError(f"column[{column_name}] must be numeric type")
|
|
100
129
|
|
|
101
130
|
def slider_min_max(
|
|
102
131
|
self, data, column_name: str
|
|
@@ -109,3 +138,21 @@ class CallableDataSourceAble(IDataSourceAble):
|
|
|
109
138
|
return None, None
|
|
110
139
|
|
|
111
140
|
return min, max
|
|
141
|
+
|
|
142
|
+
def range_check(self, data, column_name: str) -> None:
|
|
143
|
+
from pandas.api.types import is_numeric_dtype
|
|
144
|
+
|
|
145
|
+
if not is_numeric_dtype(data[column_name]):
|
|
146
|
+
raise ValueError(f"column[{column_name}] must be numeric type")
|
|
147
|
+
|
|
148
|
+
def range_min_max(
|
|
149
|
+
self, data, column_name: str
|
|
150
|
+
) -> Tuple[Optional[float], Optional[float]]:
|
|
151
|
+
import numpy as np
|
|
152
|
+
|
|
153
|
+
min, max = data[column_name].min(), data[column_name].max()
|
|
154
|
+
|
|
155
|
+
if np.isnan(min) or np.isnan(max):
|
|
156
|
+
return None, None
|
|
157
|
+
|
|
158
|
+
return min, max
|
ex4nicegui/bi/types.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from typing import Callable, TypeVar
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
_TData = TypeVar("_TData")
|
|
5
7
|
|
|
6
8
|
_TElementID = int
|
|
7
9
|
_TNgClientID = str
|
|
8
|
-
_TComponentUpdateCallback = Callable[[_TData], None]
|
|
9
10
|
_TFilterCallback = Callable[[_TData], _TData]
|
|
10
11
|
|
|
11
12
|
_TDataSourceId = int
|
|
@@ -55,6 +55,7 @@ class echarts(Element, component="ECharts.js"):
|
|
|
55
55
|
[open echarts setOption docs](https://echarts.apache.org/zh/api.html#echartsInstance.setOption)
|
|
56
56
|
|
|
57
57
|
"""
|
|
58
|
+
self._props["options"] = options
|
|
58
59
|
self.run_method("updateOptions", options, opts)
|
|
59
60
|
self.update()
|
|
60
61
|
return self
|
|
@@ -29,7 +29,7 @@ from nicegui.elements.mixins.text_element import TextElement
|
|
|
29
29
|
|
|
30
30
|
T = TypeVar("T")
|
|
31
31
|
|
|
32
|
-
TWidget = TypeVar("TWidget")
|
|
32
|
+
TWidget = TypeVar("TWidget", bound=ui.element)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class BindableUi(Generic[TWidget]):
|
|
@@ -61,6 +61,13 @@ class BindableUi(Generic[TWidget]):
|
|
|
61
61
|
cast(ui.element, self.element).style(add, remove=remove, replace=replace)
|
|
62
62
|
return self
|
|
63
63
|
|
|
64
|
+
def __enter__(self) -> Self:
|
|
65
|
+
self.element.__enter__()
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def __exit__(self, *_):
|
|
69
|
+
self.element.default_slot.__exit__(*_)
|
|
70
|
+
|
|
64
71
|
def tooltip(self, text: str) -> Self:
|
|
65
72
|
cast(ui.element, self.element).tooltip(text)
|
|
66
73
|
return self
|
|
@@ -78,6 +85,10 @@ class BindableUi(Generic[TWidget]):
|
|
|
78
85
|
def element(self):
|
|
79
86
|
return self.__element
|
|
80
87
|
|
|
88
|
+
def delete(self) -> None:
|
|
89
|
+
"""Delete the element."""
|
|
90
|
+
self.element.delete()
|
|
91
|
+
|
|
81
92
|
def bind_prop(self, prop: str, ref_ui: ReadonlyRef):
|
|
82
93
|
if prop == "visible":
|
|
83
94
|
return self.bind_visible(ref_ui)
|
|
@@ -51,8 +51,7 @@ class DrawerBindableUi(SingleValueBindableUi[bool, Drawer]):
|
|
|
51
51
|
else:
|
|
52
52
|
element = ui.right_drawer(**value_kws)
|
|
53
53
|
|
|
54
|
-
element.
|
|
55
|
-
element.classes("flex flex-col gap-4")
|
|
54
|
+
element.classes("flex flex-col gap-4 backdrop-blur-md bg-[#5898d4]/30")
|
|
56
55
|
|
|
57
56
|
init_value = (
|
|
58
57
|
element._props["model-value"]
|