Skip to content

Add Altair plotting functionality #2810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 19, 2025
Merged

Conversation

Sahil-Chhoker
Copy link
Collaborator

Summary

Adds the functionality to make plots using altair.

Motive

Part of my GSoC commitment.

Implementation

Following the design of matplotlib plotting function, a make_altair_plot_component function is made that returns a callable solara component PlotAltair.
Additional kwarg is grid: bool, used to draw grid on the plot, default is False.

Usage Examples

plot_component = make_plot_component(
    {"plot1": "green", "plot2": "blue"}, 
    backend="altair", 
    post_process=post_process_line,
    grid=True
)

page = SolaraViz(
    model,
    components=[plot_component],
    ...
)

How it looks:

image

@Sahil-Chhoker Sahil-Chhoker requested a review from tpike3 July 10, 2025 06:16
Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 -1.0% [-1.7%, -0.4%] 🔵 -0.8% [-1.0%, -0.6%]
BoltzmannWealth large 🔵 -0.1% [-0.7%, +0.5%] 🔵 -4.4% [-5.9%, -2.9%]
Schelling small 🔵 +0.8% [+0.6%, +1.0%] 🔵 -0.3% [-0.4%, -0.1%]
Schelling large 🔵 +0.8% [-0.3%, +2.0%] 🔵 -0.7% [-1.6%, +0.2%]
WolfSheep small 🔵 +0.1% [-0.2%, +0.3%] 🔵 -0.7% [-0.8%, -0.5%]
WolfSheep large 🔵 +0.4% [-0.4%, +1.6%] 🔵 +0.2% [-1.3%, +1.9%]
BoidFlockers small 🔵 +0.4% [-0.2%, +1.0%] 🔵 -0.1% [-0.3%, +0.1%]
BoidFlockers large 🔵 +0.2% [-0.7%, +1.0%] 🔵 +0.4% [+0.1%, +0.8%]

@tpike3
Copy link
Member

tpike3 commented Jul 13, 2025

@Sahil-Chhoker I really like the set up its very smooth and plot component will be easy to adopt for users.

What I did, I implemented boltzmann wealth and sugarscape from the examples using altair.

A couple things:

  • The plots/ components had priority over the render, so it would be gini plot and then the Boltzmann renderer, for sugarscape it was number of agents plot, trading price plot, and then the renderer. Can you give the renderer priority?

  • What is the syntax for passing parameters into altair? For example, I wanted to turn of the grid. My understanding is I would pass that in space_kwargs to the renderer with draw_grid but that gave me a key error?

  • An observation, in sugarscape with the way property_layer in sugarscape is written

def propertylayer_portrayal(layer):
    if layer.name == "sugar":
        return PropertyLayerStyle(
            color="blue", alpha=0.8, colorbar=True, vmin=0, vmax=10
        )
    return PropertyLayerStyle(color="red", alpha=0.8, colorbar=True, vmin=0, vmax=10)

For the altair space this make spice the title of the render. This is just an indicator of behavior that I hope is useful as you continue to refine the code.

I really look how this is coming together, let me know what you think.

@Sahil-Chhoker
Copy link
Collaborator Author

  • The plots/ components had priority over the render, so it would be gini plot and then the Boltzmann renderer, for sugarscape it was number of agents plot, trading price plot, and then the renderer. Can you give the renderer priority?

Done.

  • What is the syntax for passing parameters into altair? For example, I wanted to turn of the grid. My understanding is I would pass that in space_kwargs to the renderer with draw_grid but that gave me a key error?

Just don't call draw_structure() when calling the methods separately. No way to do it in the render() function.

  • An observation, in sugarscape with the way property_layer in sugarscape is written
def propertylayer_portrayal(layer):
    if layer.name == "sugar":
        return PropertyLayerStyle(
            color="blue", alpha=0.8, colorbar=True, vmin=0, vmax=10
        )
    return PropertyLayerStyle(color="red", alpha=0.8, colorbar=True, vmin=0, vmax=10)

For the altair space this make spice the title of the render. This is just an indicator of behavior that I hope is useful as you continue to refine the code.

Will look into this, thanks for telling.

@Sahil-Chhoker
Copy link
Collaborator Author

@tpike3, I've addressed all the comments, feel free to let me know if you find anything else concerning.

@tpike3
Copy link
Member

tpike3 commented Jul 14, 2025

@Sahil-Chhoker

A couple more things as we start to lock this in. I used headers just to break up each point due to the visual noise.

Buffer between plots?

Is there anyway to more effectively control the spacing of the plots

By default on sugarscape, it plots like this ---

image

And this covers up part of the extended plot

image

Can we default a buffer between plots? Tangentially related, could we get the plots to be on a different page as an option?

Altair plots

I plotted the space using altair but the make_plot_components were matplotlib. I added backend='altair' but it gave me an error in the sisebar, although it seemed to plot

syntax for customization

I appreciate I am being a little lazy here but below is the quick code I used for sugarscape. Can you update it to give me an example, in altair, where I customize each plot from renderer to the plots

model = SugarscapeG1mt()

renderer = SpaceRenderer(model, backend="altair")
renderer.render(agent_portrayal,
    propertylayer_portrayal,
)

page = SolaraViz(
    model,
    renderer,
    components=[
        make_plot_component("#Traders"),
        make_plot_component("Price"),
    ],
    model_params=model_params,
    name="Sugarscape {G1, M, T}",
    play_interval=150,
)
page  # noqa

@Sahil-Chhoker
Copy link
Collaborator Author

Sahil-Chhoker commented Jul 14, 2025

Buffer between plots?

Is there anyway to more effectively control the spacing of the plots

I will need to modify this function in the solraviz.py file to get better styling:

def make_initial_grid_layout(num_components):
    """Create an initial grid layout for visualization components.

    Args:
        num_components: Number of components to display

    Returns:
        list: Initial grid layout configuration
    """
    return [
        {
            "i": i,
            "w": 16,
            "h": 16,
            "moved": False,
            "x": 6 * (i % 2),
            "y": 16 * (i - i % 2),
        }
        for i in range(num_components)
    ]

I will experiment with this a little and try to come up with something usable.

Can we default a buffer between plots? Tangentially related, could we get the plots to be on a different page as an option?

New page can definitely be possible, but first I would love to discuss the API interface for it, I'm thinking something like this:

plot_component = make_plot_component(..., page=1)
# and if the page number is too big like page=20, it will default to the last existing page, like 1

Altair plots

I plotted the space using altair but the make_plot_components were matplotlib. I added backend='altair' but it gave me an error in the sisebar, although it seemed to plot

Sometimes I get unnecessary errors that go away be reloading the page, but I didn't get this kind of error. Maybe share the contents of the error.

Syntax for customization

model = SugarscapeG1mt()
renderer = SpaceRenderer(model, backend="altair")
# to skip drawing structure
renderer.draw_agents(agent_portrayal)
renderer.draw_propertylayer(propertylayer_portrayal)

# to customize grid
renderer.render(agent_portrayal, propertylayer_portrayal, space_kwargs={
    "xlabel": "x",
    "ylabel": "y",
    "grid_color": "blue",
    "grid_dash": [6, 2],
    "grid_width": 4,
    "grid_opacity": 0.5,
}) # or just pass these arguments directly in draw_structure

# for more control:
def post_process(chart):
    chart = chart.properties(...) # or something else
    return chart

renderer.post_process = post_process

# Note: The customizability for sugerscape is very limited because of how the colorbars are handled
# in the chart and similarly the customizability is different for every space.

# To customize grid every detail is mentioned in the respective `space_drawer` docstring.

# To customize plots:
def post_process_plot(chart):
    chart = chart.(...)
    return chart
plot_component = make_plot_component("#Traders", backend="altair", grid=False, post_process=post_process_plot)

page = SolaraViz(
    model,
    renderer,
    components=[
        plot_component,
        make_plot_component("Price"),
    ],
    model_params=model_params,
    name="Sugarscape {G1, M, T}",
    play_interval=150,
)
page

@tpike3
Copy link
Member

tpike3 commented Jul 16, 2025

For this----

Altair plots
I plotted the space using altair but the make_plot_components were matplotlib. I added backend='altair' but it gave me an error in the sisebar, although it seemed to plot

Sometimes I get unnecessary errors that go away be reloading the page, but I didn't get this kind of error. Maybe share the contents of the error.

You were correct, I went to re run it and I did not get the same error, so it must have been some artifact of me trying different things.

Switching to Altair for the plots I did get this warning:

UserWarning: I don't know how to infer vegalite type from 'empty'.  Defaulting to nominal.

Just for your awareness.

@Sahil-Chhoker
Copy link
Collaborator Author

@tpike3, just a report, I am currently reworking how property layers are drawn in altair, the current version is very unstable.

@tpike3
Copy link
Member

tpike3 commented Jul 16, 2025

@tpike3, just a report, I am currently reworking how property layers are drawn in altair, the current version is very unstable.

Thanks for the update! Let me know if I can help out in anyway

@Sahil-Chhoker
Copy link
Collaborator Author

Sahil-Chhoker commented Jul 16, 2025

I determined that I can only play with the values a bit but the current make_initial_grid_layout function can't be made to change the sizes dynamically, so we can't really get a custom buffer around drawn charts. Therefore I started looking at the way the propertylayer chart is drawn, I did find two ways in which we can make the drawing of property layers more elegant. But I'm actually quite conflicted over what approach to choose for the colorbars.

First:

The colorbars are made as legends of the chart and therefore you can control the entire chart however you want using a post_process function, less prone to errors and elegant code, how it looks:
image

The first approach can also be reworked to look like this:
image


Second:

The colorbars are made as separate chart and concatenated to the main chart, looks good but limits the control over the whole chart and is kind-of error prone, how it looks:
image

What approach is a better fit in your opinion, I'm thinking 1st one because using more than two property layers with visualization is very rare and it gives proper control over the chart using post process function, but let me know what would be better in your opinion.

@Sahil-Chhoker
Copy link
Collaborator Author

@tpike3, this PR is good to go from my side.

@tpike3
Copy link
Member

tpike3 commented Jul 19, 2025

@coderabbitai

Copy link

coderabbitai bot commented Jul 19, 2025

Tip

For best results, initiate chat on the files or code changes.

@tpike3

Based on the discussion thread, it looks like great progress has been made on the Altair backend implementation! Here's a summary of the current state:

Resolved Issues:

  • Plot prioritization: Renderer now has priority over plot components
  • Parameter syntax: Clarified that draw_structure() should be skipped for grid customization
  • Property layer behavior: Identified the spice title issue in Sugarscape

🔄 Current Status:

  • Performance: Benchmarks show improvements across most models
  • Backend compatibility: Altair plotting is working (minor vegalite warning noted but functional)
  • Customization examples: Sahil-Chhoker provided comprehensive syntax examples

Pending Decisions:

  1. Property layer colorbar approach: Two options presented by Sahil-Chhoker:

    • Option 1: Legends-based (more control, elegant code)
    • Option 2: Separate concatenated charts (looks good but less flexible)
  2. Plot spacing: Current grid layout limitations acknowledged - custom buffering not easily achievable

Sahil-Chhoker mentioned the PR is "good to go" from their side.

Questions for you:

  • Do you have a preference for the property layer colorbar approach?
  • Are there any specific aspects you'd like me to review or analyze further?
  • Any remaining blockers before this can be merged?

The collaboration looks very productive and the implementation appears robust!


🧠 Learnings used
Learnt from: Sahil-Chhoker
PR: projectmesa/mesa#2803
File: mesa/visualization/backends/altair_backend.py:312-553
Timestamp: 2025-06-20T16:44:20.677Z
Learning: In mesa/visualization/backends/altair_backend.py, the author prefers to keep the draw_propertylayer method intact despite its complexity, as they believe breaking it into smaller functions would divert attention from the main logic flow. The method is well-documented and follows a clear sequence of operations for property layer rendering.

Copy link
Member

@tpike3 tpike3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Next steps for community awareness:
@Sahil-Chhoker is going to update examples (basic and advanced) showing different capabilities of the visual.

From this there may be some further enhancements to the visualization module and help it stabilize. Once these are further develop, then the plans is to update the tutorial for a robust visualization section.

@tpike3 tpike3 merged commit 47eff74 into projectmesa:main Jul 19, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants