Skip to content

Conversation

@Mani212005
Copy link

Minimal Bundled SVG Agent Icons

Summary

This PR introduces a minimal internal SVG agent icon library and helper module. It is intentionally small and focused on packaging + access + baseline performance, with no frontend integration yet. That will follow in a separate PR after reviewer feedback.

Changes

  • mesa/visualization/icons.py:
    • list_icons() – returns available icon basenames.
    • get_icon_svg(name) – returns raw SVG text (supports mesa:<name>).
  • 3 SVG icons under mesa/visualization/icons/ (using fill="currentColor" for recoloring). Current icons: neutral_face, smiley, sad_update.
  • mesa/visualization/icons/README.md – brief style/naming rules.
  • tests/test_icons.py – listing and retrieval tests.
  • pyproject.toml – added [tool.hatch.build] include patterns to package SVG assets (Hatch/hatchling is used; no MANIFEST.in).
  • benchmarks/icons_benchmark.py – dev-only Python benchmark script (not included in package builds).

No existing visualization behavior is changed.

Rationale

Split the work:

  1. Asset + access layer (this PR) – low risk, easy to review and test.
  2. Frontend integration + performance comparison (next PR).
    This sequencing allows maintainers to validate packaging + API before adopting icon rendering.

Performance (Python benchmark)

Environment:

  • Python: 3.13.x
  • Packages: cairosvg (version), Pillow (version)

Method:
python benchmarks/icons_benchmark.py --icon neutral_face --n N --frames 120
Measures cold SVG read, SVG→raster conversion, and per-frame composition time (Pillow alpha compositing).

Results (neutral_face.svg):

Agents (N) svg_read_sec svg_convert_sec avg_frame_ms p95_frame_ms
100 0.000 0.013 0.367 0.393
500 0.000 0.014 1.956 2.001
1000 0.000 0.011 3.883 4.129

Interpretation:

  • Cold read negligible.
  • One-time SVG→raster conversion fast (~13 ms).
  • Composition overhead for 100 agents is far below frame budget (<16 ms for 60 FPS).
  • Larger-N measurements will be added, and browser DOM/SVG vs Canvas will be benchmarked in the follow-up after minimal integration.

Verification

  • pytest -q passes locally.
  • Wheel build includes the SVG assets:
    • unzip -l dist/Mesa-*.whl | grep visualization/icons
  • Runtime check:
    python -c "from mesa.visualization import icons; print(icons.list_icons())" returns expected names.
  • Benchmark script runs successfully.

Follow-Up Plan (next PR)

  1. Add portrayal support: {"icon": "<name>", "color": "#RRGGBB"}.
  2. Implement Python-only Solara integration (inline SVG) and compare with cached raster composition.
  3. Benchmark browser frame times for N = 100 / 500 / 1000 agents (avg & p95).
  4. Consider metadata and/or sprite optimization if needed.

Notes

  • Icons are simple originals under the repository’s Apache 2.0 license.
  • Kept intentionally minimal: no metadata/gallery yet.
  • Benchmark script is dev-only and excluded from builds.

Refs #2857

@github-actions
Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 +0.2% [-0.7%, +1.1%] 🔵 -0.4% [-0.5%, -0.2%]
BoltzmannWealth large 🔵 -1.0% [-2.3%, +0.2%] 🔵 +1.0% [-1.8%, +3.4%]
Schelling small 🔵 +0.2% [-0.1%, +0.5%] 🔵 +1.3% [+0.8%, +1.8%]
Schelling large 🔵 -0.0% [-0.5%, +0.4%] 🔵 +1.4% [-0.1%, +2.7%]
WolfSheep small 🔵 +0.9% [+0.6%, +1.1%] 🔵 +0.5% [+0.3%, +0.8%]
WolfSheep large 🔵 +0.1% [-1.1%, +1.3%] 🔵 -1.0% [-1.9%, -0.2%]
BoidFlockers small 🔵 -1.6% [-2.4%, -0.8%] 🔵 -0.1% [-0.3%, +0.1%]
BoidFlockers large 🔵 -1.6% [-2.3%, -0.9%] 🔵 -0.9% [-1.3%, -0.6%]

@EwoutH
Copy link
Member

EwoutH commented Nov 29, 2025

Thanks for the PR, especially with the different agent count benchmarks.

Could you compare the SVG drawing to the drawing of the default marker?

@Mani212005
Copy link
Author

Sure, I would be happy to do it.

@Mani212005
Copy link
Author

Comparison: Default Marker vs Rasterized SVG (Pillow composition)

Setup:

  • Canvas: 800×600
  • Per-agent size: 32×32
  • Mode “marker”: ImageDraw.ellipse (filled circle)
  • Mode “icon”: pre-rasterized SVG (cairosvg once), per-agent alpha_composite
  • Environment: Python 3.13 (macOS), local machine

Results:

N agents Mode Avg frame time (ms) P95 frame time (ms) Notes
100 marker 0.084 0.104
100 icon 0.380 0.443 SVG convert ~0.014 s
500 marker 0.370 0.420
500 icon 1.906 1.994 SVG convert ~0.011 s
1000 marker 0.731 0.769
1000 icon 3.814 3.940 SVG convert ~0.012 s

Observations:

  • The default marker is ~4–5× faster than rasterized SVG composition at N=100, and both scale roughly linearly with N.
  • Rasterized SVG composition remains modest in absolute terms (~3.8 ms at N=1000 on this machine), but is consistently more expensive than drawing the primitive marker.
  • The one-time SVG → PNG conversion cost is small (~11–14 ms) and amortized across frames.

Next steps:

  • I’ll include a browser-side benchmark in the follow-up PR comparing: Should I do it ?
    • Canvas primitive markers vs cached icon blits (ImageBitmap/drawImage)
    • Inline SVG rendering
  • That should provide a more direct comparison for the web-based visualization path.

@EwoutH let me know if you’d like additional sizes (e.g., N=2000) or alternative marker shapes/colors for completeness.

@EwoutH
Copy link
Member

EwoutH commented Nov 29, 2025

Thanks. Can you give a screenshot how the benchmark looks?

@Mani212005
Copy link
Author

Yes, doing it

@Mani212005
Copy link
Author

Screenshot 2025-11-30 at 6 58 43 PM Screenshot 2025-11-30 at 7 01 03 PM Screenshot 2025-11-30 at 7 02 14 PM

Sorry for the delay. Can I now build the Frontend for this ?

@EwoutH EwoutH marked this pull request as draft November 30, 2025 19:02
@EwoutH
Copy link
Member

EwoutH commented Nov 30, 2025

Thanks.

Can you explain how this integrates with our Solara visualisation and render backends? Or haven't you implemented that yet?

@Mani212005
Copy link
Author

Thanks! I haven’t implemented the Solara integration yet in this PR. Here’s my plan for the follow-up :

Integration plan (Solara + render backends)

  • Solara (ComponentsView)

    • Add a per-agent (or per-layer) render option to select:
      • default (ellipse marker)
      • icon:<name> (bundled SVG)
      • custom (user-supplied)
    • Canvas backend: cache rasterized icons (ImageBitmap) once, then draw per agent via drawImage. Avoid any per-frame SVG parsing.
    • Inline-SVG backend: reference the icon as <use> or <image> (data URL). Prefer Canvas for large N to reduce DOM overhead.
  • Backend considerations

    • Canvas 2D: icon caching + batched draws; scale via drawImage. Color variants via pre-tinted bitmaps or simple composition (phase 2).
    • Inline SVG: fine for small agent counts; we’ll document the trade-off vs Canvas for larger N.
    • Fallback: if an icon is missing/not loaded, fall back to the current default marker.
  • API surface

    • Introduce an optional rendering config read by ComponentsView, e.g.:
      render_style = {"type": "icon", "name": "smiley", "size": 32}
    • Defaults unchanged so existing models render exactly as today unless users opt in.
  • Performance

    • Add a browser-side benchmark page comparing:
      • Canvas primitive markers
      • Canvas cached icon blits (drawImage)
      • Inline SVG (<use>)
    • Complement the Python results in this PR; preload/cache icons to avoid runtime stalls.

What do you think about this ?

@EwoutH
Copy link
Member

EwoutH commented Nov 30, 2025

Observations:

  • The default marker is ~4–5× faster than rasterized SVG composition at N=100, and both scale roughly linearly with N.
  • Rasterized SVG composition remains modest in absolute terms (~3.8 ms at N=1000 on this machine), but is consistently more expensive than drawing the primitive marker.
  • The one-time SVG → PNG conversion cost is small (~11–14 ms) and amortized across frames.

Assuming these numbers and observations transfer to our Solara visualization, can we do anything to optimize it? 4-5x overhead sounds like quite a lot.

@Mani212005
Copy link
Author

Yes, for sure. I think we can definitely optimize to guarantee performance, immediate win is caching. We should ensure we only rasterize the SVG to a bitmap once , rather than per frame.

@Mani212005
Copy link
Author

I have optimized it changes :

  • altair_backend.py:
    • Preserve original portrayal objects (“portrayals”) so SpaceRenderer can enrich with icon metadata.
    • Suppress warnings for icon/icon_size keys.
    • Render icons when arguments include icon_rasters (data URLs) by layering mark_image with mark_point.
  • Benchmarks:
    • backend_space_benchmark.py: single-run timing of per-frame render
    • backend_space_benchmark_matrix.py: batch runs producing Markdown tables for multiple N and modes

Benchmark Results (120 frames)

Backend Mode N Avg ms P95 ms Icon Overhead
Altair marker 100 15.02 15.45 baseline
Altair icon 100 15.10 15.56 +0.5%
Altair marker 500 22.77 22.98 baseline
Altair icon 500 22.72 23.32 -0.2%
Altair marker 1000 31.49 32.08 baseline
Altair icon 1000 31.97 32.66 +1.5%
Altair marker 2000 50.96 51.47 baseline
Altair icon 2000 51.57 52.17 +1.2%

Benchmark Results (60 frames)

Backend Mode N Avg ms P95 ms Icon Overhead
Altair marker 100 15.35 15.68 baseline
Altair icon 100 15.35 15.79 0%
Altair marker 500 22.48 22.97 baseline
Altair icon 500 23.16 23.34 +3.0%
Altair marker 1000 31.72 32.24 baseline
Altair icon 1000 32.07 32.71 +1.1%

Average icon overhead ~0.9–1.5% across test matrix (well below target of <50%).

@EwoutH
Copy link
Member

EwoutH commented Dec 2, 2025

That’s quite amazing! Thanks for the detailed benchmarks.

If you want you can move forward with Solara integration. Try to keep the implementation minimal / code complexity low. You can do that in a new PR if you want.

@Mani212005
Copy link
Author

Thankyou, would be happy to do that

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