File size: 3,863 Bytes
ef78361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""UMAP ํ† ํ”ฝ ๋งต ์‹œ๊ฐํ™” (Plotly scatter).

snapshot.coordinates: [{cluster_id, x, y, size, label}]
clusters: [{id, cluster_label, attention_score, citation_density, opportunity_score, fanout_count, ...}]
"""

import streamlit as st
import plotly.graph_objects as go


def render_topic_map(clusters: list[dict], snapshot: dict | None, frame: str = "all"):
    """Render UMAP 2D scatter from snapshot coordinates + cluster metadata."""
    if not snapshot or not snapshot.get("coordinates"):
        st.info("UMAP ์ขŒํ‘œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํด๋Ÿฌ์Šคํ„ฐ๋ง ์‹คํ–‰ ํ›„ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.")
        return

    # Frame-specific guide
    if frame == "demand":
        st.markdown("""
๊ฐ ์ ์€ ํ•˜๋‚˜์˜ **Demand ํ† ํ”ฝ** (ChatGPT sub-query ๊ทธ๋ฃน)์ž…๋‹ˆ๋‹ค.
- **์  ํฌ๊ธฐ**: ํ•ด๋‹น ํ† ํ”ฝ์˜ fanout ์ˆ˜ (ํด์ˆ˜๋ก ์†Œ๋น„์ž๊ฐ€ ์ž์ฃผ ๋ฌป๋Š” ํ† ํ”ฝ)
- **์  ์ƒ‰์ƒ**: ๊ธฐํšŒ ์ ์ˆ˜ (๋นจ๊ฐ• = ๊ธฐํšŒ ํผ, ๋…ธ๋ž‘ = ๋ณดํ†ต)
- **๊ฐ€๊นŒ์ด ์žˆ๋Š” ์ **: ์œ ์‚ฌํ•œ ๊ฒ€์ƒ‰ ์˜๋„์˜ ํ† ํ”ฝ
""")
    elif frame == "supply":
        st.markdown("""
๊ฐ ์ ์€ ํ•˜๋‚˜์˜ **Supply ํ† ํ”ฝ** (Gemini citation quote ๊ทธ๋ฃน)์ž…๋‹ˆ๋‹ค.
- **์  ํฌ๊ธฐ**: ํ•ด๋‹น ํ† ํ”ฝ์˜ ์ธ์šฉ ์ˆ˜ (ํด์ˆ˜๋ก AI๊ฐ€ ์ž์ฃผ ์ธ์šฉํ•˜๋Š” ํ† ํ”ฝ)
- **์  ์ƒ‰์ƒ**: ๊ธฐํšŒ ์ ์ˆ˜ (๋นจ๊ฐ• = ๊ธฐํšŒ ํผ, ๋…ธ๋ž‘ = ๋ณดํ†ต)
- **๊ฐ€๊นŒ์ด ์žˆ๋Š” ์ **: ์œ ์‚ฌํ•œ ์ธ์šฉ ์ฃผ์ œ์˜ ํ† ํ”ฝ
""")
    else:
        st.markdown("""
๊ฐ ์ ์€ ํ•˜๋‚˜์˜ **ํ† ํ”ฝ**(AI ์ถ”๊ฐ€ ์งˆ๋ฌธ ๊ทธ๋ฃน)์ž…๋‹ˆ๋‹ค.
- **์  ํฌ๊ธฐ**: ํ•ด๋‹น ํ† ํ”ฝ์˜ AI ์ถ”๊ฐ€ ์งˆ๋ฌธ ์ˆ˜ (ํด์ˆ˜๋ก AI๊ฐ€ ์ž์ฃผ ๋ฌป๋Š” ํ† ํ”ฝ)
- **์  ์ƒ‰์ƒ**: ๊ธฐํšŒ ์ ์ˆ˜ (๋นจ๊ฐ• = ๊ธฐํšŒ ํผ, ๋…ธ๋ž‘ = ๋ณดํ†ต)
- **๊ฐ€๊นŒ์ด ์žˆ๋Š” ์ **: ์œ ์‚ฌํ•œ ์ฃผ์ œ์˜ ํ† ํ”ฝ
""")

    count_label = "Citations" if frame == "supply" else "Fanouts"
    coords = snapshot["coordinates"]

    # Build cluster lookup by id
    cluster_map = {c["id"]: c for c in clusters}

    # Merge coordinate data with cluster metadata
    xs, ys, sizes, colors, hover_texts = [], [], [], [], []

    for pt in coords:
        cid = pt.get("cluster_id")
        meta = cluster_map.get(cid, {})

        xs.append(pt["x"])
        ys.append(pt["y"])

        fanout_count = pt.get("size", meta.get("fanout_count", 10))
        # Normalize size for display (min 5, max 40)
        norm_size = max(5, min(40, fanout_count / 5))
        sizes.append(norm_size)

        opp = float(meta.get("opportunity_score", 0) or 0)
        colors.append(opp)

        label = meta.get("cluster_label") or f"Cluster {pt.get('label', '?')}"
        attn = float(meta.get("attention_score", 0) or 0)
        density = float(meta.get("citation_density", 0) or 0)

        hover_texts.append(
            f"<b>{label}</b><br>"
            f"Attention: {attn:.4f}<br>"
            f"Density: {density:.4f}<br>"
            f"Opportunity: {opp:.4f}<br>"
            f"{count_label}: {fanout_count}"
        )

    fig = go.Figure()
    fig.add_trace(go.Scatter(
        x=xs,
        y=ys,
        mode="markers",
        marker=dict(
            size=sizes,
            color=colors,
            colorscale="YlOrRd",
            colorbar=dict(title="Opportunity"),
            opacity=0.7,
            line=dict(width=0.5, color="#333"),
        ),
        text=hover_texts,
        hoverinfo="text",
    ))

    fig.update_layout(
        title="AI ํ† ํ”ฝ ๋งต (UMAP 2D Projection)",
        xaxis=dict(title="UMAP-1", showgrid=False, zeroline=False),
        yaxis=dict(title="UMAP-2", showgrid=False, zeroline=False),
        height=600,
        template="plotly_white",
        hoverlabel=dict(bgcolor="white", font_size=12),
    )

    st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})

    # Algorithm params info
    params = snapshot.get("algorithm_params")
    if params:
        with st.expander("๋ถ„์„ ์„ค์ • (๊ธฐ์ˆ  ์ƒ์„ธ)"):
            st.json(params)