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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

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.

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