feat(dashboard): aquarium 2D visualization (fish per agent, size by fitness)
Nuova pagina Streamlit "Aquarium" che renderizza ogni genoma come pesce animato su canvas HTML5: dimensione proporzionale alla fitness, colore per cognitive_style, halo per i top-3. Helper puro-Python testabile per costruire dataset e HTML self-contained (no CDN). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import streamlit as st
|
||||
import streamlit.components.v1 as components
|
||||
|
||||
from multi_swarm.dashboard.aquarium import (
|
||||
STYLE_COLORS,
|
||||
build_aquarium_html,
|
||||
build_fish_dataset,
|
||||
)
|
||||
from multi_swarm.dashboard.data import (
|
||||
evaluations_df,
|
||||
generations_df,
|
||||
genomes_df,
|
||||
get_repo,
|
||||
list_runs_df,
|
||||
)
|
||||
|
||||
st.title("Aquarium 2D")
|
||||
st.caption(
|
||||
"Ogni genoma è un pesce: dimensione proporzionale alla fitness, "
|
||||
"colore per cognitive_style."
|
||||
)
|
||||
|
||||
db_path = st.session_state.get("db_path", "./runs.db")
|
||||
repo = get_repo(db_path)
|
||||
|
||||
runs = list_runs_df(repo)
|
||||
if runs.empty:
|
||||
st.info("Nessuna run.")
|
||||
st.stop()
|
||||
|
||||
selected = st.selectbox("Run", runs["id"].tolist())
|
||||
|
||||
gens = generations_df(repo, selected)
|
||||
gen_options: list[int | None] = [None]
|
||||
if not gens.empty:
|
||||
gen_options.extend(sorted(gens["generation_idx"].unique().tolist()))
|
||||
|
||||
def _fmt_gen(v: int | None) -> str:
|
||||
return "tutte le generazioni" if v is None else f"gen {v}"
|
||||
|
||||
# Default to latest gen if available.
|
||||
default_idx = len(gen_options) - 1 if len(gen_options) > 1 else 0
|
||||
gen_choice = st.selectbox(
|
||||
"Generazione", gen_options, index=default_idx, format_func=_fmt_gen
|
||||
)
|
||||
|
||||
with st.sidebar:
|
||||
st.subheader("Aquarium controls")
|
||||
max_fish = st.slider("Max pesci", min_value=5, max_value=100, value=30, step=1)
|
||||
show_labels = st.toggle("Mostra ID genoma", value=False)
|
||||
if st.button("Refresh"):
|
||||
st.rerun()
|
||||
|
||||
evals = evaluations_df(repo, selected)
|
||||
genomes = genomes_df(repo, selected, generation_idx=gen_choice)
|
||||
|
||||
if evals.empty or genomes.empty:
|
||||
st.warning("Nessun dato disponibile per questa run/generazione.")
|
||||
st.stop()
|
||||
|
||||
merged = evals.merge(
|
||||
genomes, left_on="genome_id", right_on="id", how="inner", suffixes=("", "_g")
|
||||
)
|
||||
if merged.empty:
|
||||
st.warning("Nessuna evaluation associata ai genomi della generazione scelta.")
|
||||
st.stop()
|
||||
|
||||
fish = build_fish_dataset(merged, max_fish=max_fish)
|
||||
st.write(f"Visualizzati {len(fish)} pesci (top per fitness).")
|
||||
|
||||
html_str = build_aquarium_html(
|
||||
fish, canvas_w=1000, canvas_h=600, show_labels=show_labels
|
||||
)
|
||||
components.html(html_str, height=620, scrolling=False)
|
||||
|
||||
with st.expander("Legenda colori"):
|
||||
legend_md = "\n".join(
|
||||
f"- <span style='color:{color};font-weight:bold;'>●</span> "
|
||||
f"`{style}`"
|
||||
for style, color in STYLE_COLORS.items()
|
||||
)
|
||||
st.markdown(legend_md, unsafe_allow_html=True)
|
||||
Reference in New Issue
Block a user