File size: 4,821 Bytes
8e56712
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import graphviz
import json
from tempfile import NamedTemporaryFile
import os
from graph_generator_utils import add_nodes_and_edges

def generate_radial_diagram(json_input: str, output_format: str) -> str:
    """
    Generates a radial (center-expanded) diagram from JSON input.

    Args:
        json_input (str): A JSON string describing the radial diagram structure.
                          It must follow the Expected JSON Format Example below.

    Expected JSON Format Example:
    {
      "central_node": "AI Core Concepts & Domains",
      "nodes": [
        {
          "id": "foundational_ml",
          "label": "Foundational ML",
          "relationship": "builds on",
          "subnodes": [
            {"id": "supervised_l", "label": "Supervised Learning", "relationship": "e.g."},
            {"id": "unsupervised_l", "label": "Unsupervised Learning", "relationship": "e.g."}
          ]
        },
        {
          "id": "dl_architectures",
          "label": "Deep Learning Arch.",
          "relationship": "evolved from",
          "subnodes": [
            {"id": "cnns_rad", "label": "CNNs", "relationship": "e.g."},
            {"id": "rnns_rad", "label": "RNNs", "relationship": "e.g."}
          ]
        },
        {
          "id": "major_applications",
          "label": "Major AI Applications",
          "relationship": "applied in",
          "subnodes": [
            {"id": "nlp_rad", "label": "Natural Language Processing", "relationship": "e.g."},
            {"id": "cv_rad", "label": "Computer Vision", "relationship": "e.g."}
          ]
        },
        {
          "id": "ethical_concerns",
          "label": "Ethical AI Concerns",
          "relationship": "addresses",
          "subnodes": [
            {"id": "fairness_rad", "label": "Fairness & Bias", "relationship": "e.g."},
            {"id": "explainability", "label": "Explainability (XAI)", "relationship": "e.g."}
          ]
        },
        {
          "id": "future_trends",
          "label": "Future AI Trends",
          "relationship": "looking at",
          "subnodes": [
            {"id": "agi_future", "label": "AGI Development", "relationship": "e.g."},
            {"id": "quantum_ai", "label": "Quantum AI", "relationship": "e.g."}
          ]
        }
      ]
    }

    Returns:
        str: The filepath to the generated PNG image file.
    """
    try:
        if not json_input.strip():
            return "Error: Empty input"
            
        data = json.loads(json_input)
        
        if 'central_node' not in data or 'nodes' not in data:
            raise ValueError("Missing required fields: central_node or nodes")

        # ํ•œ๊ธ€ ํฐํŠธ ์„ค์ •
        # GDFONTPATH๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ํฐํŠธ ํŒŒ์ผ๋ช…(ํ™•์žฅ์ž ์ œ์™ธ) ์‚ฌ์šฉ
        korean_font = 'NanumGothic-Regular'

        dot = graphviz.Digraph(
            name='RadialDiagram',
            format='png',
            engine='neato', # Use 'neato' or 'fdp' for radial/force-directed layout
            graph_attr={
                'overlap': 'false',     # Prevent node overlap
                'splines': 'true',      # Smooth splines for edges
                'bgcolor': 'white',     # White background
                'pad': '0.5',          # Padding around the graph
                'layout': 'neato',      # Explicitly set layout engine for consistency
                'fontname': korean_font,  # ๊ทธ๋ž˜ํ”„ ์ „์ฒด ํ•œ๊ธ€ ํฐํŠธ
                'charset': 'UTF-8'      # UTF-8 ์ธ์ฝ”๋”ฉ
            },
            node_attr={
                'fixedsize': 'false',   # Allow nodes to resize based on content
                'fontname': korean_font  # ๋ชจ๋“  ๋…ธ๋“œ์˜ ๊ธฐ๋ณธ ํฐํŠธ
            },
            edge_attr={
                'fontname': korean_font  # ๋ชจ๋“  ์—ฃ์ง€์˜ ๊ธฐ๋ณธ ํฐํŠธ
            }
        )
        
        base_color = '#19191a' # Hardcoded base color

        dot.node(
            'central',
            data['central_node'],
            shape='box',            # Rectangular shape
            style='filled,rounded', # Filled and rounded corners
            fillcolor=base_color,   # Darkest color
            fontcolor='white',      # White text for dark background
            fontsize='16',          # Larger font for central node
            fontname=korean_font    # ํ•œ๊ธ€ ํฐํŠธ ๋ช…์‹œ์  ์ง€์ •
        )
        
        add_nodes_and_edges(dot, 'central', data.get('nodes', []), current_depth=1, base_color=base_color)

        with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
            dot.render(tmp.name, format=output_format, cleanup=True)
            return f"{tmp.name}.{output_format}"

    except json.JSONDecodeError:
        return "Error: Invalid JSON format"
    except Exception as e:
        return f"Error: {str(e)}"