FLUJOS/VISUALIZACION/public/output_int_sec.js
2025-11-07 00:06:12 +01:00

178 lines
5.9 KiB
JavaScript
Executable file

// output_int_sec.js
// Obtener el contenedor del gráfico
document.addEventListener('DOMContentLoaded', () => {
const elem = document.getElementById('intSecContainer');
const form = document.getElementById('paramForm');
const detailPanel = document.getElementById('detailPanel');
// --- Etiqueta de texto con CanvasTexture (sin SpriteText externo) ---
function makeTextSprite(text) {
if (!window.THREE) {
console.warn('THREE no está disponible; omito etiquetas.');
return undefined;
}
const pad = 16; // más padding para nitidez
const font = 'bold 36px Fira Code, monospace'; // fuente más grande
// 1) preparar canvas al tamaño del texto
const c = document.createElement('canvas');
const ctx = c.getContext('2d');
ctx.font = font;
const w = Math.ceil(ctx.measureText(text).width) + pad * 2;
const h = 50 + pad * 2;
c.width = w; c.height = h;
// 2) dibujar texto
ctx.font = font;
ctx.fillStyle = 'rgba(255,255,255,0.98)';
ctx.textBaseline = 'middle';
ctx.fillText(text, pad, h / 2);
// 3) textura -> sprite
const tex = new THREE.CanvasTexture(c);
tex.needsUpdate = true;
const mat = new THREE.SpriteMaterial({
map: tex,
transparent: true,
depthTest: false, // que no lo tape la esfera
depthWrite: false // que no escriba en el z-buffer
});
const spr = new THREE.Sprite(mat);
// tamaño del texto en “mundo”
const k = 0.22; // ajusta tamaño del texto (↑ más grande, ↓ más pequeño)
spr.scale.set(w * k, h * k, 1);
// elevar el texto sobre el nodo
spr.position.y = 8; // sube/baja si lo ves muy pegado
return spr;
}
// Inicializar el gráfico 3D
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(1)
.linkColor(() => 'blue')
// Texto fijo sobre cada nodo (usando CanvasTexture)
.nodeThreeObject(n => makeTextSprite(n.id))
.nodeThreeObjectExtend(true) // mantiene esfera + texto
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => { elem.style.cursor = node ? 'pointer' : null; })
.forceEngine('d3');
// .d3Force('charge', d3.forceManyBody().strength(-400))
// .d3Force('link', d3.forceLink().distance(300).strength(1))
// Centrado automático del gráfico
function centerGraph() {
setTimeout(() => {
const width = elem.clientWidth;
const height = elem.clientHeight;
graph.zoomToFit(400, Math.min(width, height) * 0.1);
}, 500);
}
window.addEventListener('resize', () => {
graph.width(elem.clientWidth);
graph.height(elem.clientHeight);
centerGraph();
});
// Mostrar contenido del nodo
function showNodeContent(content) {
if (!detailPanel) {
console.log('Contenido del nodo:', content);
return;
}
detailPanel.innerHTML = `
<h2 style="margin:0 0 8px">Detalle</h2>
<pre style="white-space:pre-wrap; line-height:1.35; font-family:'Fira Code', monospace; font-size:14px; color:#e5e5e5;">${content || 'No hay contenido disponible.'}</pre>
`;
const split = document.querySelector('main.split-screen');
if (split) split.classList.add('show-detail');
}
// Función para obtener datos del servidor
async function getData(paramsObj = {}) {
try {
let url = '/api/data';
const params = new URLSearchParams();
// Establecer el tema fijo
params.append('tema', 'inteligencia y seguridad');
// Agregar parámetros del formulario, incluyendo complejidad como umbral
for (const key in paramsObj) {
if (paramsObj[key] !== undefined && paramsObj[key] !== '') {
params.append(key, paramsObj[key]);
}
}
url += `?${params.toString()}`;
console.log('🔎 Fetch URL:', url);
const response = await fetch(url);
if (!response.ok) throw new Error(response.statusText);
const data = await response.json();
console.log('Datos recibidos del servidor:', data);
// Filtrar enlaces inválidos
const nodeIds = new Set(data.nodes.map(node => node.id));
data.links = data.links.filter(link => nodeIds.has(link.source) && nodeIds.has(link.target));
return data;
} catch (error) {
console.error('Error al obtener datos del servidor:', error);
return null;
}
}
// Función principal: fetch, filtrar por complejidad/umbral y renderizar
async function fetchAndRender() {
const subtematica = document.getElementById('param2').value;
const palabraClave = document.getElementById('param1').value;
const fechaInicio = document.getElementById('fecha_inicio').value;
const fechaFin = document.getElementById('fecha_fin').value;
const nodos = document.getElementById('nodos').value;
const complejidad = document.getElementById('complejidad').value;
// Usamos 'complejidad' como porcentaje de similitud mínima (umbral)
const paramsObj = {
subtematica,
palabraClave,
fechaInicio,
fechaFin,
nodos,
complejidad // enviado al backend y usado de umbral en cliente
};
// Parsear complejidad a número
const umbralPct = parseFloat(complejidad) || 0;
console.log(`Aplicando umbral de similitud: ${umbralPct}%`);
const graphData = await getData(paramsObj);
if (!graphData) return;
// Filtrar enlaces por umbral de similitud
let filteredLinks = graphData.links;
if (umbralPct > 0) {
filteredLinks = filteredLinks.filter(link => Number(link.value) >= umbralPct);
console.log(`Enlaces tras aplicar umbral: ${filteredLinks.length}`);
}
graph.graphData({ nodes: graphData.nodes, links: filteredLinks });
centerGraph();
}
// Escuchar evento submit del formulario
form.addEventListener('submit', event => {
event.preventDefault();
fetchAndRender();
});
// Cargar gráfico inicial
fetchAndRender();
});