File size: 4,203 Bytes
78c571c
3ab07bd
78c571c
 
 
3ab07bd
78c571c
 
3ab07bd
 
 
 
78c571c
3ab07bd
 
 
78c571c
 
 
3ab07bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78c571c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""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