[REF-2202] Implement event handlers for Plotly (#3397)
* pyi_generator: do not generate kwargs for event trigger props event triggers are handled separately * Implement event handlers for Plotly * py38 compat: from __future__ import annotations
This commit is contained in:
parent
d9e718d7bd
commit
16fc3936a4
@ -1,8 +1,11 @@
|
||||
"""Component for displaying a plotly graph."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventHandler
|
||||
from reflex.vars import Var
|
||||
|
||||
try:
|
||||
@ -11,6 +14,76 @@ except ImportError:
|
||||
Figure = Any # type: ignore
|
||||
|
||||
|
||||
def _event_data_signature(e0: Var) -> List[Any]:
|
||||
"""For plotly events with event data and no points.
|
||||
|
||||
Args:
|
||||
e0: The event data.
|
||||
|
||||
Returns:
|
||||
The event key extracted from the event data (if defined).
|
||||
"""
|
||||
return [Var.create_safe(f"{e0}?.event")]
|
||||
|
||||
|
||||
def _event_points_data_signature(e0: Var) -> List[Any]:
|
||||
"""For plotly events with event data containing a point array.
|
||||
|
||||
Args:
|
||||
e0: The event data.
|
||||
|
||||
Returns:
|
||||
The event data and the extracted points.
|
||||
"""
|
||||
return [
|
||||
Var.create_safe(f"{e0}?.event"),
|
||||
Var.create_safe(
|
||||
f"extractPoints({e0}?.points)",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class _ButtonClickData(Base):
|
||||
"""Event data structure for plotly UI buttons."""
|
||||
|
||||
menu: Any
|
||||
button: Any
|
||||
active: Any
|
||||
|
||||
|
||||
def _button_click_signature(e0: _ButtonClickData) -> List[Any]:
|
||||
"""For plotly button click events.
|
||||
|
||||
Args:
|
||||
e0: The button click data.
|
||||
|
||||
Returns:
|
||||
The menu, button, and active state.
|
||||
"""
|
||||
return [e0.menu, e0.button, e0.active]
|
||||
|
||||
|
||||
def _passthrough_signature(e0: Var) -> List[Any]:
|
||||
"""For plotly events with arbitrary serializable data, passed through directly.
|
||||
|
||||
Args:
|
||||
e0: The event data.
|
||||
|
||||
Returns:
|
||||
The event data.
|
||||
"""
|
||||
return [e0]
|
||||
|
||||
|
||||
def _null_signature() -> List[Any]:
|
||||
"""For plotly events with no data or non-serializable data. Nothing passed through.
|
||||
|
||||
Returns:
|
||||
An empty list (nothing passed through).
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class PlotlyLib(NoSSRComponent):
|
||||
"""A component that wraps a plotly lib."""
|
||||
|
||||
@ -38,6 +111,105 @@ class Plotly(PlotlyLib):
|
||||
# If true, the graph will resize when the window is resized.
|
||||
use_resize_handler: Var[bool]
|
||||
|
||||
# Fired after the plot is redrawn.
|
||||
on_after_plot: EventHandler[_passthrough_signature]
|
||||
|
||||
# Fired after the plot was animated.
|
||||
on_animated: EventHandler[_null_signature]
|
||||
|
||||
# Fired while animating a single frame (does not currently pass data through).
|
||||
on_animating_frame: EventHandler[_null_signature]
|
||||
|
||||
# Fired when an animation is interrupted (to start a new animation for example).
|
||||
on_animation_interrupted: EventHandler[_null_signature]
|
||||
|
||||
# Fired when the plot is responsively sized.
|
||||
on_autosize: EventHandler[_event_data_signature]
|
||||
|
||||
# Fired whenever mouse moves over a plot.
|
||||
on_before_hover: EventHandler[_event_data_signature]
|
||||
|
||||
# Fired when a plotly UI button is clicked.
|
||||
on_button_clicked: EventHandler[_button_click_signature]
|
||||
|
||||
# Fired when the plot is clicked.
|
||||
on_click: EventHandler[_event_points_data_signature]
|
||||
|
||||
# Fired when a selection is cleared (via double click).
|
||||
on_deselect: EventHandler[_null_signature]
|
||||
|
||||
# Fired when the plot is double clicked.
|
||||
on_double_click: EventHandler[_passthrough_signature]
|
||||
|
||||
# Fired when a plot element is hovered over.
|
||||
on_hover: EventHandler[_event_points_data_signature]
|
||||
|
||||
# Fired after the plot is layed out (zoom, pan, etc).
|
||||
on_relayout: EventHandler[_passthrough_signature]
|
||||
|
||||
# Fired while the plot is being layed out.
|
||||
on_relayouting: EventHandler[_passthrough_signature]
|
||||
|
||||
# Fired after the plot style is changed.
|
||||
on_restyle: EventHandler[_passthrough_signature]
|
||||
|
||||
# Fired after the plot is redrawn.
|
||||
on_redraw: EventHandler[_event_data_signature]
|
||||
|
||||
# Fired after selecting plot elements.
|
||||
on_selected: EventHandler[_event_points_data_signature]
|
||||
|
||||
# Fired while dragging a selection.
|
||||
on_selecting: EventHandler[_event_points_data_signature]
|
||||
|
||||
# Fired while an animation is occuring.
|
||||
on_transitioning: EventHandler[_event_data_signature]
|
||||
|
||||
# Fired when a transition is stopped early.
|
||||
on_transition_interrupted: EventHandler[_event_data_signature]
|
||||
|
||||
# Fired when a hovered element is no longer hovered.
|
||||
on_unhover: EventHandler[_event_points_data_signature]
|
||||
|
||||
def add_custom_code(self) -> list[str]:
|
||||
"""Add custom codes for processing the plotly points data.
|
||||
|
||||
Returns:
|
||||
Custom code snippets for the module level.
|
||||
"""
|
||||
return [
|
||||
"const removeUndefined = (obj) => {Object.keys(obj).forEach(key => obj[key] === undefined && delete obj[key]); return obj}",
|
||||
"""
|
||||
const extractPoints = (points) => {
|
||||
if (!points) return [];
|
||||
return points.map(point => {
|
||||
const bbox = point.bbox ? removeUndefined({
|
||||
x0: point.bbox.x0,
|
||||
x1: point.bbox.x1,
|
||||
y0: point.bbox.y0,
|
||||
y1: point.bbox.y1,
|
||||
z0: point.bbox.y0,
|
||||
z1: point.bbox.y1,
|
||||
}) : undefined;
|
||||
return removeUndefined({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
z: point.z,
|
||||
lat: point.lat,
|
||||
lon: point.lon,
|
||||
curveNumber: point.curveNumber,
|
||||
pointNumber: point.pointNumber,
|
||||
pointNumbers: point.pointNumbers,
|
||||
pointIndex: point.pointIndex,
|
||||
'marker.color': point['marker.color'],
|
||||
'marker.size': point['marker.size'],
|
||||
bbox: bbox,
|
||||
})
|
||||
})
|
||||
}
|
||||
""",
|
||||
]
|
||||
|
||||
def _render(self):
|
||||
tag = super()._render()
|
||||
figure = self.data.to(dict)
|
||||
|
@ -8,7 +8,9 @@ from reflex.vars import Var, BaseVar, ComputedVar
|
||||
from reflex.event import EventChain, EventHandler, EventSpec
|
||||
from reflex.style import Style
|
||||
from typing import Any, Dict, List
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventHandler
|
||||
from reflex.vars import Var
|
||||
|
||||
try:
|
||||
@ -16,6 +18,11 @@ try:
|
||||
except ImportError:
|
||||
Figure = Any # type: ignore
|
||||
|
||||
class _ButtonClickData(Base):
|
||||
menu: Any
|
||||
button: Any
|
||||
active: Any
|
||||
|
||||
class PlotlyLib(NoSSRComponent):
|
||||
@overload
|
||||
@classmethod
|
||||
@ -93,6 +100,7 @@ class PlotlyLib(NoSSRComponent):
|
||||
...
|
||||
|
||||
class Plotly(PlotlyLib):
|
||||
def add_custom_code(self) -> list[str]: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
@ -108,21 +116,48 @@ class Plotly(PlotlyLib):
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_after_plot: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_animated: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_animating_frame: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_animation_interrupted: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_autosize: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_before_hover: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_blur: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_button_clicked: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_context_menu: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_deselect: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_double_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_focus: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_hover: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_mount: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
@ -147,9 +182,36 @@ class Plotly(PlotlyLib):
|
||||
on_mouse_up: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_redraw: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_relayout: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_relayouting: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_restyle: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_scroll: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_selected: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_selecting: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_transition_interrupted: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_transitioning: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_unhover: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
on_unmount: Optional[
|
||||
Union[EventHandler, EventSpec, list, function, BaseVar]
|
||||
] = None,
|
||||
|
@ -320,6 +320,7 @@ def _extract_class_props_as_ast_nodes(
|
||||
all_props = []
|
||||
kwargs = []
|
||||
for target_class in clzs:
|
||||
event_triggers = target_class().get_event_triggers()
|
||||
# Import from the target class to ensure type hints are resolvable.
|
||||
exec(f"from {target_class.__module__} import *", type_hint_globals)
|
||||
for name, value in target_class.__annotations__.items():
|
||||
@ -327,6 +328,7 @@ def _extract_class_props_as_ast_nodes(
|
||||
name in spec.kwonlyargs
|
||||
or name in EXCLUDED_PROPS
|
||||
or name in all_props
|
||||
or name in event_triggers
|
||||
or (isinstance(value, str) and "ClassVar" in value)
|
||||
):
|
||||
continue
|
||||
|
Loading…
Reference in New Issue
Block a user