Adds Altair plot component and some other fixes
#2435
- [x] - rename make_space_altair to something like make_altair_space_component
- [x] - add make_altair_plot_component
- [x] - Add support for all spaces. This means including the hexgrid and network transformation of the x and y coordinates.
- [x] - Have a generic get_agent_data method
@quaquel @EwoutH Iāve added a new component, make_altair_plot_component. Could you kindly check if it aligns well with the project?
P.S working on space support.
Performance benchmarks:
| Model | Size | Init time [95% CI] | Run time [95% CI] |
|---|---|---|---|
| BoltzmannWealth | small | šµ +1.3% [+0.4%, +2.2%] | šµ -0.3% [-0.5%, -0.2%] |
| BoltzmannWealth | large | šµ -1.2% [-1.8%, -0.6%] | šµ -0.5% [-1.6%, +0.7%] |
| Schelling | small | šµ -0.7% [-1.0%, -0.5%] | šµ +0.8% [+0.6%, +0.9%] |
| Schelling | large | šµ -0.7% [-1.1%, -0.3%] | šµ -0.9% [-1.3%, -0.5%] |
| WolfSheep | small | šµ +0.1% [-0.1%, +0.3%] | šµ +1.1% [+0.9%, +1.2%] |
| WolfSheep | large | šµ +0.9% [+0.3%, +1.5%] | šµ +1.9% [+1.2%, +2.5%] |
| BoidFlockers | small | šµ -0.7% [-1.3%, -0.2%] | šµ +1.8% [+1.6%, +2.0%] |
| BoidFlockers | large | šµ -0.1% [-0.5%, +0.4%] | šµ +1.9% [+1.6%, +2.3%] |
Thanks for the PR! A few of the automated checks broke, could you look into what caused that?
I will leave the review to others.
@nissu99 also please look at #2643 and #2641 As you are @sanika-n are working in the same space
As it looks like you two are looking over the entire altair implementation I would also recommend looking at #2642 discussion
It is always good to collaborate and think together you may address some larger visualization challenges.
@tpike3 When I run the tests locally, they don't fail, but they are failing here.
@tpike3 When I run the tests locally, they don't fail, but they are failing here.
I am not sure why your tests are passing locally, however for tests to pass they need to updated. It is expecting None and now getting Chart, because you added post process.
__ ERROR collecting tests/test_components_matplotlib.py _____________
tests/test_components_matplotlib.py:[20](https://github.com/projectmesa/mesa/actions/runs/12984364297/job/36207123671?pr=2644#step:6:21): in <module>
from mesa.visualization.mpl_space_drawing import (
mesa/visualization/__init__.py:13: in <module>
from .components import make_plot_component, make_space_component
mesa/visualization/components/__init__.py:7: in <module>
from .altair_components import (
mesa/visualization/components/altair_components.py:93: in <module>
post_process: Callable[[alt.Chart], alt.Chart] | None = None,
E AttributeError: 'NoneType' object has no attribute 'Chart'
@tpike3 When I run the tests locally, they don't fail, but they are failing here.
I am not sure why your tests are passing locally, however for tests to pass they need to updated. It is expecting
Noneand now gettingChart, because you added post process.__ ERROR collecting tests/test_components_matplotlib.py _____________ tests/test_components_matplotlib.py:[20](https://github.com/projectmesa/mesa/actions/runs/12984364297/job/36207123671?pr=2644#step:6:21): in <module> from mesa.visualization.mpl_space_drawing import ( mesa/visualization/__init__.py:13: in <module> from .components import make_plot_component, make_space_component mesa/visualization/components/__init__.py:7: in <module> from .altair_components import ( mesa/visualization/components/altair_components.py:93: in <module> post_process: Callable[[alt.Chart], alt.Chart] | None = None, E AttributeError: 'NoneType' object has no attribute 'Chart'
Thank You, the issue got resolved by adding altair to the toml file.
@quaquel @tpike3 i was making a generic get_agent_data function ,should i remove all the old functions like _get_agent_data_new_discrete_space or keeping them will be right?
Removing stuff that has become redundant is fine.
@quaquel i have added all the stuff,can you review it?
I'll try to review it over the weekend, but I am rather bussy at the moment. This is a part of the code base I am not intimately familiar with and his been a while since I used Altair, so reviewing will take more time than parts of the code base I know inside out.
@tpike3 For now, there haven't been many conflicts with #2641. I have also talked to @sanika-n regarding possible overlaps ,will be looking forward for your review.
I suggest we try to merge this PR first, then rebase #2643 and merge that afterward. @nissu99, @sanika-n do you both agree?
My motivation is that this overhauls the structure of the altair plotting API, while the other adds a feature to the API.
Sure, works for me
I suggest we try to merge this PR first, then rebase #2643 and merge that afterward. @nissu99, @sanika-n do you both agree?
My motivation is that this overhauls the structure of the altair plotting API, while the other adds a feature to the API.
Yes,why not.
Can you elaborate on the "other fixes" from the title?
Can you elaborate on the "other fixes" from the title?
Nothing much ,it was for renaming and adding the generic method , i should have named it better.
@nissu99 Thanks for making some changes already! Could you share the state of this PR and preferably reply to the review comments (of quaquel) individually?
@Sahil-Chhoker this would be a great first PR for you review, as it blocks some others.
@nissu99 Thanks for making some changes already! Could you share the state of this PR and preferably reply to the review comments (of quaquel) individually?
@Sahil-Chhoker this would be a great first PR for you review, as it blocks some others.
Really sorry for the delay ,was travelling due to some family reasons,I am working on changes to make the api similar to that of matplotlib.
@nissu99 can you give an indication of your timeline? We might otherwise merge some other altair PR first.
@nissu99 can you give an indication of your timeline? We might otherwise merge some other altair PR first.
As you had pointed out, I was seeing if a common overarching function could be made for both Matplotlib and Altair stuff. other changes are not that time consuming.
@quaquel what do you say should I focus on that or just make other changes and will make a future PR for the Common function?
This PR is already an improvement. The logical next step, which ideally would be part of this PR, is to structure the code exactly as done for matplotlib. This, for example, means having a single overarching method to get agent data and updating agent positions for e.g., networks, and hexgrids inside their respective draw method. The last step, and well beyond this PR is to see what we can harmonize across altair and matplotib.
This PR is already an improvement. The logical next step, which ideally would be part of this PR, is to structure the code exactly as done for matplotlib. This, for example, means having a single overarching method to get agent data and updating agent positions for e.g., networks, and hexgrids inside their respective draw method. The last step, and well beyond this PR is to see what we can harmonize across altair and matplotib.
made the changes , is this fine?
@nissu99, is the grid drawing logic implemented yet, because I am unable to see the grid lines?
@nissu99 I have done a basic overview of functioning of the
altairdrawing system, if you find similar problems please fix them. I will do a thorough review on a later date.
I have not yet implemented the grid visualization
@quaquel @Sahil-Chhoker I think we should keep the core API very concise, and changes like controlling grid visibility can be handled in the post-processing stage, what is your opinion?
I also think the the same and I think it is followed everywhere in the visualization API, but the grid resizing every time the agents move may be confusing and not very elegant.
Grid visibility is not trivial for networks or hexgrids. Hence, it is included on the matplotlib side and controlled via a boolean. As indicated in #2642, I am considering changing the API to give the user more control over this beyond just a boolean. So I am fine with leaving this out of altair for now.
I agree with @Sahil-Chhoker, the view limits should be fixed and not change every step.
@Sahil-Chhoker can you share the example you are using?
Don't mind the ugly and commented code, I keep changing this example as per need, and I really recommend the Command Center from #2674 for better debugging.
import random
from mesa.experimental.cell_space.grid import HexGrid, OrthogonalMooreGrid
from mesa.experimental.continuous_space import ContinuousSpace, ContinuousSpaceAgent
from mesa import Model
import math
from mesa.experimental.cell_space import CellAgent
from mesa.visualization import SolaraViz, make_space_component
from mesa.experimental.cell_space.property_layer import PropertyLayer
import numpy as np
from portrayal_components import PropertyLayerPortrayal, AgentPortrayalStyle, PropertyLayerStyle
import shutil
from agents import myAgent
class myModel(Model):
def __init__(self, seed=None, width=5, height=5 , n_agents=4):
super().__init__(seed=seed)
self.width = width
self.height = height
self.num_agents = n_agents
self.grid = OrthogonalMooreGrid(
(self.width, self.height), capacity=math.inf, torus=False, random=self.random
)
self.x = 1
self.test_layer = PropertyLayer("test", (self.width, self.height), default_value=1, dtype=int)
self.test_layer.data = np.random.randint(2, 6, (self.width, self.height))
for i in range(self.width * self.height):
cell = self.grid.all_cells.cells[i]
if cell.coordinate == (1, 1):
myAgent(self, cell=cell, id=0)
# else:
# myAgent(self, cell=cell, id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[i], id=self.x)
# self.x += 1
# self.grid.add_property_layer(self.test_layer)
# myAgent(self, cell=self.grid.all_cells.cells[17], id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[5], id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[7], id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[1], id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[1], id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[1], id=self.x)
# self.x += 1
# myAgent(self, cell=self.grid.all_cells.cells[1], id=self.x)
self.running = True
def step(self):
self.agents.shuffle_do("step")
class ContModel(Model):
def __init__(self, seed=None):
super().__init__(seed=seed)
self.grid = ContinuousSpace([[0, 1], [0, 1]], torus=True, random=self.random)
self.test_layer = PropertyLayer("test2", [1, 1], default_value=0.0, dtype=float)
# self.grid.add_property_layer(self.test_layer)
self.k_agent = ContinuousSpaceAgent(self.grid, self)
self.k_agent.position = [0.5, 0.5]
self.a_agent = ContinuousSpaceAgent(self.grid, self)
self.a_agent.position = [0.55, 0.55]
def step(self):
self.k_agent.position = [random.random(), random.random()]
def agent_portrayal(agent):
portrayal = AgentPortrayalStyle(
color='tab:orange',
marker='^',
size=150 if agent.id == 0 else 50,
zorder=1
)
return dict(portrayal)
property_layer_portrayal = PropertyLayerPortrayal()
property_layer_portrayal.add_layer(name="test",color="blue", alpha=0.5, colorbar=True, vmin=0, vmax=6)
property_layer_portrayal = dict(property_layer_portrayal)
def post_process(ax):
ax.set_aspect("equal")
ax.get_figure().set_size_inches(8, 8)
model_params = {
"width": {
"type": "SliderInt",
"value": 50,
"label": "Width",
"min": 5,
"max": 60,
"step": 1,
},
"height": {
"type": "SliderInt",
"value": 50,
"label": "Height",
"min": 5,
"max": 60,
"step": 1,
},
}
model = myModel()
space_component = make_space_component(
agent_portrayal=agent_portrayal, backend="altair", draw_grid=True
)
# space_component = make_space_component(
# agent_portrayal=agent_portrayal, draw_grid=True, post_process=post_process, propertylayer_portrayal=property_layer_portrayal
# )
page = SolaraViz(
model,
components=[space_component],
model_params=model_params,
name="Test Model",
additional_imports={
"myAgent": myAgent,
}
)
page
