Skip to content

Commit 165375b

Browse files
skaltmancpsievert
andauthored
add plots as inputs section to plotly component (#220)
Co-authored-by: Carson Sievert <[email protected]>
1 parent 8c3e637 commit 165375b

File tree

3 files changed

+160
-1
lines changed

3 files changed

+160
-1
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import plotly.express as px
2+
import plotly.graph_objects as go
3+
from plotly.callbacks import Points
4+
import plotly.express as px
5+
from palmerpenguins import load_penguins
6+
from shiny import App, ui, render, reactive
7+
from shinywidgets import output_widget, render_widget
8+
9+
penguins = load_penguins()
10+
11+
app_ui = ui.page_fluid(
12+
output_widget("plot"),
13+
"Click info",
14+
ui.output_text_verbatim("click_info", placeholder=True),
15+
"Hover info",
16+
ui.output_text_verbatim("hover_info", placeholder=True),
17+
"Selection info (use box or lasso select)",
18+
ui.output_text_verbatim("selection_info", placeholder=True)
19+
)
20+
21+
22+
def server(input, output, session):
23+
24+
click_reactive = reactive.value() # <<
25+
hover_reactive = reactive.value() # <<
26+
selection_reactive = reactive.value() # <<
27+
28+
@render_widget # <<
29+
def plot(): # <<
30+
fig = px.scatter(
31+
data_frame=penguins, x="body_mass_g", y="bill_length_mm"
32+
).update_layout(
33+
yaxis_title="Bill Length (mm)",
34+
xaxis_title="Body Mass (g)",
35+
)
36+
w = go.FigureWidget(fig.data, fig.layout) # <<
37+
w.data[0].on_click(on_point_click) # <<
38+
w.data[0].on_hover(on_point_hover) # <<
39+
w.data[0].on_selection(on_point_selection) # <<
40+
return w # <<
41+
42+
43+
def on_point_click(trace, points, state): # <<
44+
click_reactive.set(points) # <<
45+
46+
def on_point_hover(trace, points, state): # <<
47+
hover_reactive.set(points) # <<
48+
49+
def on_point_selection(trace, points, state): # <<
50+
selection_reactive.set(points) # <<
51+
52+
@render.text
53+
def click_info():
54+
return click_reactive.get()
55+
56+
@render.text
57+
def hover_info():
58+
return hover_reactive.get()
59+
60+
@render.text
61+
def selection_info():
62+
return selection_reactive.get()
63+
64+
65+
app = App(app_ui, server)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import plotly.express as px
2+
import plotly.graph_objects as go
3+
from plotly.callbacks import Points
4+
from shinywidgets import render_plotly
5+
from palmerpenguins import load_penguins
6+
from shiny import render, reactive
7+
from shiny.express import input, ui
8+
from shiny.ui import output_code, output_plot
9+
10+
penguins = load_penguins()
11+
12+
13+
@render_plotly # <<
14+
def plot(): # <<
15+
fig = px.scatter(
16+
data_frame=penguins, x="body_mass_g", y="bill_length_mm"
17+
).update_layout(
18+
yaxis_title="Bill Length (mm)",
19+
xaxis_title="Body Mass (g)",
20+
)
21+
# Need to create a FigureWidget() for on_click to work
22+
w = go.FigureWidget(fig.data, fig.layout) # <<
23+
w.data[0].on_click(on_point_click) # <<
24+
w.data[0].on_hover(on_point_hover) # <<
25+
w.data[0].on_selection(on_point_selection) # <<
26+
return w # <<
27+
28+
29+
# Capture the clicked point in a reactive value
30+
click_reactive = reactive.value() # <<
31+
hover_reactive = reactive.value() # <<
32+
selection_reactive = reactive.value() # <<
33+
34+
def on_point_click(trace, points, state): # <<
35+
click_reactive.set(points) # <<
36+
37+
def on_point_hover(trace, points, state): # <<
38+
hover_reactive.set(points) # <<
39+
40+
def on_point_selection(trace, points, state): # <<
41+
selection_reactive.set(points) # <<
42+
43+
44+
"Click info"
45+
@render.code
46+
def click_info():
47+
return str(click_reactive.get())
48+
49+
"Hover info"
50+
@render.code
51+
def hover_info():
52+
return str(hover_reactive.get())
53+
54+
"Selection info (use box or lasso select)"
55+
@render.code
56+
def selection_info():
57+
return str(selection_reactive.get())

components/outputs/plot-plotly/index.qmd

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ listing:
2929
- title: '@shinywidgets.render_widget()'
3030
href: https://github.com/posit-dev/py-shinywidgets/blob/main/shinywidgets/_shinywidgets.py#L213
3131
signature: shinywidgets.render_widget(fn)
32+
- id: variations
33+
template: ../../_partials/components-variations.ejs
34+
template-params:
35+
dir: components/outputs/plot-plotly/
36+
contents:
37+
- title: Plot as input
38+
description: First, convert your Plotly figure to a `FigureWidget` using `plotly.graph_objects.FigureWidget()`. Then, you can use `.on_click()`, `.on_hover()`, `.on_selection()`, and other methods to control what happens when the user clicks, hover, or selects points. Capture the click, hover, and selection information as reactive variables. The app below displays the values returned, but you can also call the values from within your computations to filter tables, perform calculations, and so on.
39+
apps:
40+
- title: Preview
41+
file: app-variation-plot-as-input-core.py
42+
height: 720
43+
- title: Express
44+
file: app-variation-plot-as-input-express.py
45+
shinylive: https://shinylive.io/py/app/#code=NobwRAdghgtgpmAXGKAHVA6VBPMAaMAYwHsIAXOcpMASxlWICcyACVAG2LPewzgA9UjOAGcRLKONT8AOhDoNmbTt14BzRmgAWAfWIAjAFZxCZcZJZricgGaNiMZVx4ZCUdu31RCAa3EKmVgAFYhpyEVt7RxEtMOwAdxoAEzU4MxYApWEIJLhGHQ5nbEiHNnd4RlRKNQBXMP96QJZOKCSC6rqICIg7Upi4jMasylzGPBZhbzIaADc4EujYiF4BIVEGxVYw1BqycbqFln7ljDrBzZZiXZ2yHRJc8auyG4KVOTkqiFr6lgBeZuIrXaX06IgAFABKd4QOQAAWyo1eRRYLDkuRsTjIkMQKLkKJRNhoaj+bH4GBEbjIFEYYLx+PpSSgZCgOjssDgv0+3y6434vxkYH0xCS2B0MEkIh0agF42w-MFNA8OnY1TIuhgMAFdJRENOqEZFGVUGwT1pEHp9OwUH4NEl024HIFACFFewWAAZVVaFhgjVQ-Dai38a22nT2lXyp3C7AsACyEp9an9eEDUPN+MD8RJVgwADEiTVhAB1ZKpLGEtQYA1QcYVjDsY1PNP0+JVplQYAABgAuhhSHd2DRfGD+wwwrdCIPfM38a3q13e-2tMQ5jTR6FyDpl6uZyi5+2F32IDoRHAVaYaKQR8ex5vT+fpqRdxM0oXzfFoXJJ0OfDpJhe5hJf9pjmDAZncGo4EhORtzyP84CmWY4CAhCALgMCIKgtN7xMR9j2ApCUMQ0DwPYSDoJhHI4AxddxwHH8wTITRCDgcZbzMcYRGZCgIUQQNv18eDiPQ08sXYkQ0zRajLhvDdblgmkmO8Vi2DkkROO4uBeMDBShLQ8k0jBcTJKomjZLonCLyvJSWLYtSNKZLS+PTFFLLwvSQJEwzjM-GEwAAYSnHwMh6awwDhBE8lcYV5lMlgBN-MIbGIbFA2EMg3yOJiwQSjykIwMtIRMgUAAkVzyELkq1CB4RGKL7li9EWF0pKUu0lyXwyxhzS4mldII0DCohYqwAAZTPXDL3NVqfRqU8WCFfhLkYZoJWII4JtMf0IrqxhotyKSMTcqadFa1KOvSzLerBY7+wG9ChrTMAAF9uyAA
46+
height: 720
47+
- title: Core
48+
file: app-variation-plot-as-input-core.py
49+
shinylive: https://shinylive.io/py/app/#code=NobwRAdghgtgpmAXGKAHVA6VBPMAaMAYwHsIAXOcpMASxlWICcyACVAG2LPewzgA9UjOAGcRLKONT8AOhDoNmbTt14BzRmgAWAfWIAjAFZxCZcZJZricgGaNiMZVx4ZCUdu31RCAa3EKmVgAFYhpyETkApQ5nXgEhUXMpWQg7BzZ3eEZUSjUAVzD-ekCWTigAEx0ciHzC23tHES0w7BYo1gBBdDwWAp7hCHK4Rn64bzIaADc4evSmloB3GnK1ODM24qViPLJUHZ0llbXRweGD5dXWFjk5atqIcQBeUuIKqtyCh4AKAEob6HQOgKLGeBSwUFWOhs7AK5S+chYiJY212+0Ol3hYBiZBkYB+PWuECR1zAAGF2DRfG1UtZ8AikWCUXsyDoKPwWdNGF4JjBMYQKb4dGEbLSehxvHAtMR2ENGI8ACqMPJwfH0xG4gASxE51JFuLwat6NAwTP2bI5w25dExUs5Qpp+uUEqlMuGCqVKoJhOJuIAynB2CYJqRdcQWF88iI4Cx9MR+MjGKVJCIw1HA6Y-nSiQzjaaWeadJyrbzcWmgzRSPa9fgnYRJdLZe7lX8IC3-kMbCwo4xOV8wsyenmelGxBXW4h-sT+ZSfDphOMptHnvPTIuMJN3MrfobbWcVxNpiCWPu1xuYXBt9nEWXV5WT4fl2NV9N15uLy2fVeWAABAay85HFchodk4ZC-Ig6pfsSNg0GoR7SBgIhuGQFCMPCUHEki5RQGQUBQpo8CPHcnwiD0-CPLisblNgOgwMmOhqI62AUWA+g0B4OiBjUZC6DAMC4oaxI-BgeSoNhFCcVA2AouhmFySw2BQPwNAiKyNDcHALEAELsewLAADK5Dx4Z8ZmBoYXJ-BKSpakadpxDUSwACyybhmoZmCUiH7yQsR5WBgABisF5MIADqFxrF8MFqBg4lQD00UYOwUkoj83o+bFOFQMAAAMAC6JoQDo06+F8lYMGELIlT4aWeYiCyZbhuUFZWu5oeVoTkDobW1RZLANXFzWFToN7BhAZVFRVXWjWOvXyceawhUSvmTkiwFwJ2HWVcVAo+F8ZCaHWYqdWYw64RQPwQXVLDVXOT4HnAiGRVNZhtl+IFbV1bX7YdcDHZVpFdudKoTn1bV3QuL5RmBL0iG9xIfZNJ0jQG5akD9Er-eEZ04SD10zXe91rtDXyw-DSK-pQsoYOa62drdwrEOB13CGQS03btEPPo9GLk4ilOnIwNMCDi70bSw4OM8zfWs+z4P3jzkV8z+f7DML7J012qO3kVUuXSzi2MESBNFQrGC8-8choKgR5dKgXzW0CNDDsMnI-GAAC+eVAA
50+
height: 720
3251
---
3352

3453
:::{#example}
@@ -43,7 +62,7 @@ Plotly is an interactive graphics plotting library.
4362

4463
To make an interactive plot with Plotly in Shiny for Python, we will need to use the `shinywidgets` library to connect Shiny with `ipywidgets`.
4564

46-
To make a plotly figure, we need to do the following steps:
65+
To make a Plotly figure, we need to do the following steps:
4766

4867
1. Import the `output_widget()` and `render_widget()` functions from the `shinywidgets` library,
4968
`from shinywidgets import output_widget, render_widget`
@@ -62,3 +81,21 @@ To make a plotly figure, we need to do the following steps:
6281
- If you use the `@output()` decorator, make sure it is __above__ the `@render_widget()` decorator.
6382

6483
Visit [shiny.posit.co/py/docs/ipywidgets.html](https://shiny.posit.co/py/docs/ipywidgets.html) to learn more about using ipywidgets with Shiny.
84+
85+
86+
### Plots as Inputs
87+
88+
You can use a Plotly figure as an input widget, collecting the locations of user clicks, hovers, and selections.
89+
90+
1. Convert your Plotly figure to a `FigureWidget` using `plotly.graph_objects.FigureWidget()`, which extends the functionality of a standard Plotly figure and enables event handling.
91+
92+
2. Use the `.data` attribute of the `FigureWidget` to access its traces. The data attribute is a list that contains all the traces in the figure. Individual traces are accessible as `.data[0]`, `.data[1]`, etc., depending on how many traces are present in the figure.
93+
94+
3. Use event handlers to listen for user interactions with the plot. These handlers include methods like `.on_click()`, `.on_hover()`, and `.on_selection()`, which are available for individual traces within the figure. You attach these handlers to a specific trace (e.g., `.data[0].on_click()`) to capture interactions with the data points in that trace.
95+
96+
4. When you use an event handler like `.on_click()`, you need to pass it a callback function that defines what should happen when the event occurs. When defining the callback function, it should receive the parameters `trace`, `points`, and `state`, which provide information about the data points interacted with. In our example app below, our callback function updates a reactive value to contain the information about the points clicked, hovered over, or selected.
97+
98+
## Variations
99+
100+
:::{#variations}
101+
:::

0 commit comments

Comments
 (0)