|
| 1 | +"""Portrayal Components Module. |
| 2 | +
|
| 3 | +This module defines data structures for styling visual elements in Mesa agent-based model visualizations. |
| 4 | +It provides user-facing classes to specify how agents and property layers should appear in the rendered space. |
| 5 | +
|
| 6 | +Classes: |
| 7 | +- AgentPortrayalStyle: Controls the appearance of individual agents (e.g., color, shape, size, etc.). |
| 8 | +- PropertyLayerStyle: Controls the appearance of background property layers (e.g., color gradients or uniform fills). |
| 9 | +
|
| 10 | +These components are designed to be passed into Mesa visualizations to customize and standardize how data is presented. |
| 11 | +""" |
| 12 | + |
| 13 | +from dataclasses import dataclass |
| 14 | +from typing import Any |
| 15 | + |
| 16 | + |
| 17 | +@dataclass |
| 18 | +class AgentPortrayalStyle: |
| 19 | + """Represents the visual styling options for an agent in a visualization. |
| 20 | +
|
| 21 | + User facing component to control how agents are drawn. |
| 22 | + Allows specifying properties like color, size, |
| 23 | + marker shape, position, and other plot attributes. |
| 24 | + """ |
| 25 | + |
| 26 | + x: float | None = None |
| 27 | + y: float | None = None |
| 28 | + color: str | tuple | None = "tab:blue" |
| 29 | + marker: str | None = "o" |
| 30 | + size: int | float | None = 50 |
| 31 | + zorder: int | None = 1 |
| 32 | + alpha: float | None = 1.0 |
| 33 | + edgecolors: str | tuple | None = None |
| 34 | + linewidths: float | int | None = 1.0 |
| 35 | + |
| 36 | + def update(self, *updates_fields: tuple[str, Any]): |
| 37 | + """Updates attributes from variable (field_name, new_value) tuple arguments. |
| 38 | +
|
| 39 | + Example: |
| 40 | + >>> def agent_portrayal(agent): |
| 41 | + >>> primary_style = AgentPortrayalStyle(color="blue", marker="^", size=10, x=agent.pos[0], y=agent.pos[1]) |
| 42 | + >>> if agent.type == 1: |
| 43 | + >>> primary_style.update(("color", "red"), ("size", 30)) |
| 44 | + >>> return primary_style |
| 45 | + """ |
| 46 | + for field_to_change, field_to_change_to in updates_fields: |
| 47 | + if hasattr(self, field_to_change): |
| 48 | + setattr(self, field_to_change, field_to_change_to) |
| 49 | + else: |
| 50 | + raise AttributeError( |
| 51 | + f"'{type(self).__name__}' object has no attribute '{field_to_change}'" |
| 52 | + ) |
| 53 | + |
| 54 | + |
| 55 | +@dataclass |
| 56 | +class PropertyLayerStyle: |
| 57 | + """Represents the visual styling options for a property layer in a visualization. |
| 58 | +
|
| 59 | + User facing component to control how property layers are drawn. |
| 60 | + Allows specifying properties like colormap, single color, value limits, |
| 61 | + and colorbar visibility. |
| 62 | +
|
| 63 | + Note: You can specify either a 'colormap' (for varying data) or a single |
| 64 | + 'color' (for a uniform layer appearance), but not both simultaneously. |
| 65 | + """ |
| 66 | + |
| 67 | + colormap: str | None = None |
| 68 | + color: str | None = None |
| 69 | + alpha: float = 0.8 |
| 70 | + colorbar: bool = True |
| 71 | + vmin: float | None = None |
| 72 | + vmax: float | None = None |
| 73 | + |
| 74 | + def __post_init__(self): |
| 75 | + """Validate that color and colormap are not simultaneously specified.""" |
| 76 | + if self.color is not None and self.colormap is not None: |
| 77 | + raise ValueError("Specify either 'color' or 'colormap', not both.") |
| 78 | + if self.color is None and self.colormap is None: |
| 79 | + raise ValueError("Specify one of 'color' or 'colormap'") |
0 commit comments