// 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 = `

Detalle

${content || 'No hay contenido disponible.'}
`; 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(); });