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, base_color: str) -> str: # base_color is now correctly used """ 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. base_color (str): The hexadecimal color string (e.g., '#19191a') for the base color of the nodes, from which a gradient will be generated. Returns: str: The filepath to the generated PNG image file. Expected JSON Format Example: { "central_node": "Making Coffee", "nodes": [ { "id": "coffee_components", "label": "Coffee Components", "relationship": "Involves", "subnodes": [ { "id": "equipment", "label": "Equipment Needed", "relationship": "Requires", "subnodes": [ {"id": "coffee_maker", "label": "Coffee Maker", "relationship": "e.g."}, {"id": "mug", "label": "Mug", "relationship": "For"} ] } ] }, { "id": "coffee_process_enjoyment", "label": "Process & Enjoyment", "relationship": "Leads to", "subnodes": [ { "id": "preparation_steps", "label": "Preparation Steps", "relationship": "Involves", "subnodes": [ {"id": "grind_beans", "label": "Grind Beans", "relationship": "Step 1 (if whole)"}, {"id": "heat_water", "label": "Heat Water", "relationship": "Step 2"} ] } ] } ] } """ 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") 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 }, node_attr={ 'fixedsize': 'false' # Allow nodes to resize based on content } ) # This line was REMOVED to ensure the passed base_color is used: base_color = '#19191a' # Central node styling (rounded box, dark 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 ) # Add child nodes and edges recursively starting from depth 1 # The add_nodes_and_edges function will handle styling consistent with other graphs add_nodes_and_edges(dot, 'central', data.get('nodes', []), current_depth=1, base_color=base_color) # Save to temporary file with NamedTemporaryFile(delete=False, suffix='.png') as tmp: dot.render(tmp.name, format='png', cleanup=True) return tmp.name + '.png' except json.JSONDecodeError: return "Error: Invalid JSON format" except Exception as e: return f"Error: {str(e)}"