Spaces:
Sleeping
Sleeping
| """Plotly figures used by the Gradio Space. | |
| `build_umap_figure` adapts `optcg_cards.visualize.render_html` | |
| (visualize.py:109-128) - returns a Figure rather than writing HTML, and | |
| overlays a second trace for search hits. | |
| `build_cost_curve_figure` plots a synergy recommendation set's | |
| cost-distribution as a bar chart for the Synergy Inspector tab. | |
| """ | |
| from __future__ import annotations | |
| from typing import TYPE_CHECKING, Any | |
| from optcg_cards.visualize import _first_color_hex, _hover_label | |
| if TYPE_CHECKING: | |
| from spaceutil.synergy import SynergyHit | |
| def build_umap_figure( | |
| cards: list[dict[str, Any]], | |
| highlight_indices: list[int] | None = None, | |
| title: str = "OPTCG Card Embedding Map", | |
| ): | |
| import plotly.graph_objects as go | |
| if not cards: | |
| raise ValueError("No cards to render") | |
| if "umap_x" not in cards[0] or "umap_y" not in cards[0]: | |
| raise ValueError("Cards must have umap_x/umap_y; cannot plot") | |
| xs = [c["umap_x"] for c in cards] | |
| ys = [c["umap_y"] for c in cards] | |
| colors = [_first_color_hex(c.get("colors", [])) for c in cards] | |
| hover = [_hover_label(c) for c in cards] | |
| base_customdata = [[i] for i in range(len(cards))] | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=xs, | |
| y=ys, | |
| mode="markers", | |
| marker=dict(size=5, color=colors, opacity=0.4), | |
| text=hover, | |
| hoverinfo="text", | |
| customdata=base_customdata, | |
| name="all cards", | |
| ) | |
| ) | |
| if highlight_indices: | |
| hi_x = [cards[i]["umap_x"] for i in highlight_indices] | |
| hi_y = [cards[i]["umap_y"] for i in highlight_indices] | |
| hi_colors = [_first_color_hex(cards[i].get("colors", [])) for i in highlight_indices] | |
| hi_hover = [ | |
| f"<b>Rank {rank + 1}</b><br>{_hover_label(cards[i])}" | |
| for rank, i in enumerate(highlight_indices) | |
| ] | |
| hi_customdata = [[idx, rank + 1] for rank, idx in enumerate(highlight_indices)] | |
| fig.add_trace( | |
| go.Scatter( | |
| x=hi_x, | |
| y=hi_y, | |
| mode="markers", | |
| marker=dict( | |
| size=14, | |
| color=hi_colors, | |
| opacity=1.0, | |
| line=dict(width=2, color="black"), | |
| ), | |
| text=hi_hover, | |
| hoverinfo="text", | |
| customdata=hi_customdata, | |
| name="search hits", | |
| ) | |
| ) | |
| fig.update_layout( | |
| title=title, | |
| xaxis=dict(title="UMAP-1", showgrid=False, zeroline=False), | |
| yaxis=dict(title="UMAP-2", showgrid=False, zeroline=False), | |
| plot_bgcolor="white", | |
| showlegend=False, | |
| margin=dict(l=40, r=40, t=60, b=40), | |
| ) | |
| return fig | |
| def build_cost_curve_figure( | |
| hits: list[SynergyHit], | |
| title: str = "Cost curve of recommendations", | |
| ): | |
| """Bar chart of synergy recommendations grouped by cost. | |
| Costs above 10 are clamped into a single "10+" bucket so a stray | |
| high-cost finisher doesn't stretch the X axis.""" | |
| import plotly.graph_objects as go | |
| from spaceutil.synergy import cost_curve | |
| counts = cost_curve(hits, max_cost=10) | |
| if not counts: | |
| fig = go.Figure() | |
| fig.add_annotation( | |
| text="No cost data yet - pick a leader to see recommendations.", | |
| xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, | |
| font=dict(color="gray"), | |
| ) | |
| fig.update_layout( | |
| xaxis=dict(visible=False), | |
| yaxis=dict(visible=False), | |
| plot_bgcolor="white", | |
| margin=dict(l=40, r=40, t=60, b=40), | |
| height=260, | |
| ) | |
| return fig | |
| costs = sorted(counts.keys()) | |
| labels = [("10+" if c == 10 else str(c)) for c in costs] | |
| values = [counts[c] for c in costs] | |
| fig = go.Figure(data=[go.Bar(x=labels, y=values, marker_color="#dc3545")]) | |
| fig.update_layout( | |
| title=title, | |
| xaxis=dict(title="Cost"), | |
| yaxis=dict(title="Count of recommendations"), | |
| plot_bgcolor="white", | |
| margin=dict(l=40, r=40, t=60, b=40), | |
| height=260, | |
| ) | |
| return fig | |