ex4nicegui 0.2.18__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.
Files changed (33) hide show
  1. ex4nicegui/__init__.py +1 -1
  2. ex4nicegui/bi/__init__.py +13 -0
  3. ex4nicegui/bi/dataSource.py +203 -0
  4. ex4nicegui/bi/dataSourceFacade.py +148 -0
  5. ex4nicegui/bi/elements/__init__.py +0 -0
  6. ex4nicegui/bi/elements/containers.py +13 -0
  7. ex4nicegui/bi/elements/layouts.py +28 -0
  8. ex4nicegui/bi/elements/models.py +55 -0
  9. ex4nicegui/bi/elements/text.py +33 -0
  10. ex4nicegui/bi/elements/ui_aggrid.py +46 -0
  11. ex4nicegui/bi/elements/ui_date_picker.js +35 -0
  12. ex4nicegui/bi/elements/ui_date_picker.py +77 -0
  13. ex4nicegui/bi/elements/ui_echarts.py +72 -0
  14. ex4nicegui/bi/elements/ui_radio.py +60 -0
  15. ex4nicegui/bi/elements/ui_range.py +119 -0
  16. ex4nicegui/bi/elements/ui_select.py +105 -0
  17. ex4nicegui/bi/elements/ui_slider.py +59 -0
  18. ex4nicegui/bi/index.py +76 -0
  19. ex4nicegui/bi/protocols.py +158 -0
  20. ex4nicegui/bi/types.py +13 -0
  21. ex4nicegui/reactive/EChartsComponent/ECharts.js +1 -2
  22. ex4nicegui/reactive/EChartsComponent/ECharts.py +1 -0
  23. ex4nicegui/reactive/officials/base.py +12 -1
  24. ex4nicegui/reactive/officials/button.py +1 -1
  25. ex4nicegui/reactive/officials/drawer.py +1 -2
  26. ex4nicegui/reactive/officials/echarts.py +1 -1
  27. ex4nicegui/utils/clientScope.py +42 -0
  28. ex4nicegui/utils/signals.py +54 -5
  29. {ex4nicegui-0.2.18.dist-info → ex4nicegui-0.3.1.dist-info}/METADATA +2 -2
  30. {ex4nicegui-0.2.18.dist-info → ex4nicegui-0.3.1.dist-info}/RECORD +33 -13
  31. {ex4nicegui-0.2.18.dist-info → ex4nicegui-0.3.1.dist-info}/LICENSE +0 -0
  32. {ex4nicegui-0.2.18.dist-info → ex4nicegui-0.3.1.dist-info}/WHEEL +0 -0
  33. {ex4nicegui-0.2.18.dist-info → ex4nicegui-0.3.1.dist-info}/top_level.txt +0 -0
@@ -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/index.py ADDED
@@ -0,0 +1,76 @@
1
+ from .dataSourceFacade import DataSourceFacade
2
+ from .dataSource import DataSource
3
+ from .protocols import CallableDataSourceAble, DataFrameDataSourceAble
4
+ from typing import Callable, cast, TypeVar, Union
5
+
6
+
7
+ _TData = TypeVar("_TData")
8
+
9
+
10
+ def data_source(data: Union[Callable[..., _TData], _TData]) -> DataSourceFacade[_TData]:
11
+ """Create a data source
12
+
13
+ Args:
14
+ data (_TData): Any supported data
15
+
16
+ Raises:
17
+ TypeError: Throw an error when the data type is not supported
18
+
19
+ Returns:
20
+ DataSourceFacade[_TData]: _description_
21
+
22
+ ## Examples
23
+
24
+ ---
25
+
26
+ pandas dataframe source:
27
+ ```python
28
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
29
+ ds = bi.data_source(df)
30
+ ```
31
+
32
+ ---
33
+
34
+ Link multiple data sources
35
+
36
+ ---
37
+ ```python
38
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
39
+ ds = bi.data_source(df)
40
+
41
+ @bi.data_source
42
+ def ds_other():
43
+ # ds.filtered_data is DataFrame after filtering
44
+ where = ds.filtered_data[''].isin(['b','c','d'])
45
+ return ds.filtered_data[where]
46
+
47
+ ```
48
+
49
+ ---
50
+
51
+ Now, when `ds` changes, it will trigger changes to `ds_other` and thus drive the related interface components to change.
52
+
53
+ ```python
54
+ # select box of data source 'ds'
55
+ # it change will trigger changes to table
56
+ ds.ui_select('name')
57
+
58
+ # table of data 'ds_other'
59
+ ds_other.ui_aggrid()
60
+ ```
61
+
62
+
63
+ """
64
+ ds_protocol = None
65
+
66
+ if type(data).__name__ == "DataFrame":
67
+ ds_protocol = DataFrameDataSourceAble(data)
68
+
69
+ if isinstance(data, Callable):
70
+ ds_protocol = CallableDataSourceAble(data)
71
+
72
+ if ds_protocol is None:
73
+ raise TypeError(f"not support type[{type(data)}]")
74
+
75
+ ds = DataSource(ds_protocol)
76
+ return cast(DataSourceFacade[_TData], DataSourceFacade(ds))
@@ -0,0 +1,158 @@
1
+ from typing_extensions import Protocol
2
+ from typing import Any, Callable, Dict, List, Optional, Tuple
3
+
4
+ from ex4nicegui.bi.types import _TFilterCallback
5
+ from .types import _TFilterCallback
6
+ from ex4nicegui.utils import common as utils_common
7
+
8
+
9
+ class IDataSourceAble(Protocol):
10
+ def get_data(self) -> Any:
11
+ ...
12
+
13
+ def apply_filters(self, data, filters: List[_TFilterCallback]) -> Any:
14
+ ...
15
+
16
+ def duplicates_column_values(self, data, column_name: str) -> List:
17
+ ...
18
+
19
+ def get_aggrid_options(self, data) -> Dict:
20
+ ...
21
+
22
+ def slider_check(self, data, column_name: str) -> None:
23
+ ...
24
+
25
+ def slider_min_max(
26
+ self, data, column_name: str
27
+ ) -> Tuple[Optional[float], Optional[float]]:
28
+ ...
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
+
38
+
39
+ class DataFrameDataSourceAble(IDataSourceAble):
40
+ def __init__(self, df) -> None:
41
+ self.data = df
42
+
43
+ def get_data(self):
44
+ return self.data
45
+
46
+ def apply_filters(self, data, filters: List[_TFilterCallback]):
47
+ new_data = data
48
+ for f in filters:
49
+ new_data = f(new_data)
50
+
51
+ return new_data
52
+
53
+ def duplicates_column_values(self, data, column_name: str) -> List:
54
+ return data[column_name].drop_duplicates().tolist()
55
+
56
+ def get_aggrid_options(self, data) -> Dict:
57
+ df = utils_common.convert_dataframe(data)
58
+ return {
59
+ "columnDefs": [{"field": col} for col in df.columns],
60
+ "rowData": df.to_dict("records"),
61
+ }
62
+
63
+ def slider_check(self, data, column_name: str) -> None:
64
+ from pandas.api.types import is_numeric_dtype
65
+
66
+ if not is_numeric_dtype(data[column_name]):
67
+ raise ValueError(f"column[{column_name}] must be numeric type")
68
+
69
+ def slider_min_max(
70
+ self, data, column_name: str
71
+ ) -> Tuple[Optional[float], Optional[float]]:
72
+ import numpy as np
73
+
74
+ min, max = data[column_name].min(), data[column_name].max()
75
+
76
+ if np.isnan(min) or np.isnan(max):
77
+ return None, None
78
+
79
+ return min, max
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
+
99
+
100
+ class CallableDataSourceAble(IDataSourceAble):
101
+ def __init__(self, fn: Callable) -> None:
102
+ self.data_fn = fn
103
+
104
+ def get_data(self):
105
+ return self.data_fn()
106
+
107
+ def apply_filters(self, data, filters: List[_TFilterCallback]):
108
+ new_data = data
109
+ for f in filters:
110
+ new_data = f(new_data)
111
+
112
+ return new_data
113
+
114
+ def duplicates_column_values(self, data, column_name: str) -> List:
115
+ return data[column_name].drop_duplicates().tolist()
116
+
117
+ def get_aggrid_options(self, data) -> Dict:
118
+ df = utils_common.convert_dataframe(data)
119
+ return {
120
+ "columnDefs": [{"field": col} for col in df.columns],
121
+ "rowData": df.to_dict("records"),
122
+ }
123
+
124
+ def slider_check(self, data, column_name: str) -> None:
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")
129
+
130
+ def slider_min_max(
131
+ self, data, column_name: str
132
+ ) -> Tuple[Optional[float], Optional[float]]:
133
+ import numpy as np
134
+
135
+ min, max = data[column_name].min(), data[column_name].max()
136
+
137
+ if np.isnan(min) or np.isnan(max):
138
+ return None, None
139
+
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 ADDED
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+ from typing import Callable, TypeVar
3
+ from typing import TYPE_CHECKING
4
+
5
+
6
+ _TData = TypeVar("_TData")
7
+
8
+ _TElementID = int
9
+ _TNgClientID = str
10
+ _TFilterCallback = Callable[[_TData], _TData]
11
+
12
+ _TDataSourceId = int
13
+ _TSourceBuildFn = Callable[..., _TData]
@@ -43967,8 +43967,7 @@ const TX = { class: "echart-container relative" }, CX = /* @__PURE__ */ GL({
43967
43967
  l.target || t("chartClickBlank");
43968
43968
  });
43969
43969
  function s() {
43970
- const l = n.value.getBoundingClientRect();
43971
- i == null || i.resize({ width: l.width - 5, height: l.height - 5 });
43970
+ i == null || i.resize();
43972
43971
  }
43973
43972
  ZL("resize", () => {
43974
43973
  s();
@@ -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