Skip to content

Commit 74e2df6

Browse files
authored
Extend visualization documentation (#2162)
- Add docstring to Jupyter viz module and functions - Render the docstring in Read the Docs: https://mesa.readthedocs.io/en/latest/apis/visualization.html
1 parent 0868f4b commit 74e2df6

File tree

2 files changed

+123
-39
lines changed

2 files changed

+123
-39
lines changed

docs/apis/visualization.md

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,21 @@
11
# Visualization
22

3-
```{eval-rst}
4-
.. automodule:: visualization.__init__
5-
:members:
6-
```
7-
8-
```{eval-rst}
9-
.. automodule:: visualization.ModularVisualization
10-
:members:
11-
```
12-
13-
```{eval-rst}
14-
.. automodule:: visualization.TextVisualization
15-
:members:
16-
```
3+
For a detailed tutorial, please refer to our [Visualization Tutorial](../tutorials/visualization_tutorial.ipynb).
174

18-
## Modules
5+
## Jupyter Visualization
196

207
```{eval-rst}
21-
.. automodule:: visualization.modules.__init__
8+
.. automodule:: mesa.visualization.jupyter_viz
229
:members:
10+
:undoc-members:
11+
:show-inheritance:
2312
```
2413

25-
```{eval-rst}
26-
.. automodule:: visualization.modules.CanvasGridVisualization
27-
:members:
28-
```
29-
30-
```{eval-rst}
31-
.. automodule:: visualization.modules.ChartVisualization
32-
:members:
33-
```
14+
## User Parameters
3415

3516
```{eval-rst}
36-
.. automodule:: visualization.modules.TextVisualization
17+
.. automodule:: mesa.visualization.UserParam
3718
:members:
19+
:undoc-members:
20+
:show-inheritance:
3821
```

mesa/visualization/jupyter_viz.py

Lines changed: 114 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
"""
2+
Mesa visualization module for creating interactive model visualizations.
3+
4+
This module provides components to create browser- and Jupyter notebook-based visualizations of
5+
Mesa models, allowing users to watch models run step-by-step and interact with model parameters.
6+
7+
Key features:
8+
- JupyterViz: Main component for creating visualizations, supporting grid displays and plots
9+
- ModelController: Handles model execution controls (step, play, pause, reset)
10+
- UserInputs: Generates UI elements for adjusting model parameters
11+
- Card: Renders individual visualization elements (space, measures)
12+
13+
The module uses Solara for rendering in Jupyter notebooks or as standalone web applications.
14+
It supports various types of visualizations including matplotlib plots, agent grids, and
15+
custom visualization components.
16+
17+
Usage:
18+
1. Define an agent_portrayal function to specify how agents should be displayed
19+
2. Set up model_params to define adjustable parameters
20+
3. Create a JupyterViz instance with your model, parameters, and desired measures
21+
4. Display the visualization in a Jupyter notebook or run as a Solara app
22+
23+
See the Visualization Tutorial and example models for more details.
24+
"""
25+
126
import sys
227
import threading
328

@@ -19,6 +44,21 @@
1944
def Card(
2045
model, measures, agent_portrayal, space_drawer, dependencies, color, layout_type
2146
):
47+
"""
48+
Create a card component for visualizing model space or measures.
49+
50+
Args:
51+
model: The Mesa model instance
52+
measures: List of measures to be plotted
53+
agent_portrayal: Function to define agent appearance
54+
space_drawer: Method to render agent space
55+
dependencies: List of dependencies for updating the visualization
56+
color: Background color of the card
57+
layout_type: Type of layout (Space or Measure)
58+
59+
Returns:
60+
rv.Card: A card component containing the visualization
61+
"""
2262
with rv.Card(
2363
style_=f"background-color: {color}; width: 100%; height: 100%"
2464
) as main:
@@ -60,19 +100,21 @@ def JupyterViz(
60100
play_interval=150,
61101
seed=None,
62102
):
63-
"""Initialize a component to visualize a model.
103+
"""
104+
Initialize a component to visualize a model.
105+
64106
Args:
65-
model_class: class of the model to instantiate
66-
model_params: parameters for initializing the model
67-
measures: list of callables or data attributes to plot
68-
name: name for display
69-
agent_portrayal: options for rendering agents (dictionary)
70-
space_drawer: method to render the agent space for
107+
model_class: Class of the model to instantiate
108+
model_params: Parameters for initializing the model
109+
measures: List of callables or data attributes to plot
110+
name: Name for display
111+
agent_portrayal: Options for rendering agents (dictionary)
112+
space_drawer: Method to render the agent space for
71113
the model; default implementation is the `SpaceMatplotlib` component;
72114
simulations with no space to visualize should
73115
specify `space_drawer=False`
74-
play_interval: play interval (default: 150)
75-
seed: the random seed used to initialize the model
116+
play_interval: Play interval (default: 150)
117+
seed: The random seed used to initialize the model
76118
"""
77119
if name is None:
78120
name = model_class.__name__
@@ -88,6 +130,7 @@ def JupyterViz(
88130

89131
# 2. Set up Model
90132
def make_model():
133+
"""Create a new model instance with current parameters and seed."""
91134
model = model_class.__new__(
92135
model_class, **model_parameters, seed=reactive_seed.value
93136
)
@@ -106,6 +149,7 @@ def make_model():
106149
)
107150

108151
def handle_change_model_params(name: str, value: any):
152+
"""Update model parameters when user input changes."""
109153
set_model_parameters({**model_parameters, name: value})
110154

111155
# 3. Set up UI
@@ -115,12 +159,14 @@ def handle_change_model_params(name: str, value: any):
115159

116160
# render layout and plot
117161
def do_reseed():
162+
"""Update the random seed for the model."""
118163
reactive_seed.value = model.random.random()
119164

120165
# jupyter
121166
dependencies = [current_step.value, reactive_seed.value]
122167

123168
def render_in_jupyter():
169+
"""Render the visualization components in Jupyter notebook."""
124170
with solara.GridFixed(columns=2):
125171
UserInputs(user_params, on_change=handle_change_model_params)
126172
ModelController(model, play_interval, current_step, reset_counter)
@@ -154,6 +200,7 @@ def render_in_jupyter():
154200
)
155201

156202
def render_in_browser():
203+
"""Render the visualization components in a web browser."""
157204
# if space drawer is disabled, do not include it
158205
layout_types = [{"Space": "default"}] if space_drawer else []
159206

@@ -205,6 +252,15 @@ def render_in_browser():
205252

206253
@solara.component
207254
def ModelController(model, play_interval, current_step, reset_counter):
255+
"""
256+
Create controls for model execution (step, play, pause, reset).
257+
258+
Args:
259+
model: The model being visualized
260+
play_interval: Interval between steps during play
261+
current_step: Reactive value for the current step
262+
reset_counter: Counter to trigger model reset
263+
"""
208264
playing = solara.use_reactive(False)
209265
thread = solara.use_reactive(None)
210266
# We track the previous step to detect if user resets the model via
@@ -214,6 +270,7 @@ def ModelController(model, play_interval, current_step, reset_counter):
214270
previous_step = solara.use_reactive(0)
215271

216272
def on_value_play(change):
273+
"""Handle play/pause state changes."""
217274
if previous_step.value > current_step.value and current_step.value == 0:
218275
# We add extra checks for current_step.value == 0, just to be sure.
219276
# We automatically stop the playing if a model is reset.
@@ -224,31 +281,37 @@ def on_value_play(change):
224281
playing.value = False
225282

226283
def do_step():
284+
"""Advance the model by one step."""
227285
model.step()
228286
previous_step.value = current_step.value
229287
current_step.value = model._steps
230288

231289
def do_play():
290+
"""Run the model continuously."""
232291
model.running = True
233292
while model.running:
234293
do_step()
235294

236295
def threaded_do_play():
296+
"""Start a new thread for continuous model execution."""
237297
if thread is not None and thread.is_alive():
238298
return
239299
thread.value = threading.Thread(target=do_play)
240300
thread.start()
241301

242302
def do_pause():
303+
"""Pause the model execution."""
243304
if (thread is None) or (not thread.is_alive()):
244305
return
245306
model.running = False
246307
thread.join()
247308

248309
def do_reset():
310+
"""Reset the model."""
249311
reset_counter.value += 1
250312

251313
def do_set_playing(value):
314+
"""Set the playing state."""
252315
if current_step.value == 0:
253316
# This means the model has been recreated, and the step resets to
254317
# 0. We want to avoid triggering the playing.value = False in the
@@ -292,6 +355,15 @@ def do_set_playing(value):
292355

293356

294357
def split_model_params(model_params):
358+
"""
359+
Split model parameters into user-adjustable and fixed parameters.
360+
361+
Args:
362+
model_params: Dictionary of all model parameters
363+
364+
Returns:
365+
tuple: (user_adjustable_params, fixed_params)
366+
"""
295367
model_params_input = {}
296368
model_params_fixed = {}
297369
for k, v in model_params.items():
@@ -303,6 +375,15 @@ def split_model_params(model_params):
303375

304376

305377
def check_param_is_fixed(param):
378+
"""
379+
Check if a parameter is fixed (not user-adjustable).
380+
381+
Args:
382+
param: Parameter to check
383+
384+
Returns:
385+
bool: True if parameter is fixed, False otherwise
386+
"""
306387
if isinstance(param, Slider):
307388
return False
308389
if not isinstance(param, dict):
@@ -313,14 +394,15 @@ def check_param_is_fixed(param):
313394

314395
@solara.component
315396
def UserInputs(user_params, on_change=None):
316-
"""Initialize user inputs for configurable model parameters.
397+
"""
398+
Initialize user inputs for configurable model parameters.
317399
Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
318400
:class:`solara.Select`, and :class:`solara.Checkbox`.
319401
320-
Props:
321-
user_params: dictionary with options for the input, including label,
402+
Args:
403+
user_params: Dictionary with options for the input, including label,
322404
min and max values, and other fields specific to the input type.
323-
on_change: function to be called with (name, value) when the value of an input changes.
405+
on_change: Function to be called with (name, value) when the value of an input changes.
324406
"""
325407

326408
for name, options in user_params.items():
@@ -381,13 +463,32 @@ def change_handler(value, name=name):
381463

382464

383465
def make_text(renderer):
466+
"""
467+
Create a function that renders text using Markdown.
468+
469+
Args:
470+
renderer: Function that takes a model and returns a string
471+
472+
Returns:
473+
function: A function that renders the text as Markdown
474+
"""
475+
384476
def function(model):
385477
solara.Markdown(renderer(model))
386478

387479
return function
388480

389481

390482
def make_initial_grid_layout(layout_types):
483+
"""
484+
Create an initial grid layout for visualization components.
485+
486+
Args:
487+
layout_types: List of layout types (Space or Measure)
488+
489+
Returns:
490+
list: Initial grid layout configuration
491+
"""
391492
return [
392493
{
393494
"i": i,

0 commit comments

Comments
 (0)