Skip to content

Conversation

Sahil-Chhoker
Copy link
Collaborator

@Sahil-Chhoker Sahil-Chhoker commented Aug 13, 2025

Summary

We can have different components on different pages in the solara dashboard now.

How it looks:

image image

Motive

Instead of cluttering all kinds of plots on a single interface, we can now divide them across multiple pages, making the dashboard cleaner and more presentable.

Implementation

ComponentsView solara component was changed to process (component, page) and display them according to the page.
If no page is passed page=0 is default.
Negative values for pages can also exist (like page=-1) and if the two plots are let's say page=1 and page=10 while being nothing in between, 9 empty pages will be generated to keep the indexing constant.
SpaceRenderer is always on the page=0.

Usage Examples

We can pass page variable to make_plot_component to change the page it renders on.
If making an custom component and want to render on page other than 0, have to pass the component like (component, 1) where component is the custom component and 1 being the page number.

plot1 = make_plot_component("encoding", page=1)

@solara.component
def CustomComp():
    ...

page = SolaraViz(
    model,
    components=[plot1, (CustomComp, 2)],
    ...
)
page

Additional Notes

I tried adding in lazy loading (loading only the page that is selected) but when page is changed from a computationally lighter page to a heavier one solara crashes.

Summary by CodeRabbit

  • New Features
    • Introduced multi-page, tabbed visualization. Components can be assigned to pages and displayed in per-page draggable grids.
    • Plot components now support a page index, enabling organization of charts across tabs.
    • Backwards-compatible defaults: components without a page are placed on page 0.
  • Documentation
    • Updated descriptions to reflect page-based layouts and the new page option for plots.

@Sahil-Chhoker Sahil-Chhoker requested a review from tpike3 August 13, 2025 06:50
@Sahil-Chhoker
Copy link
Collaborator Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 13, 2025

✅ Actions performed

Full review triggered.

Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🟢 -6.1% [-6.8%, -5.4%] 🔵 -0.3% [-0.4%, -0.1%]
BoltzmannWealth large 🔵 -1.6% [-2.6%, -0.9%] 🟢 -6.0% [-7.5%, -4.1%]
Schelling small 🔵 -0.9% [-1.1%, -0.8%] 🔵 -0.8% [-1.0%, -0.6%]
Schelling large 🔵 -2.1% [-2.7%, -1.4%] 🔵 -4.1% [-5.9%, -2.0%]
WolfSheep small 🔵 -0.4% [-0.8%, -0.1%] 🔵 -0.5% [-0.7%, -0.3%]
WolfSheep large 🔵 -0.3% [-0.7%, +0.2%] 🔵 -2.4% [-4.1%, -0.7%]
BoidFlockers small 🔵 -0.5% [-1.0%, -0.0%] 🔵 -0.3% [-0.5%, -0.1%]
BoidFlockers large 🔵 -1.5% [-2.3%, -0.7%] 🔵 -0.8% [-1.1%, -0.6%]

Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

Adds a page parameter to plotting component factories, forwards it through backend dispatch, and changes backends to return (component, page). Updates Solara visualization to a multi-page, tabbed layout: components are provided as (component, page) tuples, normalized to page 0 for legacy inputs, and rendered per-page grids.

Changes

Cohort / File(s) Summary of Changes
Plot component factory API
mesa/visualization/components/__init__.py
Added page: int = 0 to make_plot_component; forwarded page to backend-specific factories; updated docstring; signature updated.
Backend plot components
mesa/visualization/components/matplotlib_components.py, mesa/visualization/components/altair_components.py
Added page: int = 0 to factory functions; return type changed from component callable to (component callable, page); docstrings updated; internal callable logic unchanged.
Solara multi-page layout
mesa/visualization/solara_viz.py
Reworked SolaraViz and ComponentsView to accept and normalize (component, page) tuples; grouped components by page; introduced tabbed UI with per-page GridDraggable layouts; updated imports and docstrings; public signatures changed.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SolaraViz
  participant ComponentsView
  participant Grid per Page
  User->>SolaraViz: components (list) or "default"
  SolaraViz->>SolaraViz: Normalize to (component, page) tuples (default page=0)
  SolaraViz->>ComponentsView: components[(component, page)...]
  ComponentsView->>ComponentsView: Group by page, order pages
  ComponentsView->>Grid per Page: Build per-page grid layouts
  User->>ComponentsView: Switch tab (page N)
  ComponentsView->>Grid per Page: Render grid for page N
Loading
sequenceDiagram
  participant Caller
  participant make_plot_component
  participant Matplotlib
  participant Altair
  Caller->>make_plot_component: measure, post_process, backend, page, kwargs
  alt matplotlib
    make_plot_component->>Matplotlib: make_mpl_plot_component(..., page, **kwargs)
    Matplotlib-->>make_plot_component: (MakePlotMatplotlib, page)
  else altair
    make_plot_component->>Altair: make_altair_plot_component(..., page, **kwargs)
    Altair-->>make_plot_component: (MakePlotAltair, page)
  end
  make_plot_component-->>Caller: (component_callable, page)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

I stack my charts in carrot rows, page by tidy page,
Tabs like burrows neatly close, a tidy warren stage.
Altair stars and Matplotlib moons, tuple trails to track,
Click, hop—new grids appear, no lettuce left to lack.
Thump-thump: release approved! 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (2)
mesa/visualization/components/altair_components.py (1)

454-475: make_altair_plot_component now returns a tuple, breaking make_plot_component’s API
The Altair variant of the plotting factory was changed to return (MakePlotAltair, page), whereas the Matplotlib version still returns just a factory function. As a result,

  • Calls to make_plot_component(..., backend="altair") now receive a (func, page) tuple instead of a single callable
  • Existing user code that invokes the returned value as a function will break at runtime

Locations requiring your attention:

  • mesa/visualization/components/altair_components.py, lines 454–475
    • Change the return value of make_altair_plot_component so it returns only the factory function (e.g. embed page in the closure)
    — or—
  • mesa/visualization/components/init.py, in make_plot_component altair branch
    • Unpack (MakePlotAltair, page) and return just the function (or document/handle the tuple consistently for both backends)

Please align the Altair API with Matplotlib’s to avoid this breaking change.

mesa/visualization/components/matplotlib_components.py (1)

107-130: Breaking API change: make_mpl_plot_component now returns (MakePlotMatplotlib, page)

This update aligns the Matplotlib helper with Altair’s behavior but will break any existing code that expects a lone function.

Please address the following:

  • In mesa/visualization/components/matplotlib_components.py (lines 107–130), update the docstring to state that the function returns a (component_factory, page) tuple.
  • In mesa/visualization/components/__init__.py, audit all calls to make_mpl_plot_component and ensure callers unpack both elements of the tuple.
  • Update your changelog/release notes and bump the major version to signal this breaking change.
🧹 Nitpick comments (1)
mesa/visualization/solara_viz.py (1)

61-63: Complex type annotation may cause compatibility issues

The type annotation for components is quite complex and might cause issues with older Python versions or type checkers. Consider simplifying or using typing.Union for better compatibility.

-    components: list[tuple[reacton.core.Component], int]
-    | list[tuple[Callable[[Model], reacton.core.Component], 0]]
+    components: list[tuple[reacton.core.Component, int]]
+    | list[tuple[Callable[[Model], reacton.core.Component], int]]
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ded4c49 and fe570be.

📒 Files selected for processing (4)
  • mesa/visualization/components/__init__.py (2 hunks)
  • mesa/visualization/components/altair_components.py (2 hunks)
  • mesa/visualization/components/matplotlib_components.py (2 hunks)
  • mesa/visualization/solara_viz.py (6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
mesa/visualization/components/__init__.py (2)
mesa/visualization/components/matplotlib_components.py (1)
  • make_mpl_plot_component (107-130)
mesa/visualization/components/altair_components.py (1)
  • make_altair_plot_component (454-475)
mesa/visualization/solara_viz.py (4)
mesa/visualization/components/altair_components.py (2)
  • make_altair_space (27-61)
  • agent_portrayal (50-51)
tests/test_solara_viz.py (1)
  • agent_portrayal (129-130)
tests/test_solara_viz_updated.py (1)
  • agent_portrayal (126-127)
mesa/visualization/space_renderer.py (2)
  • post_process (383-389)
  • post_process (392-399)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: run-benchmarks
🔇 Additional comments (12)
mesa/visualization/components/altair_components.py (1)

457-457: LGTM! Page parameter properly added and documented

The new page parameter is correctly added with a sensible default value of 0, and the docstring has been updated to document it.

Also applies to: 465-465

mesa/visualization/components/__init__.py (2)

77-77: LGTM! Page parameter properly added with clear documentation

The page parameter is correctly added with a default value of 0, and the docstring clearly explains its purpose.

Also applies to: 86-86


93-99: Backend plot components updated as expected

Both make_mpl_plot_component and make_altair_plot_component now include the page parameter in their signatures and return tuples ((MakePlotMatplotlib, page) and (MakePlotAltair, page), respectively), so no further changes are needed.

mesa/visualization/components/matplotlib_components.py (1)

110-110: LGTM! Page parameter properly documented

The page parameter is correctly added with a default value of 0 and properly documented in the docstring.

Also applies to: 118-118

mesa/visualization/solara_viz.py (8)

27-27: LGTM! Required import for defaultdict

The collections import is correctly added to support the new defaultdict usage in ComponentsView.


84-87: Documentation updated correctly for multipage support

The docstring has been properly updated to explain the new tuple format with page numbers.


123-131: Default component correctly wrapped with page 0

The default Altair space component is properly wrapped as a tuple with page 0, maintaining consistency with the new multipage structure.


151-151: Space component correctly inserted with page 0

The space component from the renderer is properly inserted at the beginning with page 0.


375-382: Good backward compatibility handling

The code properly handles backward compatibility by converting non-tuple components to (component, 0) tuples.


384-395: Efficient page organization with gap filling

The code efficiently groups components by page and fills in missing page indices to maintain sequential tab order. This ensures a clean UI even when pages are specified non-contiguously.


403-419: Well-implemented layout synchronization

The layout synchronization logic properly handles adding new pages and removing deleted ones, maintaining state consistency.


422-443: Clean implementation of tabbed multipage layout

The implementation using Solara's Tabs and TabItems is clean and properly handles the per-page grid layouts with drag-and-drop functionality preserved.

@quaquel
Copy link
Member

quaquel commented Aug 13, 2025

I like the look of this. Great stuff.

2 quick thoughts.

  1. A shame that lazy loading caused crashes. Any idea what explains this?
  2. I am not sure about the implementation choice to generate empty pages. I prefer explicit over implicit. So, let the user specify explicitly the number of pages and raise errors if page numbers are used outside this range.

@Sahil-Chhoker
Copy link
Collaborator Author

I like the look of this. Great stuff.

Thanks!

  1. A shame that lazy loading caused crashes. Any idea what explains this?

This is just my observation, but when we look only at the plot component, the steps increase very quickly—so quickly that the space component can’t keep up. When it switches to the space one, the step counters don’t match, so two values exist at the same time. Since that can’t happen, it crashes.

  1. I am not sure about the implementation choice to generate empty pages. I prefer explicit over implicit. So, let the user specify explicitly the number of pages and raise errors if page numbers are used outside this range.

My goal was to keep the indexing consistent, and it felt weird to put page 10 exactly after page 1 (if they were passed), therefore I just decided to make empty pages, if that's not what the user wants they can adjust the index themselves.
I don't want to add unwanted restrictions to these pages like they can't be negative and can't exist without space, because I just see them as different space where plots can or can't exist.
I hope I made my though process clear, let me know if I should change this.

@tpike3
Copy link
Member

tpike3 commented Aug 15, 2025

I like the look of this. Great stuff.

Thanks!

  1. A shame that lazy loading caused crashes. Any idea what explains this?

This is just my observation, but when we look only at the plot component, the steps increase very quickly—so quickly that the space component can’t keep up. When it switches to the space one, the step counters don’t match, so two values exist at the same time. Since that can’t happen, it crashes.

  1. I am not sure about the implementation choice to generate empty pages. I prefer explicit over implicit. So, let the user specify explicitly the number of pages and raise errors if page numbers are used outside this range.

My goal was to keep the indexing consistent, and it felt weird to put page 10 exactly after page 1 (if they were passed), therefore I just decided to make empty pages, if that's not what the user wants they can adjust the index themselves. I don't want to add unwanted restrictions to these pages like they can't be negative and can't exist without space, because I just see them as different space where plots can or can't exist. I hope I made my though process clear, let me know if I should change this.

My thought on this, is that the empty pages are ok, because the parameter is stating "put this chart on this page".

Correct me if I am wrong @Sahil-Chhoker (having not tested it yet) if we expand your example, then the below would put the space on page 0, plot1 on page 1 and then the two custom component on page 2, correct?

plot1 = make_plot_component("encoding", page=1)

@solara.component
def CustomComp():
    ...

@solara.component
def CustomComp2():
    ...
    
page = SolaraViz(
    model,
    components=[plot1, (CustomComp, 2), (CustomComp2, 2), ],
    ...
)
page

If my understanding is correct, one possible way to make it more explicit would be to make the kwarg -- on_page

One question, do users have the ability to change the page title - so instead of "Page 0" it could say "Sugarscape", page 1 could say "Price Index" etc...

@Sahil-Chhoker
Copy link
Collaborator Author

Correct me if I am wrong @Sahil-Chhoker (having not tested it yet) if we expand your example, then the below would put the space on page 0, plot1 on page 1 and then the two custom component on page 2, correct?

plot1 = make_plot_component("encoding", page=1)

@solara.component
def CustomComp():
    ...

@solara.component
def CustomComp2():
    ...
    
page = SolaraViz(
    model,
    components=[plot1, (CustomComp, 2), (CustomComp2, 2), ],
    ...
)
page

Yes!

If my understanding is correct, one possible way to make it more explicit would be to make the kwarg -- on_page

Change page to on_page?

One question, do users have the ability to change the page title - so instead of "Page 0" it could say "Sugarscape", page 1 could say "Price Index" etc...

Not yet, but if you want that is possible if I just add another field to the tuple and rework the page naming logic.

@tpike3
Copy link
Member

tpike3 commented Aug 17, 2025

Correct me if I am wrong @Sahil-Chhoker (having not tested it yet) if we expand your example, then the below would put the space on page 0, plot1 on page 1 and then the two custom component on page 2, correct?

plot1 = make_plot_component("encoding", page=1)

@solara.component
def CustomComp():
    ...

@solara.component
def CustomComp2():
    ...
    
page = SolaraViz(
    model,
    components=[plot1, (CustomComp, 2), (CustomComp2, 2), ],
    ...
)
page

Yes!

If my understanding is correct, one possible way to make it more explicit would be to make the kwarg -- on_page

Change page to on_page?

One question, do users have the ability to change the page title - so instead of "Page 0" it could say "Sugarscape", page 1 could say "Price Index" etc...

Not yet, but if you want that is possible if I just add another field to the tuple and rework the page naming logic.

That would be great if users can define their page titles, please do. As for the page vs on_page. I would wait for @quaquel to provide his thoughts.

@Sahil-Chhoker
Copy link
Collaborator Author

I've tried adding page name but this introduces too many reactive variables in SolaraViz, making it unstable. I believe we don't want that, what do you say @tpike3?

@tpike3
Copy link
Member

tpike3 commented Aug 19, 2025

I've tried adding page name but this introduces too many reactive variables in SolaraViz, making it unstable. I believe we don't want that, what do you say @tpike3?

No stable is definitely better

@tpike3
Copy link
Member

tpike3 commented Aug 19, 2025

@Sahil-Chhoker I am not sure there is anything you can do about this but there definitely was some challenges switching between plots, while it was running. If there is no ability to refine, I would add a warning to pause the model before switching views.

Could you please also add use of pages to an example (I think sugarscape would be the obvious, with the warning) as well as add it to a tutorial (with the warning about switching pages unless paused, unless it can be refined).

Only other question, as I didn't experiment with it, is I had to use the page kwarg otherwise I got an error (matplotlib ax). Your custom component example above did not have the page kwarg. Appreciating you may have just been demoing usage, if the example above is accurate, could you please make the page kwarg required for both just to follow the principle of least surprise.

Form here then we can merge and I will start the Mesa 3.3. release process.

@Sahil-Chhoker
Copy link
Collaborator Author

@Sahil-Chhoker I am not sure there is anything you can do about this but there definitely was some challenges switching between plots, while it was running. If there is no ability to refine, I would add a warning to pause the model before switching views.

I'll see what I can do.

Could you please also add use of pages to an example (I think sugarscape would be the obvious, with the warning) as well as add it to a tutorial (with the warning about switching pages unless paused, unless it can be refined).

Sure.

Only other question, as I didn't experiment with it, is I had to use the page kwarg otherwise I got an error (matplotlib ax). Your custom component example above did not have the page kwarg. Appreciating you may have just been demoing usage, if the example above is accurate, could you please make the page kwarg required for both just to follow the principle of least surprise.

I would have done the same but in case of custom components we are passing a tuple of component and page, and tuple don't support passing keyword arguments inside them.
I'll have to use dictionaries to get that behavior but this is just my personal opinion, I find writing dictionaries a pain. An example of how it would look:

{
    "component": CustomCom,
    "page": 3,
}

Let me know if I should change the tuples with dict.

@Sahil-Chhoker
Copy link
Collaborator Author

@Sahil-Chhoker I am not sure there is anything you can do about this but there definitely was some challenges switching between plots, while it was running. If there is no ability to refine, I would add a warning to pause the model before switching views.

There’s no way to optimize page switching while the model is running. If we add a warning to pause the model before switching, I suggest we also implement lazy loading, as it significantly improves performance.

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.

Excellent work as always.

@quaquel , @EwoutH , @jackiekazil

Any comments or issues, before I merge. This will wrap up @Sahil-Chhoker GSOC project and cause release of Mesa 3.3

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.

3 participants