--- id: draw_join_graph_figure_py_datascience name: draw_join_graph_figure kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def draw_join_graph_figure(join_graph: dict, title: str = None) -> \"matplotlib.figure.Figure\"" description: "Rasteriza el join graph de una base (relaciones FK inter-tabla, salida de build_join_graph) a un matplotlib.figure.Figure: nodos circulares con el nombre de cada tabla (hubs en color de acento cálido, el resto neutro) y aristas dirigidas etiquetadas from_col→to_col (más la cardinalidad si viene). Es la contrapartida dibujada del string Mermaid para que el capítulo de relaciones del informe AutomaticEDA muestre un diagrama real. Layout networkx spring_layout determinista (seed=42), backend Agg sin abrir ventanas; defensivo: nunca lanza y nunca hace I/O." tags: [eda, plot, relations, graph, matplotlib, figure, networkx, datascience, impure] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [matplotlib, networkx] example: | from draw_join_graph_figure import draw_join_graph_figure join_graph = { "nodes": [ {"table": "customers", "out_degree": 0, "in_degree": 1, "role": "dimension"}, {"table": "orders", "out_degree": 1, "in_degree": 0, "role": "fact"}, ], "edges": [ {"from_table": "orders", "from_col": "customer_id", "to_table": "customers", "to_col": "id", "cardinality": "N:1"}, ], "hubs": ["orders"], } fig = draw_join_graph_figure(join_graph, title="Relaciones FK") fig.savefig("/tmp/join_graph.png") tested: true tests: - "test_returns_figure_with_axis" - "test_savefig_produces_nonempty_png" - "test_empty_dict_does_not_raise_and_savefig_png" - "test_none_does_not_raise_and_savefig_png" test_file_path: "python/functions/datascience/draw_join_graph_figure_test.py" file_path: "python/functions/datascience/draw_join_graph_figure.py" params: - name: join_graph desc: "Dict producido por build_join_graph. Claves: `nodes` (list[dict] con table, out_degree, in_degree, role), `edges` (list[dict] con from_table, from_col, to_table, to_col y opcional cardinality/inclusion) y `hubs` (list[str] de tablas hub a destacar en color cálido). Claves ausentes, items no-dict, None o {} se toleran (devuelve Figure con texto, sin lanzar). Los nombres de nodo se derivan también de las aristas, así que un grafo con edges pero sin nodes explícitos igual se dibuja." - name: title desc: "Título dibujado sobre el diagrama. Si se omite (None) se usa \"Join graph\". Default None." output: "Un matplotlib.figure.Figure (figsize 7x5) con un único Axes que contiene el diagrama node-link dirigido: tablas como nodos circulares etiquetados (hubs en acento cálido #DD8452, resto en azul neutro #4C72B0) y FKs como flechas dirigidas con etiqueta from_col→to_col (+ cardinalidad). Si join_graph no tiene nodos ni aristas (o es None/{}), devuelve igualmente una Figure con el texto centrado \"Sin relaciones FK detectadas.\"; ante cualquier fallo interno devuelve una Figure con un mensaje genérico (nunca lanza). El caller rasteriza/cierra la figura; la función no la muestra ni la guarda." --- ## Ejemplo ```python from draw_join_graph_figure import draw_join_graph_figure # `join_graph` es la salida de build_join_graph (nodes + edges + hubs). join_graph = { "nodes": [ {"table": "customers", "out_degree": 0, "in_degree": 1, "role": "dimension"}, {"table": "orders", "out_degree": 2, "in_degree": 0, "role": "fact"}, {"table": "products", "out_degree": 0, "in_degree": 1, "role": "dimension"}, ], "edges": [ {"from_table": "orders", "from_col": "customer_id", "to_table": "customers", "to_col": "id", "cardinality": "N:1"}, {"from_table": "orders", "from_col": "product_id", "to_table": "products", "to_col": "id", "cardinality": "N:1"}, ], "hubs": ["orders"], # `orders` se pinta en color de acento (tabla de hechos) } fig = draw_join_graph_figure(join_graph, title="Relaciones FK") # El renderer del informe lo rasteriza; aquí solo persistimos para inspección. fig.savefig("/tmp/join_graph.png") ``` ## Cuando usarla Úsala en el capítulo de relaciones de un informe AutomaticEDA cuando quieras un diagrama **dibujado** del esquema relacional, no solo el bloque Mermaid pegable. Pásale directamente la salida de `build_join_graph` (`nodes` + `edges` + `hubs`) y obtienes una `matplotlib.figure.Figure` lista para que el renderer perezoso la rasterice. Es la pareja visual del string Mermaid: Mermaid sirve para pegar en Markdown/docs que lo soporten; esta función produce la imagen real (PNG/PDF) que va embebida en informes que no renderizan Mermaid. ## Gotchas - **Impura por matplotlib.** Fija el backend `Agg` al importar — no abre ventanas ni depende de un display. Segura de llamar en lotes desde el renderer. - **Layout determinista (`seed=42`).** Usa `nx.spring_layout(G, seed=42)`, así que la misma entrada produce el mismo diagrama (test reproducible). Para grafos de 0/1 nodos usa una posición fija centrada en vez del spring layout. - **No hace I/O.** No llama `plt.show()` ni guarda a disco — solo devuelve la `Figure`. Quien la consume la rasteriza y la libera (`plt.close(fig)`) para no acumular memoria en informes con muchas tablas. - **Devuelve una Figure, NO un dict.** A diferencia de `build_join_graph` (que devuelve el dict del grafo), esta función devuelve el objeto de figura ya dibujado. - **Defensiva, nunca lanza.** `None`, `{}`, claves ausentes o items malformados se manejan sin error: en el peor caso devuelve una `Figure` con "Sin relaciones FK detectadas." (vacío) o un mensaje genérico (fallo interno). No la envuelvas en try/except por miedo a un raise — no lo hay.