echart.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from typing import Any, Callable, Dict, Optional
  2. from typing_extensions import Self
  3. from .. import optional_features
  4. from ..awaitable_response import AwaitableResponse
  5. from ..element import Element
  6. from ..events import EChartPointClickEventArguments, GenericEventArguments, handle_event
  7. try:
  8. from pyecharts.charts.base import default, json
  9. from pyecharts.charts.chart import Base as Chart
  10. from pyecharts.commons.utils import JsCode
  11. JS_CODE_MARKER = JsCode('\n').js_code.split('\n')[0]
  12. optional_features.register('pyecharts')
  13. except ImportError:
  14. pass
  15. class EChart(Element, component='echart.js', libraries=['lib/echarts/echarts.min.js']):
  16. def __init__(self, options: Dict, on_point_click: Optional[Callable] = None) -> None:
  17. """Apache EChart
  18. An element to create a chart using `ECharts <https://echarts.apache.org/>`_.
  19. Updates can be pushed to the chart by changing the `options` property.
  20. After data has changed, call the `update` method to refresh the chart.
  21. :param options: dictionary of EChart options
  22. :param on_click_point: callback that is invoked when a point is clicked
  23. """
  24. super().__init__()
  25. self._props['options'] = options
  26. self._classes.append('nicegui-echart')
  27. if on_point_click:
  28. self.on_point_click(on_point_click)
  29. def on_point_click(self, callback: Callable[..., Any]) -> Self:
  30. """Add a callback to be invoked when a point is clicked."""
  31. def handle_point_click(e: GenericEventArguments) -> None:
  32. handle_event(callback, EChartPointClickEventArguments(
  33. sender=self,
  34. client=self.client,
  35. component_type=e.args['componentType'],
  36. series_type=e.args['seriesType'],
  37. series_index=e.args['seriesIndex'],
  38. series_name=e.args['seriesName'],
  39. name=e.args['name'],
  40. data_index=e.args['dataIndex'],
  41. data=e.args['data'],
  42. data_type=e.args.get('dataType'),
  43. value=e.args['value'],
  44. ))
  45. self.on('pointClick', handle_point_click, [
  46. 'componentType',
  47. 'seriesType',
  48. 'seriesIndex',
  49. 'seriesName',
  50. 'name',
  51. 'dataIndex',
  52. 'data',
  53. 'dataType',
  54. 'value',
  55. ])
  56. return self
  57. @classmethod
  58. def from_pyecharts(cls, chart: 'Chart', on_point_click: Optional[Callable] = None) -> Self:
  59. """Create an echart element from a pyecharts object.
  60. :param chart: pyecharts chart object
  61. :param on_click_point: callback which is invoked when a point is clicked
  62. :return: echart element
  63. """
  64. options = json.loads(json.dumps(chart.get_options(), default=default, ignore_nan=True))
  65. stack = [options]
  66. while stack:
  67. current = stack.pop()
  68. if isinstance(current, list):
  69. stack.extend(current)
  70. elif isinstance(current, dict):
  71. for key, value in tuple(current.items()):
  72. if isinstance(value, str) and value.startswith(JS_CODE_MARKER) and value.endswith(JS_CODE_MARKER):
  73. current[f':{key}'] = current.pop(key)[len(JS_CODE_MARKER):-len(JS_CODE_MARKER)]
  74. else:
  75. stack.append(value)
  76. return cls(options, on_point_click)
  77. @property
  78. def options(self) -> Dict:
  79. """The options dictionary."""
  80. return self._props['options']
  81. def update(self) -> None:
  82. super().update()
  83. self.run_method('update_chart')
  84. def run_chart_method(self, name: str, *args, timeout: float = 1,
  85. check_interval: float = 0.01) -> AwaitableResponse:
  86. """Run a method of the JSONEditor instance.
  87. See the `ECharts documentation <https://echarts.apache.org/en/api.html#echartsInstance>`_ for a list of methods.
  88. If the function is awaited, the result of the method call is returned.
  89. Otherwise, the method is executed without waiting for a response.
  90. :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
  91. :param args: arguments to pass to the method (Python objects or JavaScript expressions)
  92. :param timeout: timeout in seconds (default: 1 second)
  93. :return: AwaitableResponse that can be awaited to get the result of the method call
  94. """
  95. return self.run_method('run_chart_method', name, *args, timeout=timeout, check_interval=check_interval)