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.
@@ -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)
@@ -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
- pass
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)
@@ -57,7 +57,7 @@ class ButtonBindableUi(SingleValueBindableUi[str, ui.button]):
57
57
  @effect
58
58
  def _():
59
59
  ele = self.element
60
- ele._props["text"] = ref_ui.value
60
+ ele._props["label"] = ref_ui.value
61
61
  ele.update()
62
62
 
63
63
  return self
@@ -51,8 +51,7 @@ class DrawerBindableUi(SingleValueBindableUi[bool, Drawer]):
51
51
  else:
52
52
  element = ui.right_drawer(**value_kws)
53
53
 
54
- element.style(f"background-color:rgba(25, 118, 210,0.3)")
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"]