|
@@ -2,10 +2,21 @@ from typing import 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:
|
|
|
+ import simplejson as json # TODO: replace library or add to requirements or optional features
|
|
|
+ from pyecharts.charts.base import default
|
|
|
+ from pyecharts.charts.chart import Base as Chart
|
|
|
+ from pyecharts.commons.utils import JsCode
|
|
|
+ JS_CODE_PREFIX = JsCode('⬌').js_code.split('⬌')[0]
|
|
|
+ optional_features.register('pyecharts')
|
|
|
+except ImportError:
|
|
|
+ pass
|
|
|
+
|
|
|
|
|
|
class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min.js']):
|
|
|
|
|
@@ -51,15 +62,26 @@ class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min
|
|
|
])
|
|
|
|
|
|
@classmethod
|
|
|
- def from_pyecharts(cls, chart_object: object, on_point_click: Optional[Callable] = None) -> Self:
|
|
|
+ def from_pyecharts(cls, chart: 'Chart', on_point_click: Optional[Callable] = None) -> Self:
|
|
|
"""Create an echart element from a pyecharts object.
|
|
|
|
|
|
- :param chart_object: pyecharts chart object
|
|
|
+ :param chart: pyecharts chart object
|
|
|
:param on_click_point: callback function that is called when a point is clicked
|
|
|
|
|
|
:return: echart element
|
|
|
"""
|
|
|
- options = _PyechartsUtils.chart2options(chart_object)
|
|
|
+ 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_PREFIX) and value.endswith(JS_CODE_PREFIX):
|
|
|
+ current[f':{key}'] = current.pop(key)[len(JS_CODE_PREFIX):-len(JS_CODE_PREFIX)]
|
|
|
+ else:
|
|
|
+ stack.append(value)
|
|
|
return cls(options, on_point_click)
|
|
|
|
|
|
@property
|
|
@@ -88,48 +110,3 @@ class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min
|
|
|
: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)
|
|
|
-
|
|
|
-
|
|
|
-class _PyechartsUtils:
|
|
|
- # pyecharts is not defined as a constant.
|
|
|
- # https://github.com/pyecharts/pyecharts/blob/f92c839a51d3878eeb24504ad191706c9db2c2ed/pyecharts/commons/utils.py#L8
|
|
|
- JS_CODE_PREFIX = '--x_x--0_0--'
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def chart2options(cls, chart) -> Dict:
|
|
|
- """Convert pyecharts chart object to options dictionary."""
|
|
|
- # pylint: disable=import-outside-toplevel
|
|
|
- import simplejson as json
|
|
|
- from pyecharts.charts.base import default
|
|
|
- from pyecharts.charts.chart import Base
|
|
|
-
|
|
|
- assert isinstance(chart, Base), 'must be a pyecharts chart object'
|
|
|
-
|
|
|
- dumps_str = json.dumps(chart.get_options(), default=default, ignore_nan=True)
|
|
|
- opts = json.loads(dumps_str)
|
|
|
- cls._replace_key_name_of_js_code(opts)
|
|
|
- return opts
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def _replace_key_name_of_js_code(cls, opts: Dict) -> None:
|
|
|
- stack = [opts]
|
|
|
- while stack:
|
|
|
- cur = stack.pop()
|
|
|
- if isinstance(cur, list):
|
|
|
- stack.extend(cur)
|
|
|
- elif isinstance(cur, dict):
|
|
|
- for key, value in tuple(cur.items()):
|
|
|
- if isinstance(value, str) and cls._is_js_code_str(value):
|
|
|
- cur[f':{key}'] = cls._replace_js_code_fix(value)
|
|
|
- del cur[key]
|
|
|
- else:
|
|
|
- stack.append(value)
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def _is_js_code_str(cls, text: str) -> bool:
|
|
|
- return text[:len(cls.JS_CODE_PREFIX)] == cls.JS_CODE_PREFIX \
|
|
|
- and text[-len(cls.JS_CODE_PREFIX):] == cls.JS_CODE_PREFIX
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def _replace_js_code_fix(cls, text: str) -> str:
|
|
|
- return text[len(cls.JS_CODE_PREFIX):-len(cls.JS_CODE_PREFIX)]
|