File size: 6,489 Bytes
7c43635 028a336 27e273f 7c43635 028a336 7c43635 bf5eed8 028a336 7c43635 028a336 7c43635 7a5d3f8 7c43635 27e273f 7c43635 27e273f 7c43635 27e273f 7c43635 27e273f 7a5d3f8 7c43635 27e273f 7c43635 27e273f 89c005a 7a5d3f8 27e273f 7a5d3f8 27e273f 7a5d3f8 27e273f 89c005a 27e273f 7a5d3f8 7c43635 028a336 7c43635 9912372 7c43635 27e273f 028a336 7c43635 7e08bc6 028a336 7e08bc6 028a336 7c43635 7a5d3f8 7c43635 9912372 7c43635 |
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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
import gradio as gr
import json
from graphviz import Digraph
import os
from tempfile import NamedTemporaryFile
from sample_data import COMPLEX_SAMPLE_JSON # El JSON de ejemplo se mantiene sin cambios
def generate_concept_map(json_input: str) -> str:
"""
Generate concept map from JSON and return as image file
Args:
json_input (str): JSON describing the concept map structure.
REQUIRED FORMAT EXAMPLE:
{
"central_node": "AI",
"nodes": [
{
"id": "ml",
"label": "Machine Learning",
"relationship": "subcategory",
"subnodes": [
{
"id": "dl",
"label": "Deep Learning",
"relationship": "type",
"subnodes": [
{
"id": "cnn",
"label": "CNN",
"relationship": "architecture"
}
]
}
]
}
]
}
Returns:
str: Path to 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")
dot = Digraph(
name='ConceptMap',
format='png',
graph_attr={
'rankdir': 'TB', # Top-to-Bottom (layout de arriba a abajo)
'splines': 'ortho', # Líneas rectas
'bgcolor': 'white', # Fondo blanco
'pad': '0.5', # Margen interno (padding)
'ranksep': '1.8', # ¡Aumenta la separación vertical entre niveles! (más alto)
'ratio': '1.2' # ¡Relación de aspecto (altura/anchura)! >1 para más alto que ancho
}
)
# Base color for the central node
base_color = '#19191a' # Casi negro
# Central node (now a rounded box)
dot.node(
'central',
data['central_node'],
shape='box', # Ahora es un rectángulo
style='filled,rounded', # Redondeado
fillcolor=base_color, # Color base
fontcolor='white',
fontsize='16' # Un poco más grande para el título
)
# Helper function to recursively add nodes and edges
def add_nodes_and_edges(parent_id, nodes_list, current_depth=0):
# Calculate color for current depth, making it lighter
lightening_factor = 0.12
# Convert base_color hex to RGB
base_r = int(base_color[1:3], 16)
base_g = int(base_color[3:5], 16)
base_b = int(base_color[5:7], 16)
# Calculate current node color
current_r = base_r + int((255 - base_r) * current_depth * lightening_factor)
current_g = base_g + int((255 - base_g) * current_depth * lightening_factor)
current_b = base_b + int((255 - base_b) * current_depth * lightening_factor)
# Clamp values to 255
current_r = min(255, current_r)
current_g = min(255, current_g)
current_b = min(255, current_b)
node_fill_color = f'#{current_r:02x}{current_g:02x}{current_b:02x}'
# Font color: white for dark nodes, black for very light nodes
font_color = 'white' if current_depth * lightening_factor < 0.6 else 'black'
edge_color = '#4a4a4a'
font_size = max(9, 14 - (current_depth * 2))
edge_font_size = max(7, 10 - (current_depth * 1))
for node in nodes_list:
node_id = node.get('id')
label = node.get('label')
relationship = node.get('relationship')
if not all([node_id, label, relationship]):
raise ValueError(f"Invalid node: {node}")
dot.node(
node_id,
label,
shape='box',
style='filled,rounded',
fillcolor=node_fill_color,
fontcolor=font_color,
fontsize=str(font_size)
)
dot.edge(
parent_id,
node_id,
label=relationship,
color=edge_color,
fontcolor=edge_color,
fontsize=str(edge_font_size)
)
if 'subnodes' in node:
add_nodes_and_edges(node_id, node['subnodes'], current_depth + 1)
# Start processing from the top-level nodes connected to the central node
add_nodes_and_edges('central', data.get('nodes', []), current_depth=1)
# 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)}"
if __name__ == "__main__":
demo = gr.Interface(
fn=generate_concept_map,
inputs=gr.Textbox(
value=COMPLEX_SAMPLE_JSON,
placeholder="Paste JSON following the documented format",
label="Structured JSON Input",
lines=25
),
outputs=gr.Image(
label="Generated Concept Map",
type="filepath",
show_download_button=True
),
title="AI Concept Map (Optimized Proportions)",
description="Generates an AI concept map with custom rounded boxes, color gradient, white background, and improved aspect ratio for vertical display."
)
demo.launch(
mcp_server=True,
share=False,
server_port=7860,
server_name="0.0.0.0"
) |