File size: 6,862 Bytes
7c43635 028a336 27e273f 7c43635 028a336 7c43635 bf5eed8 028a336 7c43635 028a336 7c43635 55cab16 7c43635 27e273f 7c43635 27e273f 7c43635 27e273f 7c43635 27e273f 7c43635 27e273f 7c43635 27e273f 89c005a 27e273f 55cab16 27e273f 89c005a 27e273f 7c43635 028a336 7c43635 9912372 7c43635 27e273f 028a336 7c43635 7e08bc6 028a336 7e08bc6 028a336 7c43635 27e273f 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 176 177 |
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
'splines': 'ortho', # Straight lines
'bgcolor': 'white', # Fondo blanco
'pad': '0.5', # Margen alrededor del gráfico
'size': '8,12!', # ¡Nuevo! Tamaño deseado (ancho, alto en pulgadas). El '!' es importante.
'ranksep': '1.0' # ¡Nuevo! Aumenta la separación vertical entre niveles
# 'ratio': 'fill' # Podría usarse con 'size' para forzar a rellenar el espacio
}
)
# 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 # How much lighter each level gets
# 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 colors can remain constant or change. Let's make them slightly visible.
edge_color = '#4a4a4a' # Un gris oscuro para las líneas
font_size = max(9, 14 - (current_depth * 2)) # Adjust font size based on depth
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', # Rectángulo
style='filled,rounded', # Redondeado
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, # Color de la fuente de la arista también gris
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) # Initial depth is 1 for nodes under central
# 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 (Custom Style)",
description="Generates an AI concept map with custom rounded boxes, color gradient, and white background."
)
demo.launch(
mcp_server=True,
share=False,
server_port=7860,
server_name="0.0.0.0"
) |