import graphviz import json from tempfile import NamedTemporaryFile import os from graph_generator_utils import add_nodes_and_edges def generate_synoptic_chart(json_input: str) -> str: # Removed base_color parameter """ Generates a synoptic chart (horizontal flowchart) from JSON input. Args: json_input (str): A JSON string describing the synoptic chart structure. It must follow the Expected JSON Format Example below. Returns: str: The filepath to the generated PNG image file. Expected JSON Format Example: { "central_node": "AI Project Lifecycle", "nodes": [ { "id": "phase1", "label": "I. Problem Definition & Data Acquisition", "relationship": "Starts\\nwith", "subnodes": [ { "id": "sub1_1", "label": "1. Problem Formulation", "relationship": "Involves\\n(Steps)", "subnodes": [ {"id": "sub1_1_1", "label": "1.1. Identify Business Need", "relationship": "e.g.\\n(Need)"}, {"id": "sub1_1_2", "label": "1.2. Define KPIs", "relationship": "e.g.\\n(Metrics)"} ] } ] }, { "id": "phase2", "label": "II. Model Development", "relationship": "Proceeds\\nto", "subnodes": [ { "id": "sub2_1", "label": "1. Feature Engineering", "relationship": "Comprises\\n(Features)", "subnodes": [ {"id": "sub2_1_1", "label": "1.1. Feature Selection", "relationship": "e.g.\\n(Select)"} ] } ] } ] } """ 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='SynopticChart', format='png', graph_attr={ 'rankdir': 'LR', # Left-to-Right layout (horizontal hierarchy) 'splines': 'ortho', # Straight lines 'bgcolor': 'white', # White background 'pad': '0.5', # Padding around the graph 'ranksep': '0.7', # Reduced horizontal separation between ranks (columns) 'nodesep': '0.3' # Adjusted vertical separation between nodes in the same rank } ) base_color = '#19191a' # Hardcoded base color # 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 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)}"