Spaces:
Sleeping
Sleeping
| """Plotly figures for the deck-builder Space. | |
| Three breakdown charts displayed alongside a generated deck: | |
| - cost curve (bar): quantities by cost bucket vs. the target curve | |
| - type breakdown (bar): Character/Event/Stage counts | |
| - color breakdown (bar): cards per color | |
| Color hex map mirrors the upstream `optcg_cards.visualize._first_color_hex` | |
| so palettes stay consistent across all OPTCG-related Spaces. | |
| """ | |
| from __future__ import annotations | |
| from typing import TYPE_CHECKING | |
| from optcg_cards.visualize import _first_color_hex | |
| if TYPE_CHECKING: | |
| from spaceutil.deck import Deck | |
| def _empty_fig(message: str, height: int = 240): | |
| import plotly.graph_objects as go | |
| fig = go.Figure() | |
| fig.add_annotation( | |
| text=message, | |
| 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=40, b=40), | |
| height=height, | |
| ) | |
| return fig | |
| def build_cost_curve_figure(deck: Deck | None, height: int = 280): | |
| """Bar chart comparing the deck's actual cost curve vs. the style target.""" | |
| import plotly.graph_objects as go | |
| if deck is None or deck.total_quantity == 0: | |
| return _empty_fig("Build a deck to see its cost curve.", height=height) | |
| actual = deck.cost_distribution | |
| target = deck.target_curve or {} | |
| buckets = sorted(set(actual) | set(target)) | |
| labels = [("8+" if b == 8 else str(b)) for b in buckets] | |
| actual_y = [actual.get(b, 0) for b in buckets] | |
| target_y = [target.get(b, 0) for b in buckets] | |
| fig = go.Figure(data=[ | |
| go.Bar(name="Actual", x=labels, y=actual_y, marker_color="#dc3545"), | |
| go.Bar(name="Target", x=labels, y=target_y, marker_color="#1f77b4", opacity=0.5), | |
| ]) | |
| fig.update_layout( | |
| title="Cost curve: actual vs. target", | |
| xaxis=dict(title="Cost"), | |
| yaxis=dict(title="Cards"), | |
| barmode="group", | |
| plot_bgcolor="white", | |
| margin=dict(l=40, r=40, t=60, b=40), | |
| height=height, | |
| legend=dict(orientation="h", y=1.0, yanchor="bottom"), | |
| ) | |
| return fig | |
| def build_type_breakdown_figure(deck: Deck | None, height: int = 240): | |
| import plotly.graph_objects as go | |
| if deck is None or deck.total_quantity == 0: | |
| return _empty_fig("Build a deck to see its type mix.", height=height) | |
| dist = deck.type_distribution | |
| types = sorted(dist.keys()) | |
| counts = [dist[t] for t in types] | |
| fig = go.Figure(data=[go.Bar(x=types, y=counts, marker_color="#6c757d")]) | |
| fig.update_layout( | |
| title="Card type mix", | |
| xaxis=dict(title=""), | |
| yaxis=dict(title="Cards"), | |
| plot_bgcolor="white", | |
| margin=dict(l=40, r=40, t=60, b=40), | |
| height=height, | |
| ) | |
| return fig | |
| def build_color_breakdown_figure(deck: Deck | None, height: int = 240): | |
| import plotly.graph_objects as go | |
| if deck is None or deck.total_quantity == 0: | |
| return _empty_fig("Build a deck to see its color mix.", height=height) | |
| dist = deck.color_distribution | |
| colors = sorted(dist.keys()) | |
| counts = [dist[c] for c in colors] | |
| bar_colors = [_first_color_hex([c]) for c in colors] | |
| fig = go.Figure(data=[go.Bar(x=colors, y=counts, marker_color=bar_colors)]) | |
| fig.update_layout( | |
| title="Color mix (per copy)", | |
| xaxis=dict(title=""), | |
| yaxis=dict(title="Cards (counted per color of multicolor cards)"), | |
| plot_bgcolor="white", | |
| margin=dict(l=40, r=40, t=60, b=40), | |
| height=height, | |
| ) | |
| return fig | |