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

Broken click_data event on plotly data point since panel 1.x.x #5096

Closed
afrimberger opened this issue Jun 12, 2023 · 6 comments · Fixed by #6753
Closed

Broken click_data event on plotly data point since panel 1.x.x #5096

afrimberger opened this issue Jun 12, 2023 · 6 comments · Fixed by #6753
Labels
type: bug Something isn't correct or isn't working
Milestone

Comments

@afrimberger
Copy link

ALL software version info

Chrome 113
Python 3.11.4

panel==1.1.0
bokeh==3.1.1
plotly==5.14.1 (same issue with plotly==5.15.0)

Description of expected behavior and the observed behavior

Observed behavior

image

Clicking on one of the data points either doesn't trigger the Python click_data callback handler or it triggers the callback handler with wrong customdata (e.g. url).

For example, I clicked on every data point, but the Python callback was only fired for the first one:

image

I'm experiencing this issue since panel 1.x.x

Expected behavior

Clicking on a data point fires the Python click_data event handler on every click with the associated customdata.

Complete, minimal, self-contained example code that reproduces the issue

import pandas as pd
import panel as pn

import plotly.express as px

pn.extension("plotly")
pn.extension(sizing_mode="stretch_width")

bootstrap = pn.template.BootstrapTemplate()


def job_panel():
    data = {
        "job_name": {
            "1507302": "job1",
            "1507303": "job1",
            "1508602": "job1",
            "1443920": "job1",
            "1442532": "job1",
            "1429689": "job1",
            "1439023": "job1",
            "1431578": "job1",
            "1426812": "job1",
            "1501430": "job1",
            "1426410": "job1",
            "1425319": "job1",
            "1509387": "job1",
            "1431356": "job1",
            "1426386": "job1",
        },
        "duration": {
            "1507302": 120.0424790333,
            "1507303": 100.0424790333,
            "1508602": 76.2438600667,
            "1443920": 11.3378994167,
            "1442532": 7.7382620833,
            "1429689": 5.7965679,
            "1439023": 4.68264615,
            "1431578": 3.7771960167,
            "1426812": 3.7264409333,
            "1501430": 3.0562511,
            "1426410": 2.7512232,
            "1425319": 2.71998275,
            "1509387": 2.71287155,
            "1431356": 2.6406619333,
            "1426386": 0.1588929833,
        },
        "job_web_url": {
            "1507302": "https://example.com/1507302",
            "1507303": "https://example.com/1507303",
            "1508602": "https://example.com/1508602",
            "1443920": "https://example.com/1443920",
            "1442532": "https://example.com/1442532",
            "1429689": "https://example.com/1429689",
            "1439023": "https://example.com/1439023",
            "1431578": "https://example.com/1431578",
            "1426812": "https://example.com/1426812",
            "1501430": "https://example.com/1501430",
            "1426410": "https://example.com/1426410",
            "1425319": "https://example.com/1425319",
            "1509387": "https://example.com/1509387",
            "1431356": "https://example.com/1431356",
            "1426386": "https://example.com/1426386",
        },
    }

    df = pd.DataFrame(data)
    fig = px.box(
        df,
        x="job_name",
        y="duration",
        title="job median runtime",
        boxmode="overlay",
        hover_data="job_web_url",
    )
    fig.update_traces(
        quartilemethod="linear",
        boxpoints="outliers",
        marker_color="cornflowerblue",
        line_color="cornflowerblue",
    )
    fig.update_xaxes(title_text="job name")
    fig.update_yaxes(title_text="duration (min)")

    fig.layout.autosize = True
    fig.layout.height = 600

    plotly_panel = pn.pane.Plotly(
        fig,
        config={"responsive": True},
        sizing_mode="stretch_width",
    )

    def handle_event(click_data):
        point = click_data.new["points"][0]
        print(point["x"], point["y"], point["customdata"][0])

    plotly_panel.param.watch(handle_event, ["click_data"])

    return plotly_panel


bootstrap.main.append(job_panel)

bootstrap.servable()

Run the code with panel serve dashboard.py. While clicking on one of the data points, the job name, duration and job url should be printed on the console
image

Stack traceback and/or browser JavaScript console output

No error message in the Browser's console log or in the server's output

@philippjfr philippjfr added the type: bug Something isn't correct or isn't working label Jun 13, 2023
@philippjfr philippjfr added this to the v1.1.1 milestone Jun 13, 2023
@philippjfr
Copy link
Member

If this worked in the past this seems to be a change in Plotly itself. When I inspect the click events it's always including all the points:

Screen Shot 2023-06-15 at 11 21 36

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Jun 19, 2023

I'm trying to help a user on Discord in https://discourse.holoviz.org/t/updating-a-selectbox-dependent-plotly-scatterplot-based-on-click-events/5542. I'm on Panel 1.1.0 and Plotly plotly-5.15.0. I'm also hit by this.

In the below example I would expect the update function to be triggered when I click the bars in the plotly plot. But nothing happens and I see no error message anywhere.

import panel as pn
import param
import plotly.express as px
import pandas as pd

# 1. Configure your application
# Remember to list your "dependencies" like plotly here!
pn.extension("plotly", sizing_mode="stretch_width")


# 2. Optionally create a Parameterized class to hold (some of) your application state
class AppState(param.Parameterized):
    click_data = param.Parameter()

state = AppState()

# 2. Extract your data. Add caching if needed
@pn.cache
def get_data():
    return pd.DataFrame({
    'Stat1': [5, 8, 3, 6],
    'Stat2': [7, 10, 4, 8],
    'Stat3': [9, 12, 6, 10],
    'Stat4': [4, 6, 2, 5]
})

data = get_data()

# 3. Transform your data. Add caching if needed
@pn.cache
def get_plot(graph_option1, graph_option2, click_data=None, data=data):
    fig = px.bar(data, x=graph_option1, y=graph_option2, title=f'{graph_option1}, {graph_option2}, {click_data}')
    fig.layout.autosize=True
    return fig

# 4. Define your widgets and optionally some panes and layouts
graph_options = list(data)
graph_option1 = pn.widgets.Select(name="x-axis Stat:", 
                options=graph_options, value="Stat1")
graph_option2 = pn.widgets.Select(name="y-axis Stat:", 
                options=graph_options, value="Stat2")

# 5. Bind your functions to widgets
# Panel will then know it needs to rerun the function when the value of a widget changes
bound_plot = pn.bind(get_plot, graph_option1=graph_option1, graph_option2=graph_option2, click_data=state.param.click_data)


# Optionally provide your bound functions to Panes or other widgets
# In Panel 1.x you can provide *bound* functions as arguments to Panel panes, widgets and layouts
plot_pane = pn.pane.Plotly(bound_plot)

def update(click_data):
    state.param.update(click_data=click_data)
    print("update", click_data)

pn.bind(update, click_data=plot_pane.param.click_data, watch=True)

# 6. Layout your components

# 7. Wrap it up in a nice template
pn.template.FastListTemplate(
    title="Reacting to Plotly events",
    sidebar=[graph_option1, graph_option2],
    main=[plot_pane]
).servable()

Update

If I click the 2nd bar from the left first a click_data event is actually triggered and catched. Any other way of clicking does not raise a click_data event.

image

@MarcSkovMadsen
Copy link
Collaborator

The Controls example at https://panel.holoviz.org/reference/panes/Plotly.html is also broken. I cannot get it to show any click_data.

@MarcSkovMadsen
Copy link
Collaborator

I tried downgrading to Panel 0.14.4. But keeping Plotly 5.15.

The click_data in the example below is not working either

import panel as pn
import param
import plotly.express as px
import pandas as pd

import mimetypes
mimetypes.add_type("application/javascript", ".js")

# 1. Configure your application
# Remember to list your "dependencies" like plotly here!
pn.extension("plotly", sizing_mode="stretch_width")


# 2. Optionally create a Parameterized class to hold (some of) your application state
class AppState(param.Parameterized):
    click_data = param.Parameter()

state = AppState()

# 2. Extract your data. Add caching if needed
@pn.cache
def get_data():
    return pd.DataFrame({
    'Stat1': [5, 8, 3, 6],
    'Stat2': [7, 10, 4, 8],
    'Stat3': [9, 12, 6, 10],
    'Stat4': [4, 6, 2, 5]
})

data = get_data()

# 3. Transform your data. Add caching if needed
@pn.cache
def get_plot(graph_option1, graph_option2, click_data=None, data=data):
    fig = px.bar(data, x=graph_option1, y=graph_option2, title=f'{graph_option1}, {graph_option2}, {click_data}')
    fig.layout.autosize=True
    return fig

# 4. Define your widgets and optionally some panes and layouts
graph_options = list(data)
graph_option1 = pn.widgets.Select(name="x-axis Stat:", 
                options=graph_options, value="Stat1")
graph_option2 = pn.widgets.Select(name="y-axis Stat:", 
                options=graph_options, value="Stat2")
plot_pane = pn.pane.Plotly()

# 5. Bind your functions to widgets
# Panel will then know it needs to rerun the function when the value of a widget changes

def get_plot_helper(graph_option1=graph_option1, graph_option2=graph_option2, click_data=state.param.click_data, data=data):
    print("update")
    plot_pane.object=get_plot(graph_option1=graph_option1, graph_option2=graph_option2, click_data=click_data, data=data)
    
    
bound_plot = pn.bind(get_plot_helper, graph_option1=graph_option1, graph_option2=graph_option2, click_data=state.param.click_data, watch=True)
bound_plot()
# 6. Layout your components

# 7. Wrap it up in a nice template
pn.template.FastListTemplate(
    title="Reacting to Plotly events",
    sidebar=[graph_option1, graph_option2],
    main=[plot_pane]
).servable()

@philippjfr philippjfr modified the milestones: v1.1.1, v1.2.0 Jun 22, 2023
@philippjfr philippjfr modified the milestones: v1.2.0, v1.2.1 Jul 10, 2023
@philippjfr philippjfr modified the milestones: v1.2.1, v1.2.2 Jul 25, 2023
@hoxbro hoxbro modified the milestones: v1.2.2, v1.2.3 Sep 4, 2023
@philippjfr philippjfr modified the milestones: v1.2.3, v1.2.4 Sep 18, 2023
@philippjfr philippjfr modified the milestones: v1.2.4, v1.3.0 Oct 9, 2023
@philippjfr philippjfr modified the milestones: v1.3.0, v1.3.1 Oct 23, 2023
@philippjfr philippjfr modified the milestones: v1.3.1, v1.3.2 Oct 31, 2023
@rRieser
Copy link

rRieser commented Nov 8, 2023

I noticed that the click_data event is only triggered when the dragmode is set to select.

panel: 1.3.1
plotly: 5.18.0

import pandas as pd
import panel as pn
import plotly.express as px

pn.extension('plotly')

df = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [2, 1, 4, 1, 3]
})

fig = px.scatter(df, 'x', 'y', title='Plotly-Scatter-Plot')
fig.update_layout(dragmode='select')

plot_panel = pn.pane.Plotly(fig)

def print_click_data(event):
    print("click_data", event.new)
plot_panel.param.watch(print_click_data, ['click_data'])

plot_panel.servable()

The example on https://panel.holoviz.org/reference/panes/Plotly.html also only works if you click on the 'Box Select' button on the plot.

@philippjfr philippjfr modified the milestones: v1.3.2, v1.4.0 Nov 22, 2023
@philippjfr philippjfr modified the milestones: v1.4.0, v1.4.x Mar 13, 2024
@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Apr 18, 2024

This is still a problem with Panel==1.4.1.

I would have like to add an example of click_data usage to the reference guide but as its not working I cannot.

import pandas as pd
import panel as pn
import plotly.express as px

pn.extension("plotly")

data = pd.DataFrame([
    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),
    ('Friday', 4), ('Saturday', 4), ('Sunday', 4)], columns=['Day', 'Orders']
)

fig = px.line(data, x="Day", y="Orders")
fig.update_traces(mode="lines+markers", marker=dict(size=10), line=dict(width=4))
fig.layout.autosize = True

responsive = pn.pane.Plotly(fig, height=300)

pn.Row(responsive.controls(), responsive).servable()

Notice that the click_data does not work initially. But it does after I lasso select something.

click_data_not_working.mp4

UPDATE: As soon as you select the Box or Lasso selection tool, click_data starts working. When you try the official plotly.js event examples this is not nescessary https://plotly.com/javascript/plotlyjs-events/.

@philippjfr philippjfr removed this from the v1.4.x milestone Apr 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug Something isn't correct or isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants