Browse Source

Merge pull request #1579 from thetableman/echart-enhancements

Echart enhancements - onclick and dynamic options
Falko Schindler 1 year ago
parent
commit
6f63ba41aa

+ 5 - 1
nicegui/elements/echart.js

@@ -1,8 +1,11 @@
+import { convertDynamicProperties } from "../../static/utils/dynamic_properties.js";
+
 export default {
   template: "<div></div>",
   mounted() {
     this.chart = echarts.init(this.$el);
-    this.chart.setOption(this.options);
+    this.chart.on("click", (e) => this.$emit("pointClick", e));
+    this.update_chart();
     new ResizeObserver(this.chart.resize).observe(this.$el);
   },
   beforeDestroy() {
@@ -13,6 +16,7 @@ export default {
   },
   methods: {
     update_chart() {
+      convertDynamicProperties(this.options, true);
       this.chart.setOption(this.options);
     },
   },

+ 31 - 2
nicegui/elements/echart.py

@@ -1,11 +1,12 @@
-from typing import Dict
+from typing import Callable, Dict, Optional
 
 from ..element import Element
+from ..events import EChartPointClickEventArguments, GenericEventArguments, handle_event
 
 
 class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min.js']):
 
-    def __init__(self, options: Dict) -> None:
+    def __init__(self, options: Dict, on_point_click: Optional[Callable] = None) -> None:
         """Apache EChart
 
         An element to create a chart using `ECharts <https://echarts.apache.org/>`_.
@@ -13,11 +14,39 @@ class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min
         After data has changed, call the `update` method to refresh the chart.
 
         :param options: dictionary of EChart options
+        :param on_click_point: callback function that is called when a point is clicked
         """
         super().__init__()
         self._props['options'] = options
         self._classes = ['nicegui-echart']
 
+        if on_point_click:
+            def handle_point_click(e: GenericEventArguments) -> None:
+                handle_event(on_point_click, EChartPointClickEventArguments(
+                    sender=self,
+                    client=self.client,
+                    component_type=e.args['componentType'],
+                    series_type=e.args['seriesType'],
+                    series_index=e.args['seriesIndex'],
+                    series_name=e.args['seriesName'],
+                    name=e.args['name'],
+                    data_index=e.args['dataIndex'],
+                    data=e.args['data'],
+                    data_type=e.args.get('dataType'),
+                    value=e.args['value'],
+                ))
+            self.on('pointClick', handle_point_click, [
+                'componentType',
+                'seriesType',
+                'seriesIndex',
+                'seriesName',
+                'name',
+                'dataIndex',
+                'data',
+                'dataType',
+                'value',
+            ])
+
     @property
     def options(self) -> Dict:
         return self._props['options']

+ 13 - 0
nicegui/events.py

@@ -53,6 +53,19 @@ class ChartEventArguments(UiEventArguments):
     event_type: str
 
 
+@dataclass(**KWONLY_SLOTS)
+class EChartPointClickEventArguments(UiEventArguments):
+    component_type: str
+    series_type: str
+    series_index: int
+    series_name: str
+    name: str
+    data_index: int
+    data: Union[float, int, str]
+    data_type: str
+    value: Union[float, int, list]
+
+
 @dataclass(**KWONLY_SLOTS)
 class ChartPointClickEventArguments(ChartEventArguments):
     series_index: int

+ 63 - 0
tests/test_echart.py

@@ -0,0 +1,63 @@
+from nicegui import ui
+
+from .screen import Screen
+
+
+def test_create_dynamically(screen: Screen):
+    def create():
+        ui.echart({
+            'xAxis': {'type': 'value'},
+            'yAxis': {'type': 'category', 'data': ['A', 'B', 'C']},
+            'series': [{'type': 'line', 'data': [0.1, 0.2, 0.3]}],
+        })
+    ui.button('Create', on_click=create)
+
+    screen.open('/')
+    screen.click('Create')
+    assert screen.find_by_tag('canvas')
+
+
+def test_update(screen: Screen):
+    def update():
+        chart.options['xAxis'] = {'type': 'value'}
+        chart.options['yAxis'] = {'type': 'category', 'data': ['A', 'B', 'C']}
+        chart.options['series'] = [{'type': 'line', 'data': [0.1, 0.2, 0.3]}]
+        chart.update()
+    chart = ui.echart({})
+    ui.button('Update', on_click=update)
+
+    screen.open('/')
+    assert not screen.find_all_by_tag('canvas')
+    screen.click('Update')
+    assert screen.find_by_tag('canvas')
+
+
+def test_nested_card(screen: Screen):
+    with ui.card().style('height: 200px; width: 600px'):
+        ui.echart({
+            'xAxis': {'type': 'value'},
+            'yAxis': {'type': 'category', 'data': ['A', 'B', 'C']},
+            'series': [{'type': 'line', 'data': [0.1, 0.2, 0.3]}],
+        })
+
+    screen.open('/')
+    canvas = screen.find_by_tag('canvas')
+    assert canvas.rect['height'] == 168
+    assert canvas.rect['width'] == 568
+
+
+def test_nested_expansion(screen: Screen):
+    with ui.expansion() as expansion:
+        with ui.card().style('height: 200px; width: 600px'):
+            ui.echart({
+                'xAxis': {'type': 'value'},
+                'yAxis': {'type': 'category', 'data': ['A', 'B', 'C']},
+                'series': [{'type': 'line', 'data': [0.1, 0.2, 0.3]}],
+            })
+    ui.button('Open', on_click=expansion.open)
+
+    screen.open('/')
+    screen.click('Open')
+    canvas = screen.find_by_tag('canvas')
+    assert canvas.rect['height'] == 168
+    assert canvas.rect['width'] == 568

+ 25 - 0
website/more_documentation/echart_documentation.py

@@ -1,5 +1,7 @@
 from nicegui import ui
 
+from ..documentation_tools import text_demo
+
 
 def main_demo() -> None:
     from random import random
@@ -19,3 +21,26 @@ def main_demo() -> None:
         echart.update()
 
     ui.button('Update', on_click=update)
+
+
+def more() -> None:
+    @text_demo('EChart with clickable points', '''
+        You can register a callback for an event when a series point is clicked.
+    ''')
+    def clickable_points() -> None:
+        ui.echart({
+            'xAxis': {'type': 'category'},
+            'yAxis': {'type': 'value'},
+            'series': [{'type': 'line', 'data': [20, 10, 30, 50, 40, 30]}],
+        }, on_point_click=ui.notify)
+
+    @text_demo('EChart with dynamic properties', '''
+        Dynamic properties can be passed to chart elements to customize them such as apply an axis label format.
+        To make a property dynamic, prefix a colon ":" to the property name.
+    ''')
+    def dynamic_properties() -> None:
+        ui.echart({
+            'xAxis': {'type': 'category'},
+            'yAxis': {'axisLabel': {':formatter': 'value => "$" + value'}},
+            'series': [{'type': 'line', 'data': [5, 8, 13, 21, 34, 55]}],
+        })