Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: change plotly figure widget to allow image annotation #285

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