From a53d8e86551ebd2484c5de16e8a5f81aa1bb8014 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 18 Nov 2025 15:06:03 +0530 Subject: [PATCH 01/23] separate setup functions from drawing functions --- mesa/visualization/solara_viz.py | 18 ++---- mesa/visualization/space_renderer.py | 88 ++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py index 3bd33e5b432..b84cbd2f4a5 100644 --- a/mesa/visualization/solara_viz.py +++ b/mesa/visualization/solara_viz.py @@ -271,17 +271,15 @@ def SpaceRendererComponent( # Draw the space structure if specified if renderer.space_mesh: - renderer.draw_structure(**renderer.space_kwargs) + renderer.draw_structure() # Draw agents if specified if renderer.agent_mesh: - renderer.draw_agents( - agent_portrayal=renderer.agent_portrayal, **renderer.agent_kwargs - ) + renderer.draw_agents() # Draw property layers if specified if renderer.propertylayer_mesh: - renderer.draw_propertylayer(renderer.propertylayer_portrayal) + renderer.draw_propertylayer() # Update the fig every time frame if dependencies: @@ -306,15 +304,11 @@ def SpaceRendererComponent( propertylayer = renderer.propertylayer_mesh or None if renderer.space_mesh: - structure = renderer.draw_structure(**renderer.space_kwargs) + structure = renderer.draw_structure() if renderer.agent_mesh: - agents = renderer.draw_agents( - renderer.agent_portrayal, **renderer.agent_kwargs - ) + agents = renderer.draw_agents() if renderer.propertylayer_mesh: - propertylayer = renderer.draw_propertylayer( - renderer.propertylayer_portrayal - ) + propertylayer = renderer.draw_propertylayer() spatial_charts_list = [ chart for chart in [structure, propertylayer, agents] if chart diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index a517a70b9da..141eeb23449 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -4,6 +4,8 @@ backends, supporting various space types and visualization components. """ +from __future__ import annotations + import contextlib import warnings from collections.abc import Callable @@ -63,10 +65,17 @@ def __init__( self.space = getattr(model, "grid", getattr(model, "space", None)) self.space_drawer = self._get_space_drawer() + self.space_mesh = None self.agent_mesh = None self.propertylayer_mesh = None + self.draw_agent_kwargs = None + self.draw_space_kwargs = None + + self.agent_portrayal = None + self.propertylayer_portrayal = None + self.post_process_func = None # Keep track of whether post-processing has been applied # to avoid multiple applications on the same axis. @@ -160,56 +169,85 @@ def _map_coordinates(self, arguments): mapped_arguments["loc"] = pos[x] return mapped_arguments - - def draw_structure(self, **kwargs): - """Draw the space structure. + + def setup_structure(self, **kwargs) -> SpaceRenderer: + """Setup the space structure without drawing. Args: - **kwargs: Additional keyword arguments for the drawing function. + **kwargs: Additional keyword arguments for the setup function. Checkout respective `SpaceDrawer` class on details how to pass **kwargs. Returns: - The visual representation of the space structure. + SpaceRenderer: The current instance for method chaining. """ - # Store space_kwargs for internal use - self.space_kwargs = kwargs + # Store draw_space_kwargs for internal use + self.draw_space_kwargs = kwargs - self.space_mesh = self.backend_renderer.draw_structure(**self.space_kwargs) - return self.space_mesh + return self - def draw_agents(self, agent_portrayal: Callable, **kwargs): - """Draw agents on the space. + def setup_agents(self, agent_portrayal: Callable, **kwargs) -> SpaceRenderer: + """Setup agents on the space without drawing. Args: agent_portrayal (Callable): Function that takes an agent and returns AgentPortrayalStyle. - **kwargs: Additional keyword arguments for the drawing function. + **kwargs: Additional keyword arguments for the setup function. Checkout respective `SpaceDrawer` class on details how to pass **kwargs. Returns: - The visual representation of the agents. + SpaceRenderer: The current instance for method chaining. """ - # Store data for internal use + # Store agent_portrayal and draw_agent_kwargs for internal use self.agent_portrayal = agent_portrayal - self.agent_kwargs = kwargs + self.draw_agent_kwargs = kwargs + + return self + + def setup_propertylayer( + self, propertylayer_portrayal: Callable | dict + ) -> SpaceRenderer: + """Setup property layers on the space without drawing. + + Args: + propertylayer_portrayal (Callable | dict): Function that returns PropertyLayerStyle + or dict with portrayal parameters. + + Returns: + SpaceRenderer: The current instance for method chaining. + """ + # Store propertylayer_portrayal for internal use + self.propertylayer_portrayal = propertylayer_portrayal + return self + + def draw_structure(self): + """Draw the space structure. + + Returns: + The visual representation of the space structure. + """ + self.space_mesh = self.backend_renderer.draw_structure(**self.draw_space_kwargs) + return self.space_mesh + + def draw_agents(self): + """Draw agents on the space. + + Returns: + The visual representation of the agents. + """ # Prepare data for agent plotting arguments = self.backend_renderer.collect_agent_data( - self.space, agent_portrayal, default_size=self.space_drawer.s_default + self.space, self.agent_portrayal, default_size=self.space_drawer.s_default ) arguments = self._map_coordinates(arguments) self.agent_mesh = self.backend_renderer.draw_agents( - arguments, **self.agent_kwargs + arguments, **self.draw_agent_kwargs ) return self.agent_mesh - def draw_propertylayer(self, propertylayer_portrayal: Callable | dict): + def draw_propertylayer(self): """Draw property layers on the space. - Args: - propertylayer_portrayal (Callable | dict): Function that returns PropertyLayerStyle - or dict with portrayal parameters. - Returns: The visual representation of the property layers. @@ -267,10 +305,8 @@ def style_callable(layer_object): property_layers = self.space._mesa_property_layers # Convert portrayal to callable if needed - if isinstance(propertylayer_portrayal, dict): - self.propertylayer_portrayal = _dict_to_callable(propertylayer_portrayal) - else: - self.propertylayer_portrayal = propertylayer_portrayal + if isinstance(self.propertylayer_portrayal, dict): + self.propertylayer_portrayal = _dict_to_callable(self.propertylayer_portrayal) number_of_propertylayers = sum( [1 for layer in property_layers if layer != "empty"] From e39df00c85a34d4557fb567b63d78f7be2b40969 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 18 Nov 2025 15:07:24 +0530 Subject: [PATCH 02/23] simplify axes clearing --- .../backends/matplotlib_backend.py | 3 --- mesa/visualization/solara_viz.py | 22 +------------------ 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/mesa/visualization/backends/matplotlib_backend.py b/mesa/visualization/backends/matplotlib_backend.py index 1ae407b1683..230022e2e4e 100644 --- a/mesa/visualization/backends/matplotlib_backend.py +++ b/mesa/visualization/backends/matplotlib_backend.py @@ -47,8 +47,6 @@ def __init__(self, space_drawer): """ super().__init__(space_drawer) - self._active_colorbars = [] - def initialize_canvas(self, ax=None): """Initialize the matplotlib canvas. @@ -419,5 +417,4 @@ def draw_propertylayer(self, space, property_layers, propertylayer_portrayal): sm = ScalarMappable(norm=norm, cmap=cmap) sm.set_array([]) cbar = plt.colorbar(sm, ax=self.ax, label=layer_name) - self._active_colorbars.append(cbar) return self.ax, cbar diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py index b84cbd2f4a5..82a9bac6221 100644 --- a/mesa/visualization/solara_viz.py +++ b/mesa/visualization/solara_viz.py @@ -252,32 +252,12 @@ def SpaceRendererComponent( if renderer.backend == "matplotlib": # Clear the previous plotted data and agents - all_artists = [ - renderer.canvas.lines[:], - renderer.canvas.collections[:], - renderer.canvas.patches[:], - renderer.canvas.images[:], - renderer.canvas.artists[:], - ] - - # Remove duplicate colorbars from the canvas - for cbar in renderer.backend_renderer._active_colorbars: - cbar.remove() - renderer.backend_renderer._active_colorbars.clear() - - # Chain them together into a single iterable - for artist in itertools.chain.from_iterable(all_artists): - artist.remove() + renderer.canvas.clear() - # Draw the space structure if specified if renderer.space_mesh: renderer.draw_structure() - - # Draw agents if specified if renderer.agent_mesh: renderer.draw_agents() - - # Draw property layers if specified if renderer.propertylayer_mesh: renderer.draw_propertylayer() From 3f410d0da58bfca64ba1b82ecb97e518d98a6dc6 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 18 Nov 2025 16:28:21 +0530 Subject: [PATCH 03/23] cleanup --- mesa/visualization/solara_viz.py | 6 +- mesa/visualization/space_drawers.py | 108 +++++++++++++-------------- mesa/visualization/space_renderer.py | 40 +++------- 3 files changed, 66 insertions(+), 88 deletions(-) diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py index 82a9bac6221..19a75b98b10 100644 --- a/mesa/visualization/solara_viz.py +++ b/mesa/visualization/solara_viz.py @@ -370,8 +370,8 @@ def ComponentsView( sorted_page_indices = all_indices # State for current tab and layouts - current_tab_index, set_current_tab_index = solara.use_state(0) - layouts, set_layouts = solara.use_state({}) + current_tab_index, set_current_tab_index = solara.use_state(0) # noqa: SH101 + layouts, set_layouts = solara.use_state({}) # noqa: SH101 # Keep layouts in sync with pages def sync_layouts(): @@ -390,7 +390,7 @@ def sync_layouts(): if new_layouts or len(cleaned_layouts) != len(layouts): set_layouts({**cleaned_layouts, **new_layouts}) - solara.use_effect(sync_layouts, list(pages.keys())) + solara.use_effect(sync_layouts, list(pages.keys())) # noqa: SH101 # Tab Navigation with solara.v.Tabs(v_model=current_tab_index, on_v_model=set_current_tab_index): diff --git a/mesa/visualization/space_drawers.py b/mesa/visualization/space_drawers.py index da5a4079533..f0b5c891f23 100644 --- a/mesa/visualization/space_drawers.py +++ b/mesa/visualization/space_drawers.py @@ -82,12 +82,12 @@ def __init__(self, space: OrthogonalGrid): self.viz_ymin = -0.5 self.viz_ymax = self.space.height - 0.5 - def draw_matplotlib(self, ax=None, **space_kwargs): + def draw_matplotlib(self, ax=None, **draw_space_kwargs): """Draw the orthogonal grid using matplotlib. Args: ax: Matplotlib axes object to draw on - **space_kwargs: Additional keyword arguments for styling. + **draw_space_kwargs: Additional keyword arguments for styling. Examples: figsize=(10, 10), color="blue", linewidth=2. @@ -96,8 +96,8 @@ def draw_matplotlib(self, ax=None, **space_kwargs): The modified axes object """ fig_kwargs = { - "figsize": space_kwargs.pop("figsize", (8, 8)), - "dpi": space_kwargs.pop("dpi", 100), + "figsize": draw_space_kwargs.pop("figsize", (8, 8)), + "dpi": draw_space_kwargs.pop("dpi", 100), } if ax is None: @@ -110,7 +110,7 @@ def draw_matplotlib(self, ax=None, **space_kwargs): "linewidth": 1, "alpha": 1, } - line_kwargs.update(space_kwargs) + line_kwargs.update(draw_space_kwargs) ax.set_xlim(self.viz_xmin, self.viz_xmax) ax.set_ylim(self.viz_ymin, self.viz_ymax) @@ -123,13 +123,13 @@ def draw_matplotlib(self, ax=None, **space_kwargs): return ax - def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): + def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): """Draw the orthogonal grid using Altair. Args: chart_width: Width for the shown chart chart_height: Height for the shown chart - **chart_kwargs: Additional keyword arguments for styling the chart. + **draw_chart_kwargs: Additional keyword arguments for styling the chart. Examples: width=500, height=500, title="Grid". @@ -139,12 +139,12 @@ def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): """ # for axis and grid styling axis_kwargs = { - "xlabel": chart_kwargs.pop("xlabel", "X"), - "ylabel": chart_kwargs.pop("ylabel", "Y"), - "grid_color": chart_kwargs.pop("grid_color", "lightgray"), - "grid_dash": chart_kwargs.pop("grid_dash", [2, 2]), - "grid_width": chart_kwargs.pop("grid_width", 1), - "grid_opacity": chart_kwargs.pop("grid_opacity", 1), + "xlabel": draw_chart_kwargs.pop("xlabel", "X"), + "ylabel": draw_chart_kwargs.pop("ylabel", "Y"), + "grid_color": draw_chart_kwargs.pop("grid_color", "lightgray"), + "grid_dash": draw_chart_kwargs.pop("grid_dash", [2, 2]), + "grid_width": draw_chart_kwargs.pop("grid_width", 1), + "grid_opacity": draw_chart_kwargs.pop("grid_opacity", 1), } # for chart properties @@ -152,7 +152,7 @@ def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): "width": chart_width, "height": chart_height, } - chart_props.update(chart_kwargs) + chart_props.update(draw_chart_kwargs) chart = ( alt.Chart(pd.DataFrame([{}])) @@ -263,12 +263,12 @@ def _get_unique_edges(self): edges.add(edge) return edges - def draw_matplotlib(self, ax=None, **space_kwargs): + def draw_matplotlib(self, ax=None, **draw_space_kwargs): """Draw the hexagonal grid using matplotlib. Args: ax: Matplotlib axes object to draw on - **space_kwargs: Additional keyword arguments for styling. + **draw_space_kwargs: Additional keyword arguments for styling. Examples: figsize=(8, 8), color="red", alpha=0.5. @@ -277,8 +277,8 @@ def draw_matplotlib(self, ax=None, **space_kwargs): The modified axes object """ fig_kwargs = { - "figsize": space_kwargs.pop("figsize", (8, 8)), - "dpi": space_kwargs.pop("dpi", 100), + "figsize": draw_space_kwargs.pop("figsize", (8, 8)), + "dpi": draw_space_kwargs.pop("dpi", 100), } if ax is None: @@ -290,7 +290,7 @@ def draw_matplotlib(self, ax=None, **space_kwargs): "linewidth": 1, "alpha": 0.8, } - line_kwargs.update(space_kwargs) + line_kwargs.update(draw_space_kwargs) ax.set_xlim(self.viz_xmin, self.viz_xmax) ax.set_ylim(self.viz_ymin, self.viz_ymax) @@ -300,13 +300,13 @@ def draw_matplotlib(self, ax=None, **space_kwargs): ax.add_collection(LineCollection(list(edges), **line_kwargs)) return ax - def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): + def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): """Draw the hexagonal grid using Altair. Args: chart_width: Width for the shown chart chart_height: Height for the shown chart - **chart_kwargs: Additional keyword arguments for styling the chart. + **draw_chart_kwargs: Additional keyword arguments for styling the chart. Examples: * Line properties like color, strokeDash, strokeWidth, opacity. @@ -316,17 +316,17 @@ def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): Altair chart object representing the hexagonal grid. """ mark_kwargs = { - "color": chart_kwargs.pop("color", "black"), - "strokeDash": chart_kwargs.pop("strokeDash", [2, 2]), - "strokeWidth": chart_kwargs.pop("strokeWidth", 1), - "opacity": chart_kwargs.pop("opacity", 0.8), + "color": draw_chart_kwargs.pop("color", "black"), + "strokeDash": draw_chart_kwargs.pop("strokeDash", [2, 2]), + "strokeWidth": draw_chart_kwargs.pop("strokeWidth", 1), + "opacity": draw_chart_kwargs.pop("opacity", 0.8), } chart_props = { "width": chart_width, "height": chart_height, } - chart_props.update(chart_kwargs) + chart_props.update(draw_chart_kwargs) edge_data = [] edges = self._get_unique_edges() @@ -400,12 +400,12 @@ def __init__( self.viz_ymin = ymin - height / 20 self.viz_ymax = ymax + height / 20 - def draw_matplotlib(self, ax=None, **space_kwargs): + def draw_matplotlib(self, ax=None, **draw_space_kwargs): """Draw the network using matplotlib. Args: ax: Matplotlib axes object to draw on. - **space_kwargs: Dictionaries of keyword arguments for styling. + **draw_space_kwargs: Dictionaries of keyword arguments for styling. Can also handle zorder for both nodes and edges if passed. * ``node_kwargs``: A dict passed to nx.draw_networkx_nodes. * ``edge_kwargs``: A dict passed to nx.draw_networkx_edges. @@ -423,8 +423,8 @@ def draw_matplotlib(self, ax=None, **space_kwargs): node_kwargs = {"alpha": 0.5} edge_kwargs = {"alpha": 0.5, "style": "--"} - node_kwargs.update(space_kwargs.get("node_kwargs", {})) - edge_kwargs.update(space_kwargs.get("edge_kwargs", {})) + node_kwargs.update(draw_space_kwargs.get("node_kwargs", {})) + edge_kwargs.update(draw_space_kwargs.get("edge_kwargs", {})) node_zorder = node_kwargs.pop("zorder", 1) edge_zorder = edge_kwargs.pop("zorder", 0) @@ -443,13 +443,13 @@ def draw_matplotlib(self, ax=None, **space_kwargs): return ax - def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): + def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): """Draw the network using Altair. Args: chart_width: Width for the shown chart chart_height: Height for the shown chart - **chart_kwargs: Dictionaries for styling the chart. + **draw_chart_kwargs: Dictionaries for styling the chart. * ``node_kwargs``: A dict of properties for the node's mark_point. * ``edge_kwargs``: A dict of properties for the edge's mark_rule. * Other kwargs (e.g., title, width) are passed to chart.properties(). @@ -474,14 +474,14 @@ def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): node_mark_kwargs = {"filled": True, "opacity": 0.5, "size": 500} edge_mark_kwargs = {"opacity": 0.5, "strokeDash": [5, 3]} - node_mark_kwargs.update(chart_kwargs.pop("node_kwargs", {})) - edge_mark_kwargs.update(chart_kwargs.pop("edge_kwargs", {})) + node_mark_kwargs.update(draw_chart_kwargs.pop("node_kwargs", {})) + edge_mark_kwargs.update(draw_chart_kwargs.pop("edge_kwargs", {})) - chart_kwargs = { + draw_chart_kwargs = { "width": chart_width, "height": chart_height, } - chart_kwargs.update(chart_kwargs) + draw_chart_kwargs.update(draw_chart_kwargs) edge_plot = ( alt.Chart(edge_positions) @@ -510,8 +510,8 @@ def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): chart = edge_plot + node_plot - if chart_kwargs: - chart = chart.properties(**chart_kwargs) + if draw_chart_kwargs: + chart = chart.properties(**draw_chart_kwargs) return chart @@ -540,12 +540,12 @@ def __init__(self, space: ContinuousSpace): self.viz_ymin = self.space.y_min - y_padding self.viz_ymax = self.space.y_max + y_padding - def draw_matplotlib(self, ax=None, **space_kwargs): + def draw_matplotlib(self, ax=None, **draw_space_kwargs): """Draw the continuous space using matplotlib. Args: ax: Matplotlib axes object to draw on - **space_kwargs: Keyword arguments for styling the axis frame. + **draw_space_kwargs: Keyword arguments for styling the axis frame. Examples: linewidth=3, color="green" @@ -558,7 +558,7 @@ def draw_matplotlib(self, ax=None, **space_kwargs): border_style = "solid" if not self.space.torus else (0, (5, 10)) spine_kwargs = {"linewidth": 1.5, "color": "black", "linestyle": border_style} - spine_kwargs.update(space_kwargs) + spine_kwargs.update(draw_space_kwargs) for spine in ax.spines.values(): spine.set(**spine_kwargs) @@ -568,20 +568,20 @@ def draw_matplotlib(self, ax=None, **space_kwargs): return ax - def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): + def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): """Draw the continuous space using Altair. Args: chart_width: Width for the shown chart chart_height: Height for the shown chart - **chart_kwargs: Keyword arguments for styling the chart's view properties. + **draw_chart_kwargs: Keyword arguments for styling the chart's view properties. See Altair's documentation for `configure_view`. Returns: An Altair Chart object representing the space. """ chart_props = {"width": chart_width, "height": chart_height} - chart_props.update(chart_kwargs) + chart_props.update(draw_chart_kwargs) chart = ( alt.Chart(pd.DataFrame([{}])) @@ -712,12 +712,12 @@ def _get_clipped_segments(self): return final_segments, clip_box - def draw_matplotlib(self, ax=None, **space_kwargs): + def draw_matplotlib(self, ax=None, **draw_space_kwargs): """Draw the Voronoi diagram using matplotlib. Args: ax: Matplotlib axes object to draw on - **space_kwargs: Keyword arguments passed to matplotlib's LineCollection. + **draw_space_kwargs: Keyword arguments passed to matplotlib's LineCollection. Examples: lw=2, alpha=0.5, colors='red' @@ -736,7 +736,7 @@ def draw_matplotlib(self, ax=None, **space_kwargs): if final_segments: # Define default styles for the plot style_args = {"colors": "k", "linestyle": "dotted", "lw": 1} - style_args.update(space_kwargs) + style_args.update(draw_space_kwargs) # Create the LineCollection with the final styles lc = LineCollection(final_segments, **style_args) @@ -744,13 +744,13 @@ def draw_matplotlib(self, ax=None, **space_kwargs): return ax - def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): + def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): """Draw the Voronoi diagram using Altair. Args: chart_width: Width for the shown chart chart_height: Height for the shown chart - **chart_kwargs: Additional keyword arguments for styling the chart. + **draw_chart_kwargs: Additional keyword arguments for styling the chart. Examples: * Line properties like color, strokeDash, strokeWidth, opacity. @@ -771,14 +771,14 @@ def draw_altair(self, chart_width=450, chart_height=350, **chart_kwargs): # Define default properties for the mark mark_kwargs = { - "color": chart_kwargs.pop("color", "black"), - "strokeDash": chart_kwargs.pop("strokeDash", [2, 2]), - "strokeWidth": chart_kwargs.pop("strokeWidth", 1), - "opacity": chart_kwargs.pop("opacity", 0.8), + "color": draw_chart_kwargs.pop("color", "black"), + "strokeDash": draw_chart_kwargs.pop("strokeDash", [2, 2]), + "strokeWidth": draw_chart_kwargs.pop("strokeWidth", 1), + "opacity": draw_chart_kwargs.pop("opacity", 0.8), } chart_props = {"width": chart_width, "height": chart_height} - chart_props.update(chart_kwargs) + chart_props.update(draw_chart_kwargs) chart = ( alt.Chart(df) diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index 141eeb23449..28d5a8116a1 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -70,8 +70,8 @@ def __init__( self.agent_mesh = None self.propertylayer_mesh = None - self.draw_agent_kwargs = None - self.draw_space_kwargs = None + self.draw_agent_kwargs = {} + self.draw_space_kwargs = {} self.agent_portrayal = None self.propertylayer_portrayal = None @@ -319,41 +319,19 @@ def style_callable(layer_object): ) return self.propertylayer_mesh - def render( - self, - agent_portrayal: Callable | None = None, - propertylayer_portrayal: Callable | dict | None = None, - post_process: Callable | None = None, - **kwargs, - ): + def render(self): """Render the complete space with structure, agents, and property layers. It is an all-in-one method that draws everything required therefore eliminates - the need of calling each method separately, but has a drawback, if want to pass - kwargs to customize the drawing, they have to be broken into - space_kwargs and agent_kwargs. - - Args: - agent_portrayal (Callable | None): Function that returns AgentPortrayalStyle. - If None, agents won't be drawn. - propertylayer_portrayal (Callable | dict | None): Function that returns - PropertyLayerStyle or dict with portrayal parameters. If None, - property layers won't be drawn. - post_process (Callable | None): Function to apply post-processing to the canvas. - **kwargs: Additional keyword arguments for drawing functions. - * ``space_kwargs`` (dict): Arguments for ``draw_structure()``. - * ``agent_kwargs`` (dict): Arguments for ``draw_agents()``. + the need of calling each method separately. """ - space_kwargs = kwargs.pop("space_kwargs", {}) - agent_kwargs = kwargs.pop("agent_kwargs", {}) if self.space_mesh is None: - self.draw_structure(**space_kwargs) - if self.agent_mesh is None and agent_portrayal is not None: - self.draw_agents(agent_portrayal, **agent_kwargs) - if self.propertylayer_mesh is None and propertylayer_portrayal is not None: - self.draw_propertylayer(propertylayer_portrayal) + self.draw_structure() + if self.agent_mesh is None and self.agent_portrayal is not None: + self.draw_agents() + if self.propertylayer_mesh is None and self.propertylayer_portrayal is not None: + self.draw_propertylayer() - self.post_process_func = post_process return self @property From 238f33c138261c4d7758bc81cae72c373698218d Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 18 Nov 2025 16:28:24 +0530 Subject: [PATCH 04/23] Revert "simplify axes clearing" This reverts commit e39df00c85a34d4557fb567b63d78f7be2b40969. --- .../backends/matplotlib_backend.py | 3 +++ mesa/visualization/solara_viz.py | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/backends/matplotlib_backend.py b/mesa/visualization/backends/matplotlib_backend.py index 230022e2e4e..1ae407b1683 100644 --- a/mesa/visualization/backends/matplotlib_backend.py +++ b/mesa/visualization/backends/matplotlib_backend.py @@ -47,6 +47,8 @@ def __init__(self, space_drawer): """ super().__init__(space_drawer) + self._active_colorbars = [] + def initialize_canvas(self, ax=None): """Initialize the matplotlib canvas. @@ -417,4 +419,5 @@ def draw_propertylayer(self, space, property_layers, propertylayer_portrayal): sm = ScalarMappable(norm=norm, cmap=cmap) sm.set_array([]) cbar = plt.colorbar(sm, ax=self.ax, label=layer_name) + self._active_colorbars.append(cbar) return self.ax, cbar diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py index 19a75b98b10..167cbbda694 100644 --- a/mesa/visualization/solara_viz.py +++ b/mesa/visualization/solara_viz.py @@ -252,12 +252,32 @@ def SpaceRendererComponent( if renderer.backend == "matplotlib": # Clear the previous plotted data and agents - renderer.canvas.clear() + all_artists = [ + renderer.canvas.lines[:], + renderer.canvas.collections[:], + renderer.canvas.patches[:], + renderer.canvas.images[:], + renderer.canvas.artists[:], + ] + + # Remove duplicate colorbars from the canvas + for cbar in renderer.backend_renderer._active_colorbars: + cbar.remove() + renderer.backend_renderer._active_colorbars.clear() + + # Chain them together into a single iterable + for artist in itertools.chain.from_iterable(all_artists): + artist.remove() + # Draw the space structure if specified if renderer.space_mesh: renderer.draw_structure() + + # Draw agents if specified if renderer.agent_mesh: renderer.draw_agents() + + # Draw property layers if specified if renderer.propertylayer_mesh: renderer.draw_propertylayer() From f23d48615182e5d11ca33a207e444095c8e44786 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 18 Nov 2025 16:59:29 +0530 Subject: [PATCH 05/23] cleanup-2 --- mesa/visualization/solara_viz.py | 5 ----- mesa/visualization/space_renderer.py | 3 --- 2 files changed, 8 deletions(-) diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py index 167cbbda694..ecfc78b3a47 100644 --- a/mesa/visualization/solara_viz.py +++ b/mesa/visualization/solara_viz.py @@ -269,15 +269,10 @@ def SpaceRendererComponent( for artist in itertools.chain.from_iterable(all_artists): artist.remove() - # Draw the space structure if specified if renderer.space_mesh: renderer.draw_structure() - - # Draw agents if specified if renderer.agent_mesh: renderer.draw_agents() - - # Draw property layers if specified if renderer.propertylayer_mesh: renderer.draw_propertylayer() diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index 28d5a8116a1..e8a98fbf883 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -180,7 +180,6 @@ def setup_structure(self, **kwargs) -> SpaceRenderer: Returns: SpaceRenderer: The current instance for method chaining. """ - # Store draw_space_kwargs for internal use self.draw_space_kwargs = kwargs return self @@ -196,7 +195,6 @@ def setup_agents(self, agent_portrayal: Callable, **kwargs) -> SpaceRenderer: Returns: SpaceRenderer: The current instance for method chaining. """ - # Store agent_portrayal and draw_agent_kwargs for internal use self.agent_portrayal = agent_portrayal self.draw_agent_kwargs = kwargs @@ -214,7 +212,6 @@ def setup_propertylayer( Returns: SpaceRenderer: The current instance for method chaining. """ - # Store propertylayer_portrayal for internal use self.propertylayer_portrayal = propertylayer_portrayal return self From f88aff55e03093b5990eb6ff4fb1bb2bf348c17a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:22:08 +0000 Subject: [PATCH 06/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/solara_viz.py | 6 +++--- mesa/visualization/space_renderer.py | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py index ecfc78b3a47..87b33ea2a9f 100644 --- a/mesa/visualization/solara_viz.py +++ b/mesa/visualization/solara_viz.py @@ -385,8 +385,8 @@ def ComponentsView( sorted_page_indices = all_indices # State for current tab and layouts - current_tab_index, set_current_tab_index = solara.use_state(0) # noqa: SH101 - layouts, set_layouts = solara.use_state({}) # noqa: SH101 + current_tab_index, set_current_tab_index = solara.use_state(0) + layouts, set_layouts = solara.use_state({}) # Keep layouts in sync with pages def sync_layouts(): @@ -405,7 +405,7 @@ def sync_layouts(): if new_layouts or len(cleaned_layouts) != len(layouts): set_layouts({**cleaned_layouts, **new_layouts}) - solara.use_effect(sync_layouts, list(pages.keys())) # noqa: SH101 + solara.use_effect(sync_layouts, list(pages.keys())) # Tab Navigation with solara.v.Tabs(v_model=current_tab_index, on_v_model=set_current_tab_index): diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index e8a98fbf883..f61d07d8718 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -65,7 +65,7 @@ def __init__( self.space = getattr(model, "grid", getattr(model, "space", None)) self.space_drawer = self._get_space_drawer() - + self.space_mesh = None self.agent_mesh = None self.propertylayer_mesh = None @@ -169,7 +169,7 @@ def _map_coordinates(self, arguments): mapped_arguments["loc"] = pos[x] return mapped_arguments - + def setup_structure(self, **kwargs) -> SpaceRenderer: """Setup the space structure without drawing. @@ -199,7 +199,7 @@ def setup_agents(self, agent_portrayal: Callable, **kwargs) -> SpaceRenderer: self.draw_agent_kwargs = kwargs return self - + def setup_propertylayer( self, propertylayer_portrayal: Callable | dict ) -> SpaceRenderer: @@ -303,7 +303,9 @@ def style_callable(layer_object): # Convert portrayal to callable if needed if isinstance(self.propertylayer_portrayal, dict): - self.propertylayer_portrayal = _dict_to_callable(self.propertylayer_portrayal) + self.propertylayer_portrayal = _dict_to_callable( + self.propertylayer_portrayal + ) number_of_propertylayers = sum( [1 for layer in property_layers if layer != "empty"] From 09fe545a727419c8b66d271b31be104589045c3a Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Thu, 20 Nov 2025 19:05:02 +0530 Subject: [PATCH 07/23] fix chart_props in network draw altair --- mesa/visualization/space_drawers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mesa/visualization/space_drawers.py b/mesa/visualization/space_drawers.py index f0b5c891f23..e17a420f2b5 100644 --- a/mesa/visualization/space_drawers.py +++ b/mesa/visualization/space_drawers.py @@ -477,11 +477,11 @@ def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): node_mark_kwargs.update(draw_chart_kwargs.pop("node_kwargs", {})) edge_mark_kwargs.update(draw_chart_kwargs.pop("edge_kwargs", {})) - draw_chart_kwargs = { + chart_props = { "width": chart_width, "height": chart_height, } - draw_chart_kwargs.update(draw_chart_kwargs) + chart_props.update(draw_chart_kwargs) edge_plot = ( alt.Chart(edge_positions) @@ -510,8 +510,8 @@ def draw_altair(self, chart_width=450, chart_height=350, **draw_chart_kwargs): chart = edge_plot + node_plot - if draw_chart_kwargs: - chart = chart.properties(**draw_chart_kwargs) + if chart_props: + chart = chart.properties(**chart_props) return chart From edb325147d508fce7d7c1e0f82c867e8838d640b Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Thu, 20 Nov 2025 19:07:28 +0530 Subject: [PATCH 08/23] remove older method calls from canvas --- mesa/visualization/space_renderer.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index f61d07d8718..090c3fd6627 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -352,13 +352,11 @@ def canvas(self): prop_base, prop_cbar = self.propertylayer_mesh or (None, None) if self.space_mesh: - structure = self.draw_structure(**self.space_kwargs) + structure = self.draw_structure() if self.agent_mesh: - agents = self.draw_agents(self.agent_portrayal, **self.agent_kwargs) + agents = self.draw_agents() if self.propertylayer_mesh: - prop_base, prop_cbar = self.draw_propertylayer( - self.propertylayer_portrayal - ) + prop_base, prop_cbar = self.draw_propertylayer() spatial_charts_list = [ chart for chart in [structure, prop_base, agents] if chart From 077dee287786cb3fc4f12a418c007c9b8edec995 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 16:56:07 +0530 Subject: [PATCH 09/23] ensure backwards compatability --- mesa/visualization/space_renderer.py | 75 +++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index 090c3fd6627..34678993452 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -216,21 +216,56 @@ def setup_propertylayer( return self - def draw_structure(self): + def draw_structure(self, **kwargs): """Draw the space structure. + Args: + **kwargs: (Deprecated) Additional keyword arguments for drawing. + Use setup_structure() instead. + Returns: The visual representation of the space structure. """ + if kwargs: + warnings.warn( + "Passing kwargs to draw_structure() is deprecated. " + "Use setup_structure(**kwargs) before calling draw_structure().", + DeprecationWarning, + stacklevel=2, + ) + self.draw_space_kwargs.update(kwargs) + self.space_mesh = self.backend_renderer.draw_structure(**self.draw_space_kwargs) return self.space_mesh - def draw_agents(self): + def draw_agents(self, agent_portrayal=None, **kwargs): """Draw agents on the space. + Args: + agent_portrayal: (Deprecated) Function that takes an agent and returns AgentPortrayalStyle. + Use setup_agents() instead. + **kwargs: (Deprecated) Additional keyword arguments for drawing. + Returns: The visual representation of the agents. """ + if agent_portrayal is not None: + warnings.warn( + "Passing agent_portrayal to draw_agents() is deprecated. " + "Use setup_agents(agent_portrayal, **kwargs) before calling draw_agents().", + DeprecationWarning, + stacklevel=2, + ) + self.agent_portrayal = agent_portrayal + if kwargs: + warnings.warn( + "Passing kwargs to draw_agents() is deprecated. " + "Use setup_agents(**kwargs) before calling draw_agents().", + DeprecationWarning, + stacklevel=2, + ) + self.draw_agent_kwargs.update(kwargs) + # Prepare data for agent plotting arguments = self.backend_renderer.collect_agent_data( self.space, self.agent_portrayal, default_size=self.space_drawer.s_default @@ -242,15 +277,28 @@ def draw_agents(self): ) return self.agent_mesh - def draw_propertylayer(self): + def draw_propertylayer(self, propertylayer_portrayal=None): """Draw property layers on the space. + Args: + propertylayer_portrayal: (Deprecated) Function that takes a property layer and returns PropertyLayerStyle. + Use setup_propertylayer() instead. + Returns: The visual representation of the property layers. Raises: Exception: If no property layers are found on the space. """ + if propertylayer_portrayal is not None: + warnings.warn( + "Passing propertylayer_portrayal to draw_propertylayer() is deprecated. " + "Use setup_propertylayer(propertylayer_portrayal) before calling draw_propertylayer().", + DeprecationWarning, + stacklevel=2, + ) + self.propertylayer_portrayal = propertylayer_portrayal + # Import here to avoid circular imports from mesa.visualization.components import PropertyLayerStyle # noqa: PLC0415 @@ -318,12 +366,27 @@ def style_callable(layer_object): ) return self.propertylayer_mesh - def render(self): + def render(self, agent_portrayal=None, propertylayer_portrayal=None, **kwargs): """Render the complete space with structure, agents, and property layers. - It is an all-in-one method that draws everything required therefore eliminates - the need of calling each method separately. + Args: + agent_portrayal: (Deprecated) Function for agent portrayal. Use setup_agents() instead. + propertylayer_portrayal: (Deprecated) Function for property layer portrayal. Use setup_propertylayer() instead. + **kwargs: (Deprecated) Additional keyword arguments. """ + if agent_portrayal is not None or propertylayer_portrayal is not None or kwargs: + warnings.warn( + "Passing parameters to render() is deprecated. " + "Use setup_structure(), setup_agents(), and setup_propertylayer() before calling render().", + DeprecationWarning, + stacklevel=2, + ) + if agent_portrayal is not None: + self.agent_portrayal = agent_portrayal + if propertylayer_portrayal is not None: + self.propertylayer_portrayal = propertylayer_portrayal + self.draw_space_kwargs.update(kwargs) + if self.space_mesh is None: self.draw_structure() if self.agent_mesh is None and self.agent_portrayal is not None: From b8acbb51d926387bce0e9f754064bea0fa6e9916 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 16:56:33 +0530 Subject: [PATCH 10/23] update tests --- tests/test_solara_viz_updated.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_solara_viz_updated.py b/tests/test_solara_viz_updated.py index 03db138fa2d..ab07a7d703a 100644 --- a/tests/test_solara_viz_updated.py +++ b/tests/test_solara_viz_updated.py @@ -165,7 +165,7 @@ def agent_portrayal(agent): ) mock_draw_space.assert_called_with(renderer) - mock_draw_agents.assert_called_with(renderer, agent_portrayal) + mock_draw_agents.assert_called_with(renderer) # should not call this method if portrayal is None mock_draw_properties.assert_not_called() @@ -201,8 +201,8 @@ def agent_portrayal(agent): ) mock_draw_space.assert_called_with(renderer) - mock_draw_agents.assert_called_with(renderer, agent_portrayal) - mock_draw_properties.assert_called_with(renderer, propertylayer_portrayal) + mock_draw_agents.assert_called_with(renderer) + mock_draw_properties.assert_called_with(renderer) mock_draw_space.reset_mock() mock_draw_agents.reset_mock() From def6a45000c5c5164eaf097d8ef33af261ce72e2 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:01:28 +0530 Subject: [PATCH 11/23] update virus_on_network --- mesa/examples/basic/virus_on_network/app.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mesa/examples/basic/virus_on_network/app.py b/mesa/examples/basic/virus_on_network/app.py index b9de98eae8a..5d17398245d 100644 --- a/mesa/examples/basic/virus_on_network/app.py +++ b/mesa/examples/basic/virus_on_network/app.py @@ -109,12 +109,16 @@ def post_process_lineplot(chart): model1 = VirusOnNetwork() -renderer = SpaceRenderer(model1, backend="altair") -renderer.draw_structure( - node_kwargs={"color": "black", "filled": False, "strokeWidth": 5}, - edge_kwargs={"strokeDash": [6, 1]}, -) # Do this to draw the underlying network and customize it -renderer.draw_agents(agent_portrayal) +renderer = ( + SpaceRenderer(model1, backend="altair") + .setup_structure( # Do this to draw the underlying network and customize it + node_kwargs={"color": "black", "filled": False, "strokeWidth": 5}, + edge_kwargs={"strokeDash": [6, 1]}, + ) + .setup_agents(agent_portrayal) +) + +renderer.render() # Plot components can also be in altair and support post_process StatePlot = make_plot_component( From de29623646dd0468f6e2c7ae26087a460f1b553e Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:02:32 +0530 Subject: [PATCH 12/23] update schelling example --- mesa/examples/basic/schelling/app.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mesa/examples/basic/schelling/app.py b/mesa/examples/basic/schelling/app.py index 32395a66d5e..cebf2a64f1e 100644 --- a/mesa/examples/basic/schelling/app.py +++ b/mesa/examples/basic/schelling/app.py @@ -73,12 +73,11 @@ def agent_portrayal(agent): # Note: Models with images as markers are very performance intensive. model1 = Schelling() -renderer = SpaceRenderer(model1, backend="matplotlib") +renderer = SpaceRenderer(model1, backend="matplotlib").setup_agents(agent_portrayal) # Here we use renderer.render() to render the agents and grid in one go. # This function always renders the grid and then renders the agents or -# property layers on top of it if specified. It also supports passing the -# post_process function to fine-tune the plot after rendering in itself. -renderer.render(agent_portrayal=agent_portrayal) +# property layers on top of it if specified. +renderer.render() HappyPlot = make_plot_component({"happy": "tab:green"}) From 2faa07813396fd6b5d4c86030b5943369ea0e08b Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:03:33 +0530 Subject: [PATCH 13/23] update game_of_life example --- mesa/examples/basic/conways_game_of_life/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesa/examples/basic/conways_game_of_life/app.py b/mesa/examples/basic/conways_game_of_life/app.py index 618b72dbaf7..ac4f1134b0a 100644 --- a/mesa/examples/basic/conways_game_of_life/app.py +++ b/mesa/examples/basic/conways_game_of_life/app.py @@ -55,10 +55,10 @@ def post_process(ax): # Create initial model instance model1 = ConwaysGameOfLife() -renderer = SpaceRenderer(model1, backend="matplotlib") +renderer = SpaceRenderer(model1, backend="matplotlib").setup_agents(agent_portrayal) # In this case the renderer only draws the agents because we just want to observe # the state of the agents, not the structure of the grid. -renderer.draw_agents(agent_portrayal=agent_portrayal) +renderer.draw_agents() renderer.post_process = post_process # Create the SolaraViz page. This will automatically create a server and display the From 70d447e6018657148887c59e5763800b7bdd05ae Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:05:34 +0530 Subject: [PATCH 14/23] update boltzman_wealth_model example --- mesa/examples/basic/boltzmann_wealth_model/app.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mesa/examples/basic/boltzmann_wealth_model/app.py b/mesa/examples/basic/boltzmann_wealth_model/app.py index 464708536ca..759488f39d1 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/app.py +++ b/mesa/examples/basic/boltzmann_wealth_model/app.py @@ -60,10 +60,14 @@ def post_process(chart): # It builds the visualization in layers, first drawing the grid structure, # and then drawing the agents on top. It uses a specified backend # (like "altair" or "matplotlib") for creating the plots. -renderer = SpaceRenderer(model, backend="altair") + # Can customize the grid appearance. -renderer.draw_structure(grid_color="black", grid_dash=[6, 2], grid_opacity=0.3) -renderer.draw_agents(agent_portrayal=agent_portrayal, cmap="viridis", vmin=0, vmax=10) +renderer = ( + SpaceRenderer(model, backend="altair") + .setup_structure(grid_color="black", grid_dash=[6, 2], grid_opacity=0.3) + .setup_agents(agent_portrayal, cmap="viridis", vmin=0, vmax=10) +) +renderer.render() # The post_process function is used to modify the Altair chart after it has been created. # It can be used to add legends, colorbars, or other visual elements. From 7ba7a9111ac52909194363781f93c9e153bdb3a8 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:06:20 +0530 Subject: [PATCH 15/23] update bold_flockers example --- mesa/examples/basic/boid_flockers/app.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mesa/examples/basic/boid_flockers/app.py b/mesa/examples/basic/boid_flockers/app.py index c008e1bce6e..392543f1b52 100644 --- a/mesa/examples/basic/boid_flockers/app.py +++ b/mesa/examples/basic/boid_flockers/app.py @@ -70,10 +70,14 @@ def boid_draw(agent): model = BoidFlockers() # Quickest way to visualize grid along with agents or property layers. -renderer = SpaceRenderer( - model, - backend="matplotlib", -).render(agent_portrayal=boid_draw) +renderer = ( + SpaceRenderer( + model, + backend="matplotlib", + ) + .setup_agents(boid_draw) + .render() +) page = SolaraViz( model, From 953f0ca712caa46a85ca7fe4a679d128870add8b Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:17:22 +0530 Subject: [PATCH 16/23] cleanup and update wolf-sheep example --- mesa/examples/advanced/wolf_sheep/app.py | 4 ++-- mesa/examples/basic/boltzmann_wealth_model/app.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index 8b33b0855cd..e17080e53e9 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -84,9 +84,9 @@ def post_process_lines(ax): renderer = SpaceRenderer( model, backend="matplotlib", -) -renderer.draw_agents(wolf_sheep_portrayal) +).setup_agents(wolf_sheep_portrayal) renderer.post_process = post_process_space +renderer.draw_agents() page = SolaraViz( model, diff --git a/mesa/examples/basic/boltzmann_wealth_model/app.py b/mesa/examples/basic/boltzmann_wealth_model/app.py index 759488f39d1..e3eb0c5381f 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/app.py +++ b/mesa/examples/basic/boltzmann_wealth_model/app.py @@ -61,10 +61,11 @@ def post_process(chart): # and then drawing the agents on top. It uses a specified backend # (like "altair" or "matplotlib") for creating the plots. -# Can customize the grid appearance. renderer = ( SpaceRenderer(model, backend="altair") - .setup_structure(grid_color="black", grid_dash=[6, 2], grid_opacity=0.3) + .setup_structure( # To customize the grid appearance. + grid_color="black", grid_dash=[6, 2], grid_opacity=0.3 + ) .setup_agents(agent_portrayal, cmap="viridis", vmin=0, vmax=10) ) renderer.render() From b23868c11678ac99fe06c44ee7e7ce84ead07435 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:19:46 +0530 Subject: [PATCH 17/23] update sugarscape example --- mesa/examples/advanced/sugarscape_g1mt/app.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py index 0fb767e1e4e..161b40525d7 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/app.py +++ b/mesa/examples/advanced/sugarscape_g1mt/app.py @@ -57,11 +57,15 @@ def post_process(chart): # Here, the renderer uses the Altair backend, while the plot components # use the Matplotlib backend. # Both can be mixed and matched to enhance the visuals of your model. -renderer = SpaceRenderer(model, backend="altair").render( - agent_portrayal=agent_portrayal, - propertylayer_portrayal=propertylayer_portrayal, - post_process=post_process, +renderer = ( + SpaceRenderer(model, backend="altair") + .setup_agents(agent_portrayal) + .setup_propertylayer(propertylayer_portrayal) ) +renderer.draw_agents() +renderer.draw_propertylayer() + +renderer.post_process = post_process # Note: It is advised to switch the pages after pausing the model # on the Solara dashboard. From b903326d863a2573618f61e09a3c03c879810ea5 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:21:18 +0530 Subject: [PATCH 18/23] update pd_grid example --- mesa/examples/advanced/pd_grid/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mesa/examples/advanced/pd_grid/app.py b/mesa/examples/advanced/pd_grid/app.py index 580b582af47..efc4f409760 100644 --- a/mesa/examples/advanced/pd_grid/app.py +++ b/mesa/examples/advanced/pd_grid/app.py @@ -45,7 +45,11 @@ def pd_agent_portrayal(agent): # Initialize model initial_model = PdGrid() # Create grid and agent visualization component using Altair -renderer = SpaceRenderer(initial_model, backend="altair").render(pd_agent_portrayal) +renderer = ( + SpaceRenderer(initial_model, backend="altair") + .setup_agents(pd_agent_portrayal) + .render() +) # Create visualization with all components page = SolaraViz( From 7ae4afc9bc965bb3747f9dafc1725410307b288d Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:23:00 +0530 Subject: [PATCH 19/23] update civil_voilence example --- mesa/examples/advanced/epstein_civil_violence/app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mesa/examples/advanced/epstein_civil_violence/app.py b/mesa/examples/advanced/epstein_civil_violence/app.py index 0eba101af98..77b7184660a 100644 --- a/mesa/examples/advanced/epstein_civil_violence/app.py +++ b/mesa/examples/advanced/epstein_civil_violence/app.py @@ -64,8 +64,10 @@ def post_process(ax): ) epstein_model = EpsteinCivilViolence() -renderer = SpaceRenderer(epstein_model, backend="matplotlib") -renderer.draw_agents(citizen_cop_portrayal) +renderer = SpaceRenderer(epstein_model, backend="matplotlib").setup_agents( + citizen_cop_portrayal +) +renderer.draw_agents() renderer.post_process = post_process page = SolaraViz( From 8a222503b76754105f2943855c16c492b891e32f Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:38:09 +0530 Subject: [PATCH 20/23] update tests inline with the new-API --- tests/test_solara_viz_updated.py | 18 +++++++++++++----- tests/test_space_renderer.py | 11 ++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/test_solara_viz_updated.py b/tests/test_solara_viz_updated.py index ab07a7d703a..07aad6af2de 100644 --- a/tests/test_solara_viz_updated.py +++ b/tests/test_solara_viz_updated.py @@ -147,8 +147,12 @@ def agent_portrayal(agent): propertylayer_portrayal = None - renderer = SpaceRenderer(model, backend="matplotlib") - renderer.render(agent_portrayal, propertylayer_portrayal) + renderer = ( + SpaceRenderer(model, backend="matplotlib") + .setup_agents(agent_portrayal) + .setup_propertylayer(propertylayer_portrayal) + .render() + ) # component must be rendered for code to run solara.render( @@ -192,8 +196,12 @@ def agent_portrayal(agent): } solara.render(SolaraViz(model, renderer, components=[])) - renderer = SpaceRenderer(model, backend="altair") - renderer.render(agent_portrayal, propertylayer_portrayal) + renderer = ( + SpaceRenderer(model, backend="altair") + .setup_agents(agent_portrayal) + .setup_propertylayer(propertylayer_portrayal) + .render() + ) assert renderer.backend == "altair" assert isinstance( @@ -210,7 +218,7 @@ def agent_portrayal(agent): solara.render(SolaraViz(model)) - # noting is drawn if renderer is not passed + # nothing is drawn if renderer is not passed assert mock_draw_space.call_count == 0 assert mock_draw_agents.call_count == 0 assert mock_draw_properties.call_count == 0 diff --git a/tests/test_space_renderer.py b/tests/test_space_renderer.py index d74c6468bd8..5072b35143b 100644 --- a/tests/test_space_renderer.py +++ b/tests/test_space_renderer.py @@ -117,10 +117,9 @@ def test_render_calls(): sr.draw_agents = MagicMock() sr.draw_propertylayer = MagicMock() - sr.render( - agent_portrayal=lambda _: {}, - propertylayer_portrayal=lambda _: PropertyLayerStyle(color="red"), - ) + sr.setup_agents(agent_portrayal=lambda _: {}).setup_propertylayer( + propertylayer_portrayal=lambda _: PropertyLayerStyle(color="red") + ).render() sr.draw_structure.assert_called_once() sr.draw_agents.assert_called_once() @@ -139,7 +138,9 @@ def test_no_property_layers(): Exception, match=re.escape("No property layers were found on the space.") ), ): - sr.draw_propertylayer(lambda _: PropertyLayerStyle(color="red")) + sr.setup_propertylayer( + lambda _: PropertyLayerStyle(color="red") + ).draw_propertylayer() def test_post_process(): From 54e32dfaf6741c91362f78b086d50b3dda0a4176 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Sat, 22 Nov 2025 17:57:19 +0530 Subject: [PATCH 21/23] nitpick improvements --- mesa/examples/advanced/epstein_civil_violence/app.py | 1 + mesa/examples/advanced/sugarscape_g1mt/app.py | 1 + mesa/visualization/space_renderer.py | 3 +++ 3 files changed, 5 insertions(+) diff --git a/mesa/examples/advanced/epstein_civil_violence/app.py b/mesa/examples/advanced/epstein_civil_violence/app.py index 77b7184660a..376a4eec643 100644 --- a/mesa/examples/advanced/epstein_civil_violence/app.py +++ b/mesa/examples/advanced/epstein_civil_violence/app.py @@ -67,6 +67,7 @@ def post_process(ax): renderer = SpaceRenderer(epstein_model, backend="matplotlib").setup_agents( citizen_cop_portrayal ) +# Specifically, avoid drawing the grid to hide the grid lines. renderer.draw_agents() renderer.post_process = post_process diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py index 161b40525d7..33eed8b3474 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/app.py +++ b/mesa/examples/advanced/sugarscape_g1mt/app.py @@ -62,6 +62,7 @@ def post_process(chart): .setup_agents(agent_portrayal) .setup_propertylayer(propertylayer_portrayal) ) +# Specifically, avoid drawing the grid to hide the grid lines. renderer.draw_agents() renderer.draw_propertylayer() diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index 34678993452..54f7b089cc3 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -181,6 +181,7 @@ def setup_structure(self, **kwargs) -> SpaceRenderer: SpaceRenderer: The current instance for method chaining. """ self.draw_space_kwargs = kwargs + self.space_mesh = None return self @@ -197,6 +198,7 @@ def setup_agents(self, agent_portrayal: Callable, **kwargs) -> SpaceRenderer: """ self.agent_portrayal = agent_portrayal self.draw_agent_kwargs = kwargs + self.agent_mesh = None return self @@ -213,6 +215,7 @@ def setup_propertylayer( SpaceRenderer: The current instance for method chaining. """ self.propertylayer_portrayal = propertylayer_portrayal + self.propertylayer_mesh = None return self From d63ca10f479c6900652c38165c2d9a4e12841ddd Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 25 Nov 2025 23:30:28 +0530 Subject: [PATCH 22/23] change deprecation to pending deprecation warnings --- mesa/visualization/space_renderer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mesa/visualization/space_renderer.py b/mesa/visualization/space_renderer.py index 54f7b089cc3..c60bb9c2214 100644 --- a/mesa/visualization/space_renderer.py +++ b/mesa/visualization/space_renderer.py @@ -233,7 +233,7 @@ def draw_structure(self, **kwargs): warnings.warn( "Passing kwargs to draw_structure() is deprecated. " "Use setup_structure(**kwargs) before calling draw_structure().", - DeprecationWarning, + PendingDeprecationWarning, stacklevel=2, ) self.draw_space_kwargs.update(kwargs) @@ -256,7 +256,7 @@ def draw_agents(self, agent_portrayal=None, **kwargs): warnings.warn( "Passing agent_portrayal to draw_agents() is deprecated. " "Use setup_agents(agent_portrayal, **kwargs) before calling draw_agents().", - DeprecationWarning, + PendingDeprecationWarning, stacklevel=2, ) self.agent_portrayal = agent_portrayal @@ -264,7 +264,7 @@ def draw_agents(self, agent_portrayal=None, **kwargs): warnings.warn( "Passing kwargs to draw_agents() is deprecated. " "Use setup_agents(**kwargs) before calling draw_agents().", - DeprecationWarning, + PendingDeprecationWarning, stacklevel=2, ) self.draw_agent_kwargs.update(kwargs) @@ -297,7 +297,7 @@ def draw_propertylayer(self, propertylayer_portrayal=None): warnings.warn( "Passing propertylayer_portrayal to draw_propertylayer() is deprecated. " "Use setup_propertylayer(propertylayer_portrayal) before calling draw_propertylayer().", - DeprecationWarning, + PendingDeprecationWarning, stacklevel=2, ) self.propertylayer_portrayal = propertylayer_portrayal @@ -381,7 +381,7 @@ def render(self, agent_portrayal=None, propertylayer_portrayal=None, **kwargs): warnings.warn( "Passing parameters to render() is deprecated. " "Use setup_structure(), setup_agents(), and setup_propertylayer() before calling render().", - DeprecationWarning, + PendingDeprecationWarning, stacklevel=2, ) if agent_portrayal is not None: From c2e603299dd01dd397dee185e421f66842f8c853 Mon Sep 17 00:00:00 2001 From: Sahil-Chhoker Date: Tue, 25 Nov 2025 23:36:40 +0530 Subject: [PATCH 23/23] empty commit