Skip to content

Commit

Permalink
feat: support on_relayout for plotly to allow image annotation (#285)
Browse files Browse the repository at this point in the history
Also includes an example
  • Loading branch information
itepifanio authored Oct 6, 2023
1 parent 250bc34 commit 805da82
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 21 deletions.
40 changes: 21 additions & 19 deletions solara/components/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,30 +272,32 @@ def FigurePlotly(
on_click: Callable[[Any], None] = None,
on_hover: Callable[[Any], None] = None,
on_unhover: Callable[[Any], None] = None,
on_relayout: Callable[[Any], None] = None,
dependencies=None,
):
from plotly.graph_objs._figurewidget import FigureWidget

def on_points_callback(data):
if data:
event_type = data["event_type"]
if event_type == "plotly_click":
if on_click:
on_click(data)
elif event_type == "plotly_hover":
if on_hover:
on_hover(data)
elif event_type == "plotly_unhover":
if on_unhover:
on_unhover(data)
elif event_type == "plotly_selected":
if on_selection:
on_selection(data)
elif event_type == "plotly_deselect":
if on_deselect:
on_deselect(data)

fig_element = FigureWidget.element(on__js2py_pointsCallback=on_points_callback)
if not data:
return

event_type = data["event_type"]
event_mapping = {
"plotly_click": on_click,
"plotly_hover": on_hover,
"plotly_unhover": on_unhover,
"plotly_selected": on_selection,
"plotly_deselect": on_deselect
}

callback = event_mapping.get(event_type)
if callback:
callback(data)

fig_element = FigureWidget.element(
on__js2py_pointsCallback=on_points_callback,
on__js2py_relayout=on_relayout
)

def update_data():
fig_widget: FigureWidget = solara.get_widget(fig_element)
Expand Down
1 change: 0 additions & 1 deletion solara/website/pages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from ..components import Header, Hero


title = "Home"

route_order = ["/", "showcase", "docs", "api", "examples", "apps"]
Expand Down
68 changes: 68 additions & 0 deletions solara/website/pages/examples/visualization/annotator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""# Image annotation with Solara
This example displays how to annotate images with different drawing tools in plotly figures. Use the canvas
below to draw shapes and visualize the canvas callback.
Check [plotly docs](https://dash.plotly.com/annotations) for more information about image annotation.
"""
import json

import plotly.graph_objects as go

import solara

title = "Plotly Image Annotator"
shapes = solara.reactive(None)


class CustomEncoder(json.JSONEncoder):
"""
Custom JSON encoder for Plotly objects.
Plotly may return objects that the standard JSON encoder can't handle. This
encoder converts such objects to str, allowing serialization by json.dumps
"""

def default(self, o):
if isinstance(o, object):
return str(o)
return super().default(o)


@solara.component
def Page():
def on_relayout(data):
if data is None:
return

relayout_data = data["relayout_data"]

if "shapes" in relayout_data:
shapes.value = relayout_data["shapes"]

fig = go.FigureWidget(
layout=go.Layout(
showlegend=False,
autosize=False,
width=600,
height=600,
dragmode="drawrect",
modebar={
"add": [
"drawclosedpath",
"drawcircle",
"drawrect",
"eraseshape",
]
},
)
)

solara.FigurePlotly(fig, on_relayout=on_relayout)
if not shapes.value:
solara.Markdown("## Draw on the canvas")
else:
solara.Markdown("## Data returned by drawing")
formatted_shapes = str(json.dumps(shapes.value, indent=2, cls=CustomEncoder))
solara.Preformatted(formatted_shapes)
5 changes: 4 additions & 1 deletion tests/integration/server_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ def test_docs_basics(page_session: playwright.sync_api.Page, solara_server, sola
page_session.locator("text=Exponent").wait_for()
page_session.screenshot(path="tmp/screenshot_bqplot.png")

page_session.locator("text=Plotly").first.click()
page_session.locator("text=Scatter plot using Plotly").first.click()
page_session.locator("text=plotly express").first.wait_for()
page_session.screenshot(path="tmp/screenshot_plotly.png")

page_session.locator("text=Plotly Image Annotator").first.click()
page_session.locator("text=how to annotate images with").first.wait_for()


@solara.component
def ClickButton():
Expand Down

0 comments on commit 805da82

Please sign in to comment.