We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
jupyterviz checkbox input change is not propagated


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mesa/experimental/jupyter_viz.py`
Content:
```
1 import threading
2 
3 import matplotlib.pyplot as plt
4 import networkx as nx
5 import reacton.ipywidgets as widgets
6 import solara
7 from matplotlib.figure import Figure
8 from matplotlib.ticker import MaxNLocator
9 
10 import mesa
11 
12 # Avoid interactive backend
13 plt.switch_backend("agg")
14 
15 
16 @solara.component
17 def JupyterViz(
18     model_class,
19     model_params,
20     measures=None,
21     name="Mesa Model",
22     agent_portrayal=None,
23     space_drawer="default",
24     play_interval=150,
25 ):
26     """Initialize a component to visualize a model.
27     Args:
28         model_class: class of the model to instantiate
29         model_params: parameters for initializing the model
30         measures: list of callables or data attributes to plot
31         name: name for display
32         agent_portrayal: options for rendering agents (dictionary)
33         space_drawer: method to render the agent space for
34             the model; default implementation is :meth:`make_space`;
35             simulations with no space to visualize should
36             specify `space_drawer=False`
37         play_interval: play interval (default: 150)
38     """
39 
40     current_step, set_current_step = solara.use_state(0)
41 
42     # 1. Set up model parameters
43     user_params, fixed_params = split_model_params(model_params)
44     model_parameters, set_model_parameters = solara.use_state(
45         {**fixed_params, **{k: v["value"] for k, v in user_params.items()}}
46     )
47 
48     # 2. Set up Model
49     def make_model():
50         model = model_class(**model_parameters)
51         set_current_step(0)
52         return model
53 
54     reset_counter = solara.use_reactive(0)
55     model = solara.use_memo(
56         make_model, dependencies=[*list(model_parameters.values()), reset_counter.value]
57     )
58 
59     def handle_change_model_params(name: str, value: any):
60         set_model_parameters({**model_parameters, name: value})
61 
62     # 3. Set up UI
63     solara.Markdown(name)
64     UserInputs(user_params, on_change=handle_change_model_params)
65     ModelController(model, play_interval, current_step, set_current_step, reset_counter)
66 
67     with solara.GridFixed(columns=2):
68         # 4. Space
69         if space_drawer == "default":
70             # draw with the default implementation
71             make_space(model, agent_portrayal)
72         elif space_drawer:
73             # if specified, draw agent space with an alternate renderer
74             space_drawer(model, agent_portrayal)
75         # otherwise, do nothing (do not draw space)
76 
77         # 5. Plots
78         for measure in measures:
79             if callable(measure):
80                 # Is a custom object
81                 measure(model)
82             else:
83                 make_plot(model, measure)
84 
85 
86 @solara.component
87 def ModelController(
88     model, play_interval, current_step, set_current_step, reset_counter
89 ):
90     playing = solara.use_reactive(False)
91     thread = solara.use_reactive(None)
92     # We track the previous step to detect if user resets the model via
93     # clicking the reset button or changing the parameters. If previous_step >
94     # current_step, it means a model reset happens while the simulation is
95     # still playing.
96     previous_step = solara.use_reactive(0)
97 
98     def on_value_play(change):
99         if previous_step.value > current_step and current_step == 0:
100             # We add extra checks for current_step == 0, just to be sure.
101             # We automatically stop the playing if a model is reset.
102             playing.value = False
103         elif model.running:
104             do_step()
105         else:
106             playing.value = False
107 
108     def do_step():
109         model.step()
110         previous_step.value = current_step
111         set_current_step(model.schedule.steps)
112 
113     def do_play():
114         model.running = True
115         while model.running:
116             do_step()
117 
118     def threaded_do_play():
119         if thread is not None and thread.is_alive():
120             return
121         thread.value = threading.Thread(target=do_play)
122         thread.start()
123 
124     def do_pause():
125         if (thread is None) or (not thread.is_alive()):
126             return
127         model.running = False
128         thread.join()
129 
130     def do_reset():
131         reset_counter.value += 1
132 
133     with solara.Row():
134         solara.Button(label="Step", color="primary", on_click=do_step)
135         # This style is necessary so that the play widget has almost the same
136         # height as typical Solara buttons.
137         solara.Style(
138             """
139         .widget-play {
140             height: 30px;
141         }
142         """
143         )
144         widgets.Play(
145             value=0,
146             interval=play_interval,
147             repeat=True,
148             show_repeat=False,
149             on_value=on_value_play,
150             playing=playing.value,
151             on_playing=playing.set,
152         )
153         solara.Button(label="Reset", color="primary", on_click=do_reset)
154         solara.Markdown(md_text=f"**Step:** {current_step}")
155         # threaded_do_play is not used for now because it
156         # doesn't work in Google colab. We use
157         # ipywidgets.Play until it is fixed. The threading
158         # version is definite a much better implementation,
159         # if it works.
160         # solara.Button(label="▶", color="primary", on_click=viz.threaded_do_play)
161         # solara.Button(label="⏸︎", color="primary", on_click=viz.do_pause)
162         # solara.Button(label="Reset", color="primary", on_click=do_reset)
163 
164 
165 def split_model_params(model_params):
166     model_params_input = {}
167     model_params_fixed = {}
168     for k, v in model_params.items():
169         if check_param_is_fixed(v):
170             model_params_fixed[k] = v
171         else:
172             model_params_input[k] = v
173     return model_params_input, model_params_fixed
174 
175 
176 def check_param_is_fixed(param):
177     if not isinstance(param, dict):
178         return True
179     if "type" not in param:
180         return True
181 
182 
183 @solara.component
184 def UserInputs(user_params, on_change=None):
185     """Initialize user inputs for configurable model parameters.
186     Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
187     :class:`solara.Select`, and :class:`solara.Checkbox`.
188 
189     Props:
190         user_params: dictionary with options for the input, including label,
191         min and max values, and other fields specific to the input type.
192         on_change: function to be called with (name, value) when the value of an input changes.
193     """
194 
195     for name, options in user_params.items():
196         # label for the input is "label" from options or name
197         label = options.get("label", name)
198         input_type = options.get("type")
199 
200         def change_handler(value, name=name):
201             on_change(name, value)
202 
203         if input_type == "SliderInt":
204             solara.SliderInt(
205                 label,
206                 value=options.get("value"),
207                 on_value=change_handler,
208                 min=options.get("min"),
209                 max=options.get("max"),
210                 step=options.get("step"),
211             )
212         elif input_type == "SliderFloat":
213             solara.SliderFloat(
214                 label,
215                 value=options.get("value"),
216                 on_value=change_handler,
217                 min=options.get("min"),
218                 max=options.get("max"),
219                 step=options.get("step"),
220             )
221         elif input_type == "Select":
222             solara.Select(
223                 label,
224                 value=options.get("value"),
225                 on_value=change_handler,
226                 values=options.get("values"),
227             )
228         elif input_type == "Checkbox":
229             solara.Checkbox(
230                 label=label,
231                 value=options.get("value"),
232             )
233         else:
234             raise ValueError(f"{input_type} is not a supported input type")
235 
236 
237 def make_space(model, agent_portrayal):
238     space_fig = Figure()
239     space_ax = space_fig.subplots()
240     space = getattr(model, "grid", None)
241     if space is None:
242         # Sometimes the space is defined as model.space instead of model.grid
243         space = model.space
244     if isinstance(space, mesa.space.NetworkGrid):
245         _draw_network_grid(space, space_ax, agent_portrayal)
246     elif isinstance(space, mesa.space.ContinuousSpace):
247         _draw_continuous_space(space, space_ax, agent_portrayal)
248     else:
249         _draw_grid(space, space_ax, agent_portrayal)
250     space_ax.set_axis_off()
251     solara.FigureMatplotlib(space_fig, format="png")
252 
253 
254 def _draw_grid(space, space_ax, agent_portrayal):
255     def portray(g):
256         x = []
257         y = []
258         s = []  # size
259         c = []  # color
260         for i in range(g.width):
261             for j in range(g.height):
262                 content = g._grid[i][j]
263                 if not content:
264                     continue
265                 if not hasattr(content, "__iter__"):
266                     # Is a single grid
267                     content = [content]
268                 for agent in content:
269                     data = agent_portrayal(agent)
270                     x.append(i)
271                     y.append(j)
272                     if "size" in data:
273                         s.append(data["size"])
274                     if "color" in data:
275                         c.append(data["color"])
276         out = {"x": x, "y": y}
277         if len(s) > 0:
278             out["s"] = s
279         if len(c) > 0:
280             out["c"] = c
281         return out
282 
283     space_ax.scatter(**portray(space))
284 
285 
286 def _draw_network_grid(space, space_ax, agent_portrayal):
287     graph = space.G
288     pos = nx.spring_layout(graph, seed=0)
289     nx.draw(
290         graph,
291         ax=space_ax,
292         pos=pos,
293         **agent_portrayal(graph),
294     )
295 
296 
297 def _draw_continuous_space(space, space_ax, agent_portrayal):
298     def portray(space):
299         x = []
300         y = []
301         s = []  # size
302         c = []  # color
303         for agent in space._agent_to_index:
304             data = agent_portrayal(agent)
305             _x, _y = agent.pos
306             x.append(_x)
307             y.append(_y)
308             if "size" in data:
309                 s.append(data["size"])
310             if "color" in data:
311                 c.append(data["color"])
312         out = {"x": x, "y": y}
313         if len(s) > 0:
314             out["s"] = s
315         if len(c) > 0:
316             out["c"] = c
317         return out
318 
319     space_ax.scatter(**portray(space))
320 
321 
322 def make_plot(model, measure):
323     fig = Figure()
324     ax = fig.subplots()
325     df = model.datacollector.get_model_vars_dataframe()
326     ax.plot(df.loc[:, measure])
327     ax.set_ylabel(measure)
328     # Set integer x axis
329     ax.xaxis.set_major_locator(MaxNLocator(integer=True))
330     solara.FigureMatplotlib(fig)
331 
332 
333 def make_text(renderer):
334     def function(model):
335         solara.Markdown(renderer(model))
336 
337     return function
338
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py
--- a/mesa/experimental/jupyter_viz.py
+++ b/mesa/experimental/jupyter_viz.py
@@ -228,6 +228,7 @@
         elif input_type == "Checkbox":
             solara.Checkbox(
                 label=label,
+                on_value=change_handler,
                 value=options.get("value"),
             )
         else:

{"golden_diff": "diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py\n--- a/mesa/experimental/jupyter_viz.py\n+++ b/mesa/experimental/jupyter_viz.py\n@@ -228,6 +228,7 @@\n         elif input_type == \"Checkbox\":\n             solara.Checkbox(\n                 label=label,\n+                on_value=change_handler,\n                 value=options.get(\"value\"),\n             )\n         else:\n", "issue": "jupyterviz checkbox input change is not propagated\n\n", "before_files": [{"content": "import threading\n\nimport matplotlib.pyplot as plt\nimport networkx as nx\nimport reacton.ipywidgets as widgets\nimport solara\nfrom matplotlib.figure import Figure\nfrom matplotlib.ticker import MaxNLocator\n\nimport mesa\n\n# Avoid interactive backend\nplt.switch_backend(\"agg\")\n\n\n@solara.component\ndef JupyterViz(\n    model_class,\n    model_params,\n    measures=None,\n    name=\"Mesa Model\",\n    agent_portrayal=None,\n    space_drawer=\"default\",\n    play_interval=150,\n):\n    \"\"\"Initialize a component to visualize a model.\n    Args:\n        model_class: class of the model to instantiate\n        model_params: parameters for initializing the model\n        measures: list of callables or data attributes to plot\n        name: name for display\n        agent_portrayal: options for rendering agents (dictionary)\n        space_drawer: method to render the agent space for\n            the model; default implementation is :meth:`make_space`;\n            simulations with no space to visualize should\n            specify `space_drawer=False`\n        play_interval: play interval (default: 150)\n    \"\"\"\n\n    current_step, set_current_step = solara.use_state(0)\n\n    # 1. Set up model parameters\n    user_params, fixed_params = split_model_params(model_params)\n    model_parameters, set_model_parameters = solara.use_state(\n        {**fixed_params, **{k: v[\"value\"] for k, v in user_params.items()}}\n    )\n\n    # 2. Set up Model\n    def make_model():\n        model = model_class(**model_parameters)\n        set_current_step(0)\n        return model\n\n    reset_counter = solara.use_reactive(0)\n    model = solara.use_memo(\n        make_model, dependencies=[*list(model_parameters.values()), reset_counter.value]\n    )\n\n    def handle_change_model_params(name: str, value: any):\n        set_model_parameters({**model_parameters, name: value})\n\n    # 3. Set up UI\n    solara.Markdown(name)\n    UserInputs(user_params, on_change=handle_change_model_params)\n    ModelController(model, play_interval, current_step, set_current_step, reset_counter)\n\n    with solara.GridFixed(columns=2):\n        # 4. Space\n        if space_drawer == \"default\":\n            # draw with the default implementation\n            make_space(model, agent_portrayal)\n        elif space_drawer:\n            # if specified, draw agent space with an alternate renderer\n            space_drawer(model, agent_portrayal)\n        # otherwise, do nothing (do not draw space)\n\n        # 5. Plots\n        for measure in measures:\n            if callable(measure):\n                # Is a custom object\n                measure(model)\n            else:\n                make_plot(model, measure)\n\n\n@solara.component\ndef ModelController(\n    model, play_interval, current_step, set_current_step, reset_counter\n):\n    playing = solara.use_reactive(False)\n    thread = solara.use_reactive(None)\n    # We track the previous step to detect if user resets the model via\n    # clicking the reset button or changing the parameters. If previous_step >\n    # current_step, it means a model reset happens while the simulation is\n    # still playing.\n    previous_step = solara.use_reactive(0)\n\n    def on_value_play(change):\n        if previous_step.value > current_step and current_step == 0:\n            # We add extra checks for current_step == 0, just to be sure.\n            # We automatically stop the playing if a model is reset.\n            playing.value = False\n        elif model.running:\n            do_step()\n        else:\n            playing.value = False\n\n    def do_step():\n        model.step()\n        previous_step.value = current_step\n        set_current_step(model.schedule.steps)\n\n    def do_play():\n        model.running = True\n        while model.running:\n            do_step()\n\n    def threaded_do_play():\n        if thread is not None and thread.is_alive():\n            return\n        thread.value = threading.Thread(target=do_play)\n        thread.start()\n\n    def do_pause():\n        if (thread is None) or (not thread.is_alive()):\n            return\n        model.running = False\n        thread.join()\n\n    def do_reset():\n        reset_counter.value += 1\n\n    with solara.Row():\n        solara.Button(label=\"Step\", color=\"primary\", on_click=do_step)\n        # This style is necessary so that the play widget has almost the same\n        # height as typical Solara buttons.\n        solara.Style(\n            \"\"\"\n        .widget-play {\n            height: 30px;\n        }\n        \"\"\"\n        )\n        widgets.Play(\n            value=0,\n            interval=play_interval,\n            repeat=True,\n            show_repeat=False,\n            on_value=on_value_play,\n            playing=playing.value,\n            on_playing=playing.set,\n        )\n        solara.Button(label=\"Reset\", color=\"primary\", on_click=do_reset)\n        solara.Markdown(md_text=f\"**Step:** {current_step}\")\n        # threaded_do_play is not used for now because it\n        # doesn't work in Google colab. We use\n        # ipywidgets.Play until it is fixed. The threading\n        # version is definite a much better implementation,\n        # if it works.\n        # solara.Button(label=\"\u25b6\", color=\"primary\", on_click=viz.threaded_do_play)\n        # solara.Button(label=\"\u23f8\ufe0e\", color=\"primary\", on_click=viz.do_pause)\n        # solara.Button(label=\"Reset\", color=\"primary\", on_click=do_reset)\n\n\ndef split_model_params(model_params):\n    model_params_input = {}\n    model_params_fixed = {}\n    for k, v in model_params.items():\n        if check_param_is_fixed(v):\n            model_params_fixed[k] = v\n        else:\n            model_params_input[k] = v\n    return model_params_input, model_params_fixed\n\n\ndef check_param_is_fixed(param):\n    if not isinstance(param, dict):\n        return True\n    if \"type\" not in param:\n        return True\n\n\n@solara.component\ndef UserInputs(user_params, on_change=None):\n    \"\"\"Initialize user inputs for configurable model parameters.\n    Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,\n    :class:`solara.Select`, and :class:`solara.Checkbox`.\n\n    Props:\n        user_params: dictionary with options for the input, including label,\n        min and max values, and other fields specific to the input type.\n        on_change: function to be called with (name, value) when the value of an input changes.\n    \"\"\"\n\n    for name, options in user_params.items():\n        # label for the input is \"label\" from options or name\n        label = options.get(\"label\", name)\n        input_type = options.get(\"type\")\n\n        def change_handler(value, name=name):\n            on_change(name, value)\n\n        if input_type == \"SliderInt\":\n            solara.SliderInt(\n                label,\n                value=options.get(\"value\"),\n                on_value=change_handler,\n                min=options.get(\"min\"),\n                max=options.get(\"max\"),\n                step=options.get(\"step\"),\n            )\n        elif input_type == \"SliderFloat\":\n            solara.SliderFloat(\n                label,\n                value=options.get(\"value\"),\n                on_value=change_handler,\n                min=options.get(\"min\"),\n                max=options.get(\"max\"),\n                step=options.get(\"step\"),\n            )\n        elif input_type == \"Select\":\n            solara.Select(\n                label,\n                value=options.get(\"value\"),\n                on_value=change_handler,\n                values=options.get(\"values\"),\n            )\n        elif input_type == \"Checkbox\":\n            solara.Checkbox(\n                label=label,\n                value=options.get(\"value\"),\n            )\n        else:\n            raise ValueError(f\"{input_type} is not a supported input type\")\n\n\ndef make_space(model, agent_portrayal):\n    space_fig = Figure()\n    space_ax = space_fig.subplots()\n    space = getattr(model, \"grid\", None)\n    if space is None:\n        # Sometimes the space is defined as model.space instead of model.grid\n        space = model.space\n    if isinstance(space, mesa.space.NetworkGrid):\n        _draw_network_grid(space, space_ax, agent_portrayal)\n    elif isinstance(space, mesa.space.ContinuousSpace):\n        _draw_continuous_space(space, space_ax, agent_portrayal)\n    else:\n        _draw_grid(space, space_ax, agent_portrayal)\n    space_ax.set_axis_off()\n    solara.FigureMatplotlib(space_fig, format=\"png\")\n\n\ndef _draw_grid(space, space_ax, agent_portrayal):\n    def portray(g):\n        x = []\n        y = []\n        s = []  # size\n        c = []  # color\n        for i in range(g.width):\n            for j in range(g.height):\n                content = g._grid[i][j]\n                if not content:\n                    continue\n                if not hasattr(content, \"__iter__\"):\n                    # Is a single grid\n                    content = [content]\n                for agent in content:\n                    data = agent_portrayal(agent)\n                    x.append(i)\n                    y.append(j)\n                    if \"size\" in data:\n                        s.append(data[\"size\"])\n                    if \"color\" in data:\n                        c.append(data[\"color\"])\n        out = {\"x\": x, \"y\": y}\n        if len(s) > 0:\n            out[\"s\"] = s\n        if len(c) > 0:\n            out[\"c\"] = c\n        return out\n\n    space_ax.scatter(**portray(space))\n\n\ndef _draw_network_grid(space, space_ax, agent_portrayal):\n    graph = space.G\n    pos = nx.spring_layout(graph, seed=0)\n    nx.draw(\n        graph,\n        ax=space_ax,\n        pos=pos,\n        **agent_portrayal(graph),\n    )\n\n\ndef _draw_continuous_space(space, space_ax, agent_portrayal):\n    def portray(space):\n        x = []\n        y = []\n        s = []  # size\n        c = []  # color\n        for agent in space._agent_to_index:\n            data = agent_portrayal(agent)\n            _x, _y = agent.pos\n            x.append(_x)\n            y.append(_y)\n            if \"size\" in data:\n                s.append(data[\"size\"])\n            if \"color\" in data:\n                c.append(data[\"color\"])\n        out = {\"x\": x, \"y\": y}\n        if len(s) > 0:\n            out[\"s\"] = s\n        if len(c) > 0:\n            out[\"c\"] = c\n        return out\n\n    space_ax.scatter(**portray(space))\n\n\ndef make_plot(model, measure):\n    fig = Figure()\n    ax = fig.subplots()\n    df = model.datacollector.get_model_vars_dataframe()\n    ax.plot(df.loc[:, measure])\n    ax.set_ylabel(measure)\n    # Set integer x axis\n    ax.xaxis.set_major_locator(MaxNLocator(integer=True))\n    solara.FigureMatplotlib(fig)\n\n\ndef make_text(renderer):\n    def function(model):\n        solara.Markdown(renderer(model))\n\n    return function\n", "path": "mesa/experimental/jupyter_viz.py"}], "after_files": [{"content": "import threading\n\nimport matplotlib.pyplot as plt\nimport networkx as nx\nimport reacton.ipywidgets as widgets\nimport solara\nfrom matplotlib.figure import Figure\nfrom matplotlib.ticker import MaxNLocator\n\nimport mesa\n\n# Avoid interactive backend\nplt.switch_backend(\"agg\")\n\n\n@solara.component\ndef JupyterViz(\n    model_class,\n    model_params,\n    measures=None,\n    name=\"Mesa Model\",\n    agent_portrayal=None,\n    space_drawer=\"default\",\n    play_interval=150,\n):\n    \"\"\"Initialize a component to visualize a model.\n    Args:\n        model_class: class of the model to instantiate\n        model_params: parameters for initializing the model\n        measures: list of callables or data attributes to plot\n        name: name for display\n        agent_portrayal: options for rendering agents (dictionary)\n        space_drawer: method to render the agent space for\n            the model; default implementation is :meth:`make_space`;\n            simulations with no space to visualize should\n            specify `space_drawer=False`\n        play_interval: play interval (default: 150)\n    \"\"\"\n\n    current_step, set_current_step = solara.use_state(0)\n\n    # 1. Set up model parameters\n    user_params, fixed_params = split_model_params(model_params)\n    model_parameters, set_model_parameters = solara.use_state(\n        {**fixed_params, **{k: v[\"value\"] for k, v in user_params.items()}}\n    )\n\n    # 2. Set up Model\n    def make_model():\n        model = model_class(**model_parameters)\n        set_current_step(0)\n        return model\n\n    reset_counter = solara.use_reactive(0)\n    model = solara.use_memo(\n        make_model, dependencies=[*list(model_parameters.values()), reset_counter.value]\n    )\n\n    def handle_change_model_params(name: str, value: any):\n        set_model_parameters({**model_parameters, name: value})\n\n    # 3. Set up UI\n    solara.Markdown(name)\n    UserInputs(user_params, on_change=handle_change_model_params)\n    ModelController(model, play_interval, current_step, set_current_step, reset_counter)\n\n    with solara.GridFixed(columns=2):\n        # 4. Space\n        if space_drawer == \"default\":\n            # draw with the default implementation\n            make_space(model, agent_portrayal)\n        elif space_drawer:\n            # if specified, draw agent space with an alternate renderer\n            space_drawer(model, agent_portrayal)\n        # otherwise, do nothing (do not draw space)\n\n        # 5. Plots\n        for measure in measures:\n            if callable(measure):\n                # Is a custom object\n                measure(model)\n            else:\n                make_plot(model, measure)\n\n\n@solara.component\ndef ModelController(\n    model, play_interval, current_step, set_current_step, reset_counter\n):\n    playing = solara.use_reactive(False)\n    thread = solara.use_reactive(None)\n    # We track the previous step to detect if user resets the model via\n    # clicking the reset button or changing the parameters. If previous_step >\n    # current_step, it means a model reset happens while the simulation is\n    # still playing.\n    previous_step = solara.use_reactive(0)\n\n    def on_value_play(change):\n        if previous_step.value > current_step and current_step == 0:\n            # We add extra checks for current_step == 0, just to be sure.\n            # We automatically stop the playing if a model is reset.\n            playing.value = False\n        elif model.running:\n            do_step()\n        else:\n            playing.value = False\n\n    def do_step():\n        model.step()\n        previous_step.value = current_step\n        set_current_step(model.schedule.steps)\n\n    def do_play():\n        model.running = True\n        while model.running:\n            do_step()\n\n    def threaded_do_play():\n        if thread is not None and thread.is_alive():\n            return\n        thread.value = threading.Thread(target=do_play)\n        thread.start()\n\n    def do_pause():\n        if (thread is None) or (not thread.is_alive()):\n            return\n        model.running = False\n        thread.join()\n\n    def do_reset():\n        reset_counter.value += 1\n\n    with solara.Row():\n        solara.Button(label=\"Step\", color=\"primary\", on_click=do_step)\n        # This style is necessary so that the play widget has almost the same\n        # height as typical Solara buttons.\n        solara.Style(\n            \"\"\"\n        .widget-play {\n            height: 30px;\n        }\n        \"\"\"\n        )\n        widgets.Play(\n            value=0,\n            interval=play_interval,\n            repeat=True,\n            show_repeat=False,\n            on_value=on_value_play,\n            playing=playing.value,\n            on_playing=playing.set,\n        )\n        solara.Button(label=\"Reset\", color=\"primary\", on_click=do_reset)\n        solara.Markdown(md_text=f\"**Step:** {current_step}\")\n        # threaded_do_play is not used for now because it\n        # doesn't work in Google colab. We use\n        # ipywidgets.Play until it is fixed. The threading\n        # version is definite a much better implementation,\n        # if it works.\n        # solara.Button(label=\"\u25b6\", color=\"primary\", on_click=viz.threaded_do_play)\n        # solara.Button(label=\"\u23f8\ufe0e\", color=\"primary\", on_click=viz.do_pause)\n        # solara.Button(label=\"Reset\", color=\"primary\", on_click=do_reset)\n\n\ndef split_model_params(model_params):\n    model_params_input = {}\n    model_params_fixed = {}\n    for k, v in model_params.items():\n        if check_param_is_fixed(v):\n            model_params_fixed[k] = v\n        else:\n            model_params_input[k] = v\n    return model_params_input, model_params_fixed\n\n\ndef check_param_is_fixed(param):\n    if not isinstance(param, dict):\n        return True\n    if \"type\" not in param:\n        return True\n\n\n@solara.component\ndef UserInputs(user_params, on_change=None):\n    \"\"\"Initialize user inputs for configurable model parameters.\n    Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,\n    :class:`solara.Select`, and :class:`solara.Checkbox`.\n\n    Props:\n        user_params: dictionary with options for the input, including label,\n        min and max values, and other fields specific to the input type.\n        on_change: function to be called with (name, value) when the value of an input changes.\n    \"\"\"\n\n    for name, options in user_params.items():\n        # label for the input is \"label\" from options or name\n        label = options.get(\"label\", name)\n        input_type = options.get(\"type\")\n\n        def change_handler(value, name=name):\n            on_change(name, value)\n\n        if input_type == \"SliderInt\":\n            solara.SliderInt(\n                label,\n                value=options.get(\"value\"),\n                on_value=change_handler,\n                min=options.get(\"min\"),\n                max=options.get(\"max\"),\n                step=options.get(\"step\"),\n            )\n        elif input_type == \"SliderFloat\":\n            solara.SliderFloat(\n                label,\n                value=options.get(\"value\"),\n                on_value=change_handler,\n                min=options.get(\"min\"),\n                max=options.get(\"max\"),\n                step=options.get(\"step\"),\n            )\n        elif input_type == \"Select\":\n            solara.Select(\n                label,\n                value=options.get(\"value\"),\n                on_value=change_handler,\n                values=options.get(\"values\"),\n            )\n        elif input_type == \"Checkbox\":\n            solara.Checkbox(\n                label=label,\n                on_value=change_handler,\n                value=options.get(\"value\"),\n            )\n        else:\n            raise ValueError(f\"{input_type} is not a supported input type\")\n\n\ndef make_space(model, agent_portrayal):\n    space_fig = Figure()\n    space_ax = space_fig.subplots()\n    space = getattr(model, \"grid\", None)\n    if space is None:\n        # Sometimes the space is defined as model.space instead of model.grid\n        space = model.space\n    if isinstance(space, mesa.space.NetworkGrid):\n        _draw_network_grid(space, space_ax, agent_portrayal)\n    elif isinstance(space, mesa.space.ContinuousSpace):\n        _draw_continuous_space(space, space_ax, agent_portrayal)\n    else:\n        _draw_grid(space, space_ax, agent_portrayal)\n    space_ax.set_axis_off()\n    solara.FigureMatplotlib(space_fig, format=\"png\")\n\n\ndef _draw_grid(space, space_ax, agent_portrayal):\n    def portray(g):\n        x = []\n        y = []\n        s = []  # size\n        c = []  # color\n        for i in range(g.width):\n            for j in range(g.height):\n                content = g._grid[i][j]\n                if not content:\n                    continue\n                if not hasattr(content, \"__iter__\"):\n                    # Is a single grid\n                    content = [content]\n                for agent in content:\n                    data = agent_portrayal(agent)\n                    x.append(i)\n                    y.append(j)\n                    if \"size\" in data:\n                        s.append(data[\"size\"])\n                    if \"color\" in data:\n                        c.append(data[\"color\"])\n        out = {\"x\": x, \"y\": y}\n        if len(s) > 0:\n            out[\"s\"] = s\n        if len(c) > 0:\n            out[\"c\"] = c\n        return out\n\n    space_ax.scatter(**portray(space))\n\n\ndef _draw_network_grid(space, space_ax, agent_portrayal):\n    graph = space.G\n    pos = nx.spring_layout(graph, seed=0)\n    nx.draw(\n        graph,\n        ax=space_ax,\n        pos=pos,\n        **agent_portrayal(graph),\n    )\n\n\ndef _draw_continuous_space(space, space_ax, agent_portrayal):\n    def portray(space):\n        x = []\n        y = []\n        s = []  # size\n        c = []  # color\n        for agent in space._agent_to_index:\n            data = agent_portrayal(agent)\n            _x, _y = agent.pos\n            x.append(_x)\n            y.append(_y)\n            if \"size\" in data:\n                s.append(data[\"size\"])\n            if \"color\" in data:\n                c.append(data[\"color\"])\n        out = {\"x\": x, \"y\": y}\n        if len(s) > 0:\n            out[\"s\"] = s\n        if len(c) > 0:\n            out[\"c\"] = c\n        return out\n\n    space_ax.scatter(**portray(space))\n\n\ndef make_plot(model, measure):\n    fig = Figure()\n    ax = fig.subplots()\n    df = model.datacollector.get_model_vars_dataframe()\n    ax.plot(df.loc[:, measure])\n    ax.set_ylabel(measure)\n    # Set integer x axis\n    ax.xaxis.set_major_locator(MaxNLocator(integer=True))\n    solara.FigureMatplotlib(fig)\n\n\ndef make_text(renderer):\n    def function(model):\n        solara.Markdown(renderer(model))\n\n    return function\n", "path": "mesa/experimental/jupyter_viz.py"}]}