1
0

plotly.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. """Component for displaying a plotly graph."""
  2. from __future__ import annotations
  3. from typing import Any, Dict, List
  4. from reflex.base import Base
  5. from reflex.components.component import NoSSRComponent
  6. from reflex.event import EventHandler
  7. from reflex.vars import Var
  8. try:
  9. from plotly.graph_objects import Figure
  10. except ImportError:
  11. Figure = Any # type: ignore
  12. def _event_data_signature(e0: Var) -> List[Any]:
  13. """For plotly events with event data and no points.
  14. Args:
  15. e0: The event data.
  16. Returns:
  17. The event key extracted from the event data (if defined).
  18. """
  19. return [Var.create_safe(f"{e0}?.event")]
  20. def _event_points_data_signature(e0: Var) -> List[Any]:
  21. """For plotly events with event data containing a point array.
  22. Args:
  23. e0: The event data.
  24. Returns:
  25. The event data and the extracted points.
  26. """
  27. return [
  28. Var.create_safe(f"{e0}?.event"),
  29. Var.create_safe(
  30. f"extractPoints({e0}?.points)",
  31. ),
  32. ]
  33. class _ButtonClickData(Base):
  34. """Event data structure for plotly UI buttons."""
  35. menu: Any
  36. button: Any
  37. active: Any
  38. def _button_click_signature(e0: _ButtonClickData) -> List[Any]:
  39. """For plotly button click events.
  40. Args:
  41. e0: The button click data.
  42. Returns:
  43. The menu, button, and active state.
  44. """
  45. return [e0.menu, e0.button, e0.active]
  46. def _passthrough_signature(e0: Var) -> List[Any]:
  47. """For plotly events with arbitrary serializable data, passed through directly.
  48. Args:
  49. e0: The event data.
  50. Returns:
  51. The event data.
  52. """
  53. return [e0]
  54. def _null_signature() -> List[Any]:
  55. """For plotly events with no data or non-serializable data. Nothing passed through.
  56. Returns:
  57. An empty list (nothing passed through).
  58. """
  59. return []
  60. class PlotlyLib(NoSSRComponent):
  61. """A component that wraps a plotly lib."""
  62. library = "react-plotly.js@2.6.0"
  63. lib_dependencies: List[str] = ["plotly.js@2.22.0"]
  64. class Plotly(PlotlyLib):
  65. """Display a plotly graph."""
  66. tag = "Plot"
  67. is_default = True
  68. # The figure to display. This can be a plotly figure or a plotly data json.
  69. data: Var[Figure]
  70. # The layout of the graph.
  71. layout: Var[Dict]
  72. # The config of the graph.
  73. config: Var[Dict]
  74. # If true, the graph will resize when the window is resized.
  75. use_resize_handler: Var[bool] = Var.create_safe(True)
  76. # Fired after the plot is redrawn.
  77. on_after_plot: EventHandler[_passthrough_signature]
  78. # Fired after the plot was animated.
  79. on_animated: EventHandler[_null_signature]
  80. # Fired while animating a single frame (does not currently pass data through).
  81. on_animating_frame: EventHandler[_null_signature]
  82. # Fired when an animation is interrupted (to start a new animation for example).
  83. on_animation_interrupted: EventHandler[_null_signature]
  84. # Fired when the plot is responsively sized.
  85. on_autosize: EventHandler[_event_data_signature]
  86. # Fired whenever mouse moves over a plot.
  87. on_before_hover: EventHandler[_event_data_signature]
  88. # Fired when a plotly UI button is clicked.
  89. on_button_clicked: EventHandler[_button_click_signature]
  90. # Fired when the plot is clicked.
  91. on_click: EventHandler[_event_points_data_signature]
  92. # Fired when a selection is cleared (via double click).
  93. on_deselect: EventHandler[_null_signature]
  94. # Fired when the plot is double clicked.
  95. on_double_click: EventHandler[_passthrough_signature]
  96. # Fired when a plot element is hovered over.
  97. on_hover: EventHandler[_event_points_data_signature]
  98. # Fired after the plot is layed out (zoom, pan, etc).
  99. on_relayout: EventHandler[_passthrough_signature]
  100. # Fired while the plot is being layed out.
  101. on_relayouting: EventHandler[_passthrough_signature]
  102. # Fired after the plot style is changed.
  103. on_restyle: EventHandler[_passthrough_signature]
  104. # Fired after the plot is redrawn.
  105. on_redraw: EventHandler[_event_data_signature]
  106. # Fired after selecting plot elements.
  107. on_selected: EventHandler[_event_points_data_signature]
  108. # Fired while dragging a selection.
  109. on_selecting: EventHandler[_event_points_data_signature]
  110. # Fired while an animation is occuring.
  111. on_transitioning: EventHandler[_event_data_signature]
  112. # Fired when a transition is stopped early.
  113. on_transition_interrupted: EventHandler[_event_data_signature]
  114. # Fired when a hovered element is no longer hovered.
  115. on_unhover: EventHandler[_event_points_data_signature]
  116. def add_custom_code(self) -> list[str]:
  117. """Add custom codes for processing the plotly points data.
  118. Returns:
  119. Custom code snippets for the module level.
  120. """
  121. return [
  122. "const removeUndefined = (obj) => {Object.keys(obj).forEach(key => obj[key] === undefined && delete obj[key]); return obj}",
  123. """
  124. const extractPoints = (points) => {
  125. if (!points) return [];
  126. return points.map(point => {
  127. const bbox = point.bbox ? removeUndefined({
  128. x0: point.bbox.x0,
  129. x1: point.bbox.x1,
  130. y0: point.bbox.y0,
  131. y1: point.bbox.y1,
  132. z0: point.bbox.y0,
  133. z1: point.bbox.y1,
  134. }) : undefined;
  135. return removeUndefined({
  136. x: point.x,
  137. y: point.y,
  138. z: point.z,
  139. lat: point.lat,
  140. lon: point.lon,
  141. curveNumber: point.curveNumber,
  142. pointNumber: point.pointNumber,
  143. pointNumbers: point.pointNumbers,
  144. pointIndex: point.pointIndex,
  145. 'marker.color': point['marker.color'],
  146. 'marker.size': point['marker.size'],
  147. bbox: bbox,
  148. })
  149. })
  150. }
  151. """,
  152. ]
  153. def _render(self):
  154. tag = super()._render()
  155. figure = self.data.to(dict)
  156. if self.layout is None:
  157. tag.remove_props("data", "layout")
  158. tag.special_props.add(
  159. Var.create_safe(f"{{...{figure._var_name_unwrapped}}}")
  160. )
  161. else:
  162. tag.add_props(data=figure["data"])
  163. return tag