Mesa 3.0.0__py3-none-any.whl → 3.0.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Mesa might be problematic. Click here for more details.

Files changed (104) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +114 -406
  3. mesa/batchrunner.py +27 -54
  4. mesa/cookiecutter-mesa/cookiecutter.json +8 -0
  5. mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
  6. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
  7. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  8. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
  9. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
  10. mesa/datacollection.py +29 -140
  11. mesa/experimental/__init__.py +1 -11
  12. mesa/experimental/cell_space/__init__.py +1 -16
  13. mesa/experimental/cell_space/cell.py +23 -93
  14. mesa/experimental/cell_space/cell_agent.py +21 -117
  15. mesa/experimental/cell_space/cell_collection.py +17 -54
  16. mesa/experimental/cell_space/discrete_space.py +8 -92
  17. mesa/experimental/cell_space/grid.py +8 -32
  18. mesa/experimental/cell_space/network.py +7 -12
  19. mesa/experimental/devs/__init__.py +0 -2
  20. mesa/experimental/devs/eventlist.py +14 -52
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
  22. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  23. mesa/experimental/devs/simulator.py +15 -55
  24. mesa/main.py +63 -0
  25. mesa/model.py +83 -211
  26. mesa/space.py +149 -215
  27. mesa/time.py +77 -62
  28. mesa/{experimental → visualization}/UserParam.py +6 -17
  29. mesa/visualization/__init__.py +2 -25
  30. mesa/{experimental → visualization}/components/altair.py +0 -10
  31. mesa/visualization/components/matplotlib.py +134 -0
  32. mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
  33. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
  34. mesa-3.0.0a0.dist-info/RECORD +38 -0
  35. mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
  36. mesa/examples/README.md +0 -37
  37. mesa/examples/__init__.py +0 -21
  38. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
  39. mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
  40. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  41. mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
  42. mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
  43. mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
  44. mesa/examples/advanced/pd_grid/Readme.md +0 -43
  45. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  46. mesa/examples/advanced/pd_grid/agents.py +0 -50
  47. mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
  48. mesa/examples/advanced/pd_grid/app.py +0 -54
  49. mesa/examples/advanced/pd_grid/model.py +0 -71
  50. mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
  51. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  52. mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
  53. mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
  54. mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
  55. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
  56. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  57. mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
  58. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  59. mesa/examples/advanced/wolf_sheep/agents.py +0 -102
  60. mesa/examples/advanced/wolf_sheep/app.py +0 -84
  61. mesa/examples/advanced/wolf_sheep/model.py +0 -137
  62. mesa/examples/basic/__init__.py +0 -0
  63. mesa/examples/basic/boid_flockers/Readme.md +0 -22
  64. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  65. mesa/examples/basic/boid_flockers/agents.py +0 -71
  66. mesa/examples/basic/boid_flockers/app.py +0 -58
  67. mesa/examples/basic/boid_flockers/model.py +0 -69
  68. mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
  69. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  70. mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
  71. mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
  72. mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
  73. mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
  74. mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
  75. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  76. mesa/examples/basic/conways_game_of_life/agents.py +0 -47
  77. mesa/examples/basic/conways_game_of_life/app.py +0 -51
  78. mesa/examples/basic/conways_game_of_life/model.py +0 -31
  79. mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
  80. mesa/examples/basic/schelling/Readme.md +0 -40
  81. mesa/examples/basic/schelling/__init__.py +0 -0
  82. mesa/examples/basic/schelling/agents.py +0 -26
  83. mesa/examples/basic/schelling/analysis.ipynb +0 -205
  84. mesa/examples/basic/schelling/app.py +0 -42
  85. mesa/examples/basic/schelling/model.py +0 -59
  86. mesa/examples/basic/virus_on_network/Readme.md +0 -61
  87. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  88. mesa/examples/basic/virus_on_network/agents.py +0 -69
  89. mesa/examples/basic/virus_on_network/app.py +0 -114
  90. mesa/examples/basic/virus_on_network/model.py +0 -96
  91. mesa/experimental/cell_space/voronoi.py +0 -257
  92. mesa/experimental/components/matplotlib.py +0 -242
  93. mesa/visualization/components/__init__.py +0 -83
  94. mesa/visualization/components/altair_components.py +0 -188
  95. mesa/visualization/components/matplotlib_components.py +0 -175
  96. mesa/visualization/mpl_space_drawing.py +0 -593
  97. mesa/visualization/solara_viz.py +0 -458
  98. mesa/visualization/user_param.py +0 -69
  99. mesa/visualization/utils.py +0 -9
  100. mesa-3.0.0.dist-info/RECORD +0 -95
  101. mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
  102. /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
  103. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
  104. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
@@ -1,257 +0,0 @@
1
- """Support for Voronoi meshed grids."""
2
-
3
- from collections.abc import Sequence
4
- from itertools import combinations
5
- from random import Random
6
-
7
- import numpy as np
8
-
9
- from mesa.experimental.cell_space.cell import Cell
10
- from mesa.experimental.cell_space.discrete_space import DiscreteSpace
11
-
12
-
13
- class Delaunay:
14
- """Class to compute a Delaunay triangulation in 2D.
15
-
16
- ref: http://github.com/jmespadero/pyDelaunay2D
17
- """
18
-
19
- def __init__(self, center: tuple = (0, 0), radius: int = 9999) -> None:
20
- """Init and create a new frame to contain the triangulation.
21
-
22
- Args:
23
- center: Optional position for the center of the frame. Default (0,0)
24
- radius: Optional distance from corners to the center.
25
- """
26
- center = np.asarray(center)
27
- # Create coordinates for the corners of the frame
28
- self.coords = [
29
- center + radius * np.array((-1, -1)),
30
- center + radius * np.array((+1, -1)),
31
- center + radius * np.array((+1, +1)),
32
- center + radius * np.array((-1, +1)),
33
- ]
34
-
35
- # Create two dicts to store triangle neighbours and circumcircles.
36
- self.triangles = {}
37
- self.circles = {}
38
-
39
- # Create two CCW triangles for the frame
40
- triangle1 = (0, 1, 3)
41
- triangle2 = (2, 3, 1)
42
- self.triangles[triangle1] = [triangle2, None, None]
43
- self.triangles[triangle2] = [triangle1, None, None]
44
-
45
- # Compute circumcenters and circumradius for each triangle
46
- for t in self.triangles:
47
- self.circles[t] = self._circumcenter(t)
48
-
49
- def _circumcenter(self, triangle: list) -> tuple:
50
- """Compute circumcenter and circumradius of a triangle in 2D."""
51
- points = np.asarray([self.coords[v] for v in triangle])
52
- points2 = np.dot(points, points.T)
53
- a = np.bmat([[2 * points2, [[1], [1], [1]]], [[[1, 1, 1, 0]]]])
54
-
55
- b = np.hstack((np.sum(points * points, axis=1), [1]))
56
- x = np.linalg.solve(a, b)
57
- bary_coords = x[:-1]
58
- center = np.dot(bary_coords, points)
59
-
60
- radius = np.sum(np.square(points[0] - center)) # squared distance
61
- return (center, radius)
62
-
63
- def _in_circle(self, triangle: list, point: list) -> bool:
64
- """Check if point p is inside of precomputed circumcircle of triangle."""
65
- center, radius = self.circles[triangle]
66
- return np.sum(np.square(center - point)) <= radius
67
-
68
- def add_point(self, point: Sequence) -> None:
69
- """Add a point to the current DT, and refine it using Bowyer-Watson."""
70
- point_index = len(self.coords)
71
- self.coords.append(np.asarray(point))
72
-
73
- bad_triangles = []
74
- for triangle in self.triangles:
75
- if self._in_circle(triangle, point):
76
- bad_triangles.append(triangle)
77
-
78
- boundary = []
79
- triangle = bad_triangles[0]
80
- edge = 0
81
-
82
- while True:
83
- opposite_triangle = self.triangles[triangle][edge]
84
- if opposite_triangle not in bad_triangles:
85
- boundary.append(
86
- (
87
- triangle[(edge + 1) % 3],
88
- triangle[(edge - 1) % 3],
89
- opposite_triangle,
90
- )
91
- )
92
- edge = (edge + 1) % 3
93
- if boundary[0][0] == boundary[-1][1]:
94
- break
95
- else:
96
- edge = (self.triangles[opposite_triangle].index(triangle) + 1) % 3
97
- triangle = opposite_triangle
98
-
99
- for triangle in bad_triangles:
100
- del self.triangles[triangle]
101
- del self.circles[triangle]
102
-
103
- new_triangles = []
104
- for e0, e1, opposite_triangle in boundary:
105
- triangle = (point_index, e0, e1)
106
- self.circles[triangle] = self._circumcenter(triangle)
107
- self.triangles[triangle] = [opposite_triangle, None, None]
108
- if opposite_triangle:
109
- for i, neighbor in enumerate(self.triangles[opposite_triangle]):
110
- if neighbor and e1 in neighbor and e0 in neighbor:
111
- self.triangles[opposite_triangle][i] = triangle
112
-
113
- new_triangles.append(triangle)
114
-
115
- n = len(new_triangles)
116
- for i, triangle in enumerate(new_triangles):
117
- self.triangles[triangle][1] = new_triangles[(i + 1) % n] # next
118
- self.triangles[triangle][2] = new_triangles[(i - 1) % n] # previous
119
-
120
- def export_triangles(self) -> list:
121
- """Export the current list of Delaunay triangles."""
122
- triangles_list = [
123
- (a - 4, b - 4, c - 4)
124
- for (a, b, c) in self.triangles
125
- if a > 3 and b > 3 and c > 3
126
- ]
127
- return triangles_list
128
-
129
- def export_voronoi_regions(self):
130
- """Export coordinates and regions of Voronoi diagram as indexed data."""
131
- use_vertex = {i: [] for i in range(len(self.coords))}
132
- vor_coors = []
133
- index = {}
134
- for triangle_index, (a, b, c) in enumerate(sorted(self.triangles)):
135
- vor_coors.append(self.circles[(a, b, c)][0])
136
- use_vertex[a] += [(b, c, a)]
137
- use_vertex[b] += [(c, a, b)]
138
- use_vertex[c] += [(a, b, c)]
139
-
140
- index[(a, b, c)] = triangle_index
141
- index[(c, a, b)] = triangle_index
142
- index[(b, c, a)] = triangle_index
143
-
144
- regions = {}
145
- for i in range(4, len(self.coords)):
146
- vertex = use_vertex[i][0][0]
147
- region = []
148
- for _ in range(len(use_vertex[i])):
149
- triangle = next(
150
- triangle for triangle in use_vertex[i] if triangle[0] == vertex
151
- )
152
- region.append(index[triangle])
153
- vertex = triangle[1]
154
- regions[i - 4] = region
155
-
156
- return vor_coors, regions
157
-
158
-
159
- def round_float(x: float) -> int: # noqa
160
- return int(x * 500)
161
-
162
-
163
- class VoronoiGrid(DiscreteSpace):
164
- """Voronoi meshed GridSpace."""
165
-
166
- triangulation: Delaunay
167
- voronoi_coordinates: list
168
- regions: list
169
-
170
- def __init__(
171
- self,
172
- centroids_coordinates: Sequence[Sequence[float]],
173
- capacity: float | None = None,
174
- random: Random | None = None,
175
- cell_klass: type[Cell] = Cell,
176
- capacity_function: callable = round_float,
177
- cell_coloring_property: str | None = None,
178
- ) -> None:
179
- """A Voronoi Tessellation Grid.
180
-
181
- Given a set of points, this class creates a grid where a cell is centered in each point,
182
- its neighbors are given by Voronoi Tessellation cells neighbors
183
- and the capacity by the polygon area.
184
-
185
- Args:
186
- centroids_coordinates: coordinates of centroids to build the tessellation space
187
- capacity (int) : capacity of the cells in the discrete space
188
- random (Random): random number generator
189
- cell_klass (type[Cell]): type of cell class
190
- capacity_function (Callable): function to compute (int) capacity according to (float) area
191
- cell_coloring_property (str): voronoi visualization polygon fill property
192
- """
193
- super().__init__(capacity=capacity, random=random, cell_klass=cell_klass)
194
- self.centroids_coordinates = centroids_coordinates
195
- self._validate_parameters()
196
-
197
- self._cells = {
198
- i: cell_klass(self.centroids_coordinates[i], capacity, random=self.random)
199
- for i in range(len(self.centroids_coordinates))
200
- }
201
-
202
- self.regions = None
203
- self.triangulation = None
204
- self.voronoi_coordinates = None
205
- self.capacity_function = capacity_function
206
- self.cell_coloring_property = cell_coloring_property
207
-
208
- self._connect_cells()
209
- self._build_cell_polygons()
210
-
211
- def _connect_cells(self) -> None:
212
- """Connect cells to neighbors based on given centroids and using Delaunay Triangulation."""
213
- self.triangulation = Delaunay()
214
- for centroid in self.centroids_coordinates:
215
- self.triangulation.add_point(centroid)
216
-
217
- for point in self.triangulation.export_triangles():
218
- for i, j in combinations(point, 2):
219
- self._cells[i].connect(self._cells[j], (i, j))
220
- self._cells[j].connect(self._cells[i], (j, i))
221
-
222
- def _validate_parameters(self) -> None:
223
- if self.capacity is not None and not isinstance(self.capacity, float | int):
224
- raise ValueError("Capacity must be a number or None.")
225
- if not isinstance(self.centroids_coordinates, Sequence) or not isinstance(
226
- self.centroids_coordinates[0], Sequence
227
- ):
228
- raise ValueError("Centroids should be a list of lists")
229
- dimension_1 = len(self.centroids_coordinates[0])
230
- for coordinate in self.centroids_coordinates:
231
- if dimension_1 != len(coordinate):
232
- raise ValueError("Centroid coordinates should be a homogeneous array")
233
-
234
- def _get_voronoi_regions(self) -> tuple:
235
- if self.voronoi_coordinates is None or self.regions is None:
236
- (
237
- self.voronoi_coordinates,
238
- self.regions,
239
- ) = self.triangulation.export_voronoi_regions()
240
- return self.voronoi_coordinates, self.regions
241
-
242
- @staticmethod
243
- def _compute_polygon_area(polygon: list) -> float:
244
- polygon = np.array(polygon)
245
- x = polygon[:, 0]
246
- y = polygon[:, 1]
247
- return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
248
-
249
- def _build_cell_polygons(self):
250
- coordinates, regions = self._get_voronoi_regions()
251
- for region in regions:
252
- polygon = [coordinates[i] for i in regions[region]]
253
- self._cells[region].properties["polygon"] = polygon
254
- polygon_area = self._compute_polygon_area(polygon)
255
- self._cells[region].properties["area"] = polygon_area
256
- self._cells[region].capacity = self.capacity_function(polygon_area)
257
- self._cells[region].properties[self.cell_coloring_property] = 0
@@ -1,242 +0,0 @@
1
- """Support for using matplotlib to draw spaces."""
2
-
3
- from collections import defaultdict
4
-
5
- import networkx as nx
6
- import solara
7
- from matplotlib.figure import Figure
8
- from matplotlib.ticker import MaxNLocator
9
-
10
- import mesa
11
- from mesa.experimental.cell_space import VoronoiGrid
12
-
13
-
14
- @solara.component
15
- def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
16
- """A component for rendering a space using Matplotlib.
17
-
18
- Args:
19
- model: a model instance
20
- agent_portrayal: a specification of how to portray an agent.
21
- dependencies: list of dependencies.
22
-
23
- """
24
- space_fig = Figure()
25
- space_ax = space_fig.subplots()
26
- space = getattr(model, "grid", None)
27
- if space is None:
28
- # Sometimes the space is defined as model.space instead of model.grid
29
- space = model.space
30
- if isinstance(space, mesa.space.NetworkGrid):
31
- _draw_network_grid(space, space_ax, agent_portrayal)
32
- elif isinstance(space, mesa.space.ContinuousSpace):
33
- _draw_continuous_space(space, space_ax, agent_portrayal)
34
- elif isinstance(space, VoronoiGrid):
35
- _draw_voronoi(space, space_ax, agent_portrayal)
36
- else:
37
- _draw_grid(space, space_ax, agent_portrayal)
38
- solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
39
-
40
-
41
- # matplotlib scatter does not allow for multiple shapes in one call
42
- def _split_and_scatter(portray_data, space_ax):
43
- grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
44
-
45
- # Extract data from the dictionary
46
- x = portray_data["x"]
47
- y = portray_data["y"]
48
- s = portray_data["s"]
49
- c = portray_data["c"]
50
- m = portray_data["m"]
51
-
52
- if not (len(x) == len(y) == len(s) == len(c) == len(m)):
53
- raise ValueError(
54
- "Length mismatch in portrayal data lists: "
55
- f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
56
- f"color: {len(c)}, marker: {len(m)}"
57
- )
58
-
59
- # Group the data by marker
60
- for i in range(len(x)):
61
- marker = m[i]
62
- grouped_data[marker]["x"].append(x[i])
63
- grouped_data[marker]["y"].append(y[i])
64
- grouped_data[marker]["s"].append(s[i])
65
- grouped_data[marker]["c"].append(c[i])
66
-
67
- # Plot each group with the same marker
68
- for marker, data in grouped_data.items():
69
- space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
70
-
71
-
72
- def _draw_grid(space, space_ax, agent_portrayal):
73
- def portray(g):
74
- x = []
75
- y = []
76
- s = [] # size
77
- c = [] # color
78
- m = [] # shape
79
- for i in range(g.width):
80
- for j in range(g.height):
81
- content = g._grid[i][j]
82
- if not content:
83
- continue
84
- if not hasattr(content, "__iter__"):
85
- # Is a single grid
86
- content = [content]
87
- for agent in content:
88
- data = agent_portrayal(agent)
89
- x.append(i)
90
- y.append(j)
91
-
92
- # This is the default value for the marker size, which auto-scales
93
- # according to the grid area.
94
- default_size = (180 / max(g.width, g.height)) ** 2
95
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
96
- size = data.get("size", default_size)
97
- s.append(size)
98
- color = data.get("color", "tab:blue")
99
- c.append(color)
100
- mark = data.get("shape", "o")
101
- m.append(mark)
102
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
103
- return out
104
-
105
- space_ax.set_xlim(-1, space.width)
106
- space_ax.set_ylim(-1, space.height)
107
- _split_and_scatter(portray(space), space_ax)
108
-
109
-
110
- def _draw_network_grid(space, space_ax, agent_portrayal):
111
- graph = space.G
112
- pos = nx.spring_layout(graph, seed=0)
113
- nx.draw(
114
- graph,
115
- ax=space_ax,
116
- pos=pos,
117
- **agent_portrayal(graph),
118
- )
119
-
120
-
121
- def _draw_continuous_space(space, space_ax, agent_portrayal):
122
- def portray(space):
123
- x = []
124
- y = []
125
- s = [] # size
126
- c = [] # color
127
- m = [] # shape
128
- for agent in space._agent_to_index:
129
- data = agent_portrayal(agent)
130
- _x, _y = agent.pos
131
- x.append(_x)
132
- y.append(_y)
133
-
134
- # This is matplotlib's default marker size
135
- default_size = 20
136
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
137
- size = data.get("size", default_size)
138
- s.append(size)
139
- color = data.get("color", "tab:blue")
140
- c.append(color)
141
- mark = data.get("shape", "o")
142
- m.append(mark)
143
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
144
- return out
145
-
146
- # Determine border style based on space.torus
147
- border_style = "solid" if not space.torus else (0, (5, 10))
148
-
149
- # Set the border of the plot
150
- for spine in space_ax.spines.values():
151
- spine.set_linewidth(1.5)
152
- spine.set_color("black")
153
- spine.set_linestyle(border_style)
154
-
155
- width = space.x_max - space.x_min
156
- x_padding = width / 20
157
- height = space.y_max - space.y_min
158
- y_padding = height / 20
159
- space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
160
- space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
161
-
162
- # Portray and scatter the agents in the space
163
- _split_and_scatter(portray(space), space_ax)
164
-
165
-
166
- def _draw_voronoi(space, space_ax, agent_portrayal):
167
- def portray(g):
168
- x = []
169
- y = []
170
- s = [] # size
171
- c = [] # color
172
-
173
- for cell in g.all_cells:
174
- for agent in cell.agents:
175
- data = agent_portrayal(agent)
176
- x.append(cell.coordinate[0])
177
- y.append(cell.coordinate[1])
178
- if "size" in data:
179
- s.append(data["size"])
180
- if "color" in data:
181
- c.append(data["color"])
182
- out = {"x": x, "y": y}
183
- # This is the default value for the marker size, which auto-scales
184
- # according to the grid area.
185
- out["s"] = s
186
- if len(c) > 0:
187
- out["c"] = c
188
-
189
- return out
190
-
191
- x_list = [i[0] for i in space.centroids_coordinates]
192
- y_list = [i[1] for i in space.centroids_coordinates]
193
- x_max = max(x_list)
194
- x_min = min(x_list)
195
- y_max = max(y_list)
196
- y_min = min(y_list)
197
-
198
- width = x_max - x_min
199
- x_padding = width / 20
200
- height = y_max - y_min
201
- y_padding = height / 20
202
- space_ax.set_xlim(x_min - x_padding, x_max + x_padding)
203
- space_ax.set_ylim(y_min - y_padding, y_max + y_padding)
204
- space_ax.scatter(**portray(space))
205
-
206
- for cell in space.all_cells:
207
- polygon = cell.properties["polygon"]
208
- space_ax.fill(
209
- *zip(*polygon),
210
- alpha=min(1, cell.properties[space.cell_coloring_property]),
211
- c="red",
212
- ) # Plot filled polygon
213
- space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
214
-
215
-
216
- @solara.component
217
- def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
218
- """A solara component for creating a matplotlib figure.
219
-
220
- Args:
221
- model: Model instance
222
- measure: measure to plot
223
- dependencies: list of additional dependencies
224
-
225
- """
226
- fig = Figure()
227
- ax = fig.subplots()
228
- df = model.datacollector.get_model_vars_dataframe()
229
- if isinstance(measure, str):
230
- ax.plot(df.loc[:, measure])
231
- ax.set_ylabel(measure)
232
- elif isinstance(measure, dict):
233
- for m, color in measure.items():
234
- ax.plot(df.loc[:, m], label=m, color=color)
235
- fig.legend()
236
- elif isinstance(measure, list | tuple):
237
- for m in measure:
238
- ax.plot(df.loc[:, m], label=m)
239
- fig.legend()
240
- # Set integer x axis
241
- ax.xaxis.set_major_locator(MaxNLocator(integer=True))
242
- solara.FigureMatplotlib(fig, dependencies=dependencies)
@@ -1,83 +0,0 @@
1
- """custom solara components."""
2
-
3
- from __future__ import annotations
4
-
5
- from collections.abc import Callable
6
-
7
- from .altair_components import SpaceAltair, make_altair_space
8
- from .matplotlib_components import (
9
- SpaceMatplotlib,
10
- make_mpl_plot_component,
11
- make_mpl_space_component,
12
- )
13
-
14
-
15
- def make_space_component(
16
- agent_portrayal: Callable | None = None,
17
- propertylayer_portrayal: dict | None = None,
18
- post_process: Callable | None = None,
19
- backend: str = "matplotlib",
20
- **space_drawing_kwargs,
21
- ) -> SpaceMatplotlib | SpaceAltair:
22
- """Create a Matplotlib-based space visualization component.
23
-
24
- Args:
25
- agent_portrayal: Function to portray agents.
26
- propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications
27
- post_process : a callable that will be called with the Axes instance. Allows for fine-tuning plots (e.g., control ticks)
28
- backend: the backend to use {"matplotlib", "altair"}
29
- space_drawing_kwargs : additional keyword arguments to be passed on to the underlying backend specific space drawer function. See
30
- the functions for drawing the various spaces for the appropriate backend further details.
31
-
32
-
33
- Returns:
34
- function: A function that creates a space component
35
- """
36
- if backend == "matplotlib":
37
- return make_mpl_space_component(
38
- agent_portrayal,
39
- propertylayer_portrayal,
40
- post_process,
41
- **space_drawing_kwargs,
42
- )
43
- elif backend == "altair":
44
- return make_altair_space(
45
- agent_portrayal,
46
- propertylayer_portrayal,
47
- post_process,
48
- **space_drawing_kwargs,
49
- )
50
- else:
51
- raise ValueError(
52
- f"unknown backend {backend}, must be one of matplotlib, altair"
53
- )
54
-
55
-
56
- def make_plot_component(
57
- measure: str | dict[str, str] | list[str] | tuple[str],
58
- post_process: Callable | None = None,
59
- backend: str = "matplotlib",
60
- **plot_drawing_kwargs,
61
- ):
62
- """Create a plotting function for a specified measure using the specified backend.
63
-
64
- Args:
65
- measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
66
- post_process: a user-specified callable to do post-processing called with the Axes instance.
67
- backend: the backend to use {"matplotlib", "altair"}
68
- plot_drawing_kwargs: additional keyword arguments to pass onto the backend specific function for making a plotting component
69
-
70
- Notes:
71
- altair plotting backend is not yet implemented and planned for mesa 3.1.
72
-
73
- Returns:
74
- function: A function that creates a plot component
75
- """
76
- if backend == "matplotlib":
77
- return make_mpl_plot_component(measure, post_process, **plot_drawing_kwargs)
78
- elif backend == "altair":
79
- raise NotImplementedError("altair line plots are not yet implemented")
80
- else:
81
- raise ValueError(
82
- f"unknown backend {backend}, must be one of matplotlib, altair"
83
- )