from typing import Any, Callable, Dict, Optional
from typing_extensions import Self
from .. import optional_features
from ..awaitable_response import AwaitableResponse
from ..element import Element
from ..events import EChartPointClickEventArguments, GenericEventArguments, handle_event
try:
from pyecharts.charts.base import default, json
from pyecharts.charts.chart import Base as Chart
from pyecharts.commons.utils import JsCode
JS_CODE_MARKER = JsCode('\n').js_code.split('\n')[0]
optional_features.register('pyecharts')
except ImportError:
pass
class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min.js']):
def __init__(self, options: Dict, on_point_click: Optional[Callable] = None) -> None:
"""Apache EChart
An element to create a chart using `ECharts `_.
Updates can be pushed to the chart by changing the `options` property.
After data has changed, call the `update` method to refresh the chart.
:param options: dictionary of EChart options
:param on_click_point: callback that is invoked when a point is clicked
"""
super().__init__()
self._props['options'] = options
self._classes.append('nicegui-echart')
if on_point_click:
self.on_point_click(on_point_click)
def on_point_click(self, callback: Callable[..., Any]) -> Self:
"""Add a callback to be invoked when a point is clicked."""
def handle_point_click(e: GenericEventArguments) -> None:
handle_event(callback, 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',
])
return self
@classmethod
def from_pyecharts(cls, chart: 'Chart', on_point_click: Optional[Callable] = None) -> Self:
"""Create an echart element from a pyecharts object.
:param chart: pyecharts chart object
:param on_click_point: callback which is invoked when a point is clicked
:return: echart element
"""
options = json.loads(json.dumps(chart.get_options(), default=default, ignore_nan=True))
stack = [options]
while stack:
current = stack.pop()
if isinstance(current, list):
stack.extend(current)
elif isinstance(current, dict):
for key, value in tuple(current.items()):
if isinstance(value, str) and value.startswith(JS_CODE_MARKER) and value.endswith(JS_CODE_MARKER):
current[f':{key}'] = current.pop(key)[len(JS_CODE_MARKER):-len(JS_CODE_MARKER)]
else:
stack.append(value)
return cls(options, on_point_click)
@property
def options(self) -> Dict:
"""The options dictionary."""
return self._props['options']
def update(self) -> None:
super().update()
self.run_method('update_chart')
def run_chart_method(self, name: str, *args, timeout: float = 1,
check_interval: float = 0.01) -> AwaitableResponse:
"""Run a method of the JSONEditor instance.
See the `ECharts documentation `_ for a list of methods.
If the function is awaited, the result of the method call is returned.
Otherwise, the method is executed without waiting for a response.
:param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
:param args: arguments to pass to the method (Python objects or JavaScript expressions)
:param timeout: timeout in seconds (default: 1 second)
:return: AwaitableResponse that can be awaited to get the result of the method call
"""
return self.run_method('run_chart_method', name, *args, timeout=timeout, check_interval=check_interval)