Py.Cafe

iisakkirotko/

anywidget-model-change-bug

Anywidget doesn't respond to changes made to the model while view is loading

DocsPricing
  • app.py
  • plotlywidget.css
  • plotlywidget.js
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# FIND THE FULL REPO AT https://github.com/iisakkirotko/plotly-js-bindings

from pathlib import Path

from anywidget import AnyWidget
import traitlets

import numpy as np
import pandas as pd
import solara


class PlotlyWidget(AnyWidget):
    _esm = "plotlywidget.js"
    _css = "plotlywidget.css"
    
    _widget_data = traitlets.List([]).tag(sync=True)
    _widget_layout = traitlets.Dict({}).tag(sync=True)

# Create sample data
np.random.seed(42)
n_points = 15000
df = pd.DataFrame({
    'x': np.random.normal(0, 1, n_points),
    'y': np.random.normal(0, 1, n_points),
    'cell_idx': np.arange(n_points)
})
data = [{"x": df.x.to_list(), "y": df.y.to_list(), "type": "scattergl", "mode": "markers"}]

@solara.component
def Page():
    selected_indices = solara.use_reactive([])

    plot = PlotlyWidget.element(
        _widget_data=data,
        _widget_layout={
            "title": {
                "text": 'Sales Growth'
            },
            "dragmode": 'lasso',
            "xaxis": {
                "title": {
                "text": 'Year'
                },
                "showgrid": False,
                "zeroline": False
            },
            "yaxis": {
                "title": {
                "text": 'Percent'
                },
                "showline": False
            }
        }
    )

    solara.Markdown(f"Selected indices: ```{selected_indices.value}```")

    def handle_messages():
        plot_widget = solara.get_widget(plot)

        def handle_selection(msg_data):
            selected: list[list[int]] = [[] for _ in range(len(data))]
            for i, dataset_index in enumerate(msg_data["trace_indexes"]):
                selected[dataset_index].append(msg_data["point_indexes"][i])
            selected_indices.set(selected)

        def handle_message(widget, msg, buffers):
            if msg["event_type"] == "plotly_selected":
                handle_selection(msg["points"])
            else:
                print("Unknown message", msg)
        
        plot_widget.on_msg(handle_message)

        def cleanup():
            plot_widget.on_msg(handle_message, remove=True)

        return cleanup

    solara.use_effect(handle_messages, [])