Source code for modelly.viz.mpl.network

"""Draw network spaces with their agents."""

from collections.abc import Callable

import networkx as nx
from matplotlib import pyplot as plt
from matplotlib.axes import Axes

import modelly
from modelly.viz.mpl.agent_data import collect_agent_data
from modelly.viz.mpl.grids import MARKER_SPAN_POINTS, PADDING_DIVISOR
from modelly.viz.mpl.scatter import _scatter

Network = modelly.space.Network

NETWORK_NODE_SIZE = 10


[docs] def draw_network( space: Network, agent_portrayal: Callable, ax: Axes | None = None, draw_grid: bool = True, **kwargs, ): """Visualize a network space. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a AgentPortrayalStyle ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid kwargs: additional keyword arguments passed to ax.scatter Returns: Returns the Axes object with the plot drawn onto it. ``agent_portrayal`` is called with an agent and should return a AgentPortrayalStyle. Valid fields in this object are "color", "size", "marker", "zorder", alpha, linewidths, and edgecolors. Other field are ignored and will result in a user warning. """ if ax is None: _, ax = plt.subplots() pos = _node_positions(space) bounds = _node_bounds(pos) xmin, xmax, ymin, ymax = bounds width = xmax - xmin height = ymax - ymin # gather agent data s_default = (MARKER_SPAN_POINTS / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, default_size=s_default) arguments["loc"] = arguments["loc"].astype(float) _set_network_limits(ax, bounds) if draw_grid: _draw_network_grid(ax, space, pos) # plot the agents _scatter(ax, arguments, **kwargs) return ax
def _node_positions(space): """Fetch the node positions natively from the Model Cells.""" pos = {} for node_id, cell in space._cells.items(): pos_val = getattr(cell, "position", getattr(cell, "_position", None)) if pos_val is not None: pos[node_id] = pos_val return pos def _node_bounds(pos): """Return (xmin, xmax, ymin, ymax) of the node positions.""" x, y = list(zip(*pos.values())) if pos else ([0], [0]) return min(x), max(x), min(y), max(y) def _set_network_limits(ax, bounds): """Hide the axis and set padded limits around the nodes.""" xmin, xmax, ymin, ymax = bounds x_padding = (xmax - xmin) / PADDING_DIVISOR y_padding = (ymax - ymin) / PADDING_DIVISOR ax.set_axis_off() ax.set_xlim(xmin=xmin - x_padding, xmax=xmax + x_padding) ax.set_ylim(ymin=ymin - y_padding, ymax=ymax + y_padding) def _draw_network_grid(ax, space, pos): """Draw the underlying grid (edges and empty nodes) below the agents.""" nodes = nx.draw_networkx_nodes( space.G, pos, ax=ax, alpha=0.5, node_size=NETWORK_NODE_SIZE, node_color="gray" ) edges = nx.draw_networkx_edges(space.G, pos, ax=ax, alpha=0.5, style="--") if nodes: nodes.set_zorder(0) if edges: edges.set_zorder(0)