flow like the river

This commit is contained in:
root 2025-11-07 00:06:12 +01:00
commit 013fe673f3
42435 changed files with 5764238 additions and 0 deletions

View file

@ -0,0 +1,80 @@
:root {
--cube-size: 80px;
--line-color: #333;
--hover-color: #2ecc71;
--background-color: #000;
--sidebar-width: 250px; /* Ajustamos el ancho del sidebar */
}
body {
margin: 0;
padding: 0;
display: flex;
justify-content: flex-start; /* Asegura que el contenido comience desde la izquierda */
align-items: center;
height: 100vh;
background-color: var(--background-color);
}
.graph-container {
position: relative;
width: calc(100vw - var(--sidebar-width)); /* Restamos el ancho del sidebar */
height: 100vh;
margin-left: var(--sidebar-width); /* Añadimos margen para que no se solape */
}
.cube-container {
position: absolute;
width: var(--cube-size);
height: var(--cube-size);
transform-style: preserve-3d;
transition: transform 1s ease;
}
.cube-container:hover {
transform: rotateX(360deg) rotateY(360deg); /* Efecto al pasar el mouse */
}
.cube {
position: absolute;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.face {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--hover-color);
border: 1px solid #000;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.65rem;
color: white;
}
.front { transform: translateZ(calc(var(--cube-size) / 2)); }
.back { transform: rotateY(180deg) translateZ(calc(var(--cube-size) / 2)); }
.left { transform: rotateY(-90deg) translateZ(calc(var(--cube-size) / 2)); }
.right { transform: rotateY(90deg) translateZ(calc(var(--cube-size) / 2)); }
.top { transform: rotateX(90deg) translateZ(calc(var(--cube-size) / 2)); }
.bottom { transform: rotateX(-90deg) translateZ(calc(var(--cube-size) / 2)); }
.lines {
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
.line-path {
stroke: var(--line-color);
stroke-width: 2;
transition: stroke 0.3s;
}
.line-path:hover {
stroke: #e74c3c;
}

View file

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visualización 2D de Noticias Políticas</title>
<link rel="stylesheet" href="3dscript_eco-corp.css">
<style>
.popup {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
color: black;
padding: 20px;
border-radius: 10px;
z-index: 2;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.5);
}
.popup:focus,
.cube-container:focus + .popup {
display: block;
}
.popup button {
display: block;
margin: 10px auto;
padding: 10px;
background-color: #333;
color: white;
border: none;
cursor: pointer;
}
.popup button:hover {
background-color: #555;
}
</style>
</head>
<body>
<div class="graph-container" id="graph-container">
<svg class="lines" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" id="svg-lines">
<!-- Líneas generadas dinámicamente -->
</svg>
</div>
<script>
const numCubes = 9; // Número total de cubos
const spacing = 150; // Espacio mínimo entre los cubos
const graphContainer = document.getElementById('graph-container');
const svgLines = document.getElementById('svg-lines');
let cubePositions = [];
// Función para detectar colisiones y asegurarse de que los cubos no se solapen
function detectCollision(newX, newY, positions) {
for (let pos of positions) {
const distance = Math.sqrt((newX - pos.x) ** 2 + (newY - pos.y) ** 2);
if (distance < spacing) {
return true; // Hay colisión
}
}
return false; // No hay colisión
}
// Función para generar una posición aleatoria sin que los cubos se solapen
function getRandomPosition(maxWidth, maxHeight) {
let x, y;
do {
x = Math.random() * (maxWidth - spacing);
y = Math.random() * (maxHeight - spacing);
} while (detectCollision(x, y, cubePositions));
return { x, y };
}
// Función para generar cubos con posiciones aleatorias
function generateRandomCubes(cubeCount) {
for (let i = 0; i < cubeCount; i++) {
const position = getRandomPosition(window.innerWidth, window.innerHeight);
const cubeContainer = document.createElement('div');
cubeContainer.classList.add('cube-container');
cubeContainer.setAttribute('tabindex', '0');
cubeContainer.style.top = position.y + 'px';
cubeContainer.style.left = position.x + 'px';
cubeContainer.innerHTML = `
<div class="cube">
<div class="face front">País ${i}</div>
<div class="face back">Detalle ${i}</div>
<div class="face left">Año ${2000 + i}</div>
<div class="face right">Muertos ${(i + 1) * 1000}</div>
<div class="face top">ONU</div>
<div class="face bottom">Fuente</div>
</div>
`;
// Crear un popup para cada cubo
const popup = document.createElement('div');
popup.classList.add('popup');
popup.innerHTML = `
<p>Noticia relacionada con País ${i}</p>
<button>Close</button>
`;
popup.querySelector('button').addEventListener('click', () => {
popup.style.display = 'none';
cubeContainer.focus(); // Regresar el foco al cubo
});
cubeContainer.addEventListener('focus', () => {
popup.style.display = 'block';
});
cubePositions.push({ x: position.x + 35, y: position.y + 35 }); // Guardar la posición
graphContainer.appendChild(cubeContainer);
graphContainer.appendChild(popup); // Añadir el popup después del cubo
}
}
// Función para generar las líneas entre cubos relacionados
function generateLines() {
for (let i = 0; i < cubePositions.length - 1; i++) {
const x1 = cubePositions[i].x;
const y1 = cubePositions[i].y;
const x2 = cubePositions[i + 1].x;
const y2 = cubePositions[i + 1].y;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x1);
line.setAttribute('y1', y1);
line.setAttribute('x2', x2);
line.setAttribute('y2', y2);
line.setAttribute('stroke', '#333');
line.setAttribute('stroke-width', '2');
svgLines.appendChild(line);
}
}
// Generar cubos aleatorios y las líneas de conexión
generateRandomCubes(numCubes);
generateLines();
</script>
</body>
</html>

332
VISUALIZACION/public/climate.css Executable file
View file

@ -0,0 +1,332 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
text-shadow: 10px 10px 20px rgba(0, 255, 76, 0.3);
}
body {
font-family: 'Fira Code', monospace;
background: #000000;
color: #000000;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 70px;
background-color: #000000;
color: #ffffff;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
}
.logo {
font-family: "Retrolift", sans-serif;
font-size: 4em;
font-weight: bold;
letter-spacing: 4px;
text-shadow: 6px 6px 6px #73ff00;
margin-bottom: 10px;
}
.glob-war:hover .logo { text-shadow: 2px 2px 8px #39ff14; }
.int-sec:hover .logo { text-shadow: 2px 2px 8px #ff69b4; }
.climate:hover .logo { text-shadow: 2px 2px 8px #ff4500; }
.eco-corp:hover .logo { text-shadow: 2px 2px 8px #006400; }
.popl-up:hover .logo { text-shadow: 2px 2px 8px #00008b; }
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em; /* incrementa la distancia entre los elementos */
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
/* Estilo de los botones */
position:relative;
border: none;
transition: .4s ease-in;
z-index: 1;
width: 40vw;
height: 20vh;
border:3vw;
align-items: center;
border: 3px solid ;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 0 0 6px rgba(0,0,0,0.3);
/* Estilo hover de los botones */
box-shadow: 2vw 1vw;
border: 3px solid black ;
}
.glob-war:hover { color: #39ff14; }
.int-sec:hover { color: #ff69b4; }
.climate:hover { color: #ff4500; }
.eco-corp:hover { color: #00fff2; }
.popl-up:hover { color: #0066ff; }
.glob-war {
color: #FF4136;
background-color: red;
}
.int-sec {
color: #0074D9;
background-color: darkblue;
}
.climate {
color: #2ECC40;
background-color: lightgreen;
}
.eco-corp {
color: #FFDC00;
background-color: yellow;
}
.popl-up {
color: #FF851B;
background-color: orange;
}
.background {
display: flex;
height: 100vh;
width: 99%;
overflow-x: scroll;
}
.background a {
display: block;
width: 20%;
height: 90vh;
transition: transform 1.5s ease;
}
.background img {
width: 20%;
height: 90vh;
object-fit: cover;
transition: transform 1.5s ease;
}
.background a img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 1.5s ease;
}
.background img:hover {
transform: scale(1.1);
}
#sidebar.active {
transform: translateX(0);
}
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-90%);
transition: transform 0.3s ease-out;
overflow: auto;
font-size:18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar:hover {
transform: translateX(0);
}
#sidebarToggle {
position: absolute;
left: 1em;
top: 1em;
background: #007BFF;
color: #39ff14;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
z-index: 2;
}
#sidebarToggle {
display: none;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size:20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14; /* Añade el borde verde al input */
background: black; /* Cambia el fondo del input a negro */
color: #39ff14; /* Cambia el color del texto en el input a verde */
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 1;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
#climateContainer {
position: absolute;
width: 100%;
height: 100%;
opacity: 0.5;
}
@media screen and (max-width: 768px) {
.nav-links a {
font-size: 0.8em; /* reduce el tamaño de la fuente */
padding: 10px 20px; /* reduce el padding */
}
.nav-links {
list-style: none;
display: flex;
gap: 2em; /* incrementa la distancia entre los elementos */
}
}
.fade-out {
animation: fadeOut 2s forwards; /* Fades out over 3 seconds */
}
@keyframes fadeOut {
from {opacity: 1;}
to {opacity: 0;}
}
.grafico {
z-index: 1; /* Bring to front */
/* Rest of your styles */
}
.grafico {
width: 100%;
height: 100vh;
position: absolute;
z-index: 1;
}

View file

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CLIMATE</title>
<link rel="stylesheet" href="climate.css">
<!-- Fuentes de Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
<!-- Cargar 3d-force-graph (incluye Three.js) -->
<!-- CORRECTO: d3 primero, force-graph después -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<script src="https://unpkg.com/3d-force-graph"></script>
</head>
<body>
<nav>
<ul class="nav-links">
<li><a href="climate.html" class="climate">CLIMATE</a></li>
</ul>
</nav>
<main>
<div id="climateContainer" style="position: absolute; width: 100%; height: 100%;"></div>
<div class="background">
<img src="/images/flujos6.jpg">
<img src="/images/flujos6.jpg">
<img src="/images/flujos6.jpg">
<img src="/images/flujos6.jpg">
<img src="/images/flujos6.jpg">
<script>
setTimeout(function() {
var fondo = document.querySelector('.background');
fondo.classList.add('fade-out');
fondo.style.pointerEvents = 'none';
}, 1000);
</script>
</div>
</main>
<div id="sidebar">
<h2>Parámetros</h2>
<form id="paramForm">
<label for="fecha_inicio">Fecha de inicio:</label>
<input type="date" id="fecha_inicio" name="fecha_inicio">
<label for="fecha_fin">Fecha de fin:</label>
<input type="date" id="fecha_fin" name="fecha_fin">
<label for="nodos">Nodos:</label>
<input type="number" id="nodos" name="nodos" value="100">
<label for="complejidad">Complejidad:</label>
<input type="range" id="complejidad" name="complejidad" min="1" max="40" value="20">
<label for="param1">Búsqueda por palabra:</label>
<input type="text" id="param1" name="param1">
<label for="color1">Color 1:</label>
<input type="color" id="color1" name="color1">
<label for="param2">Búsqueda por temática personalizada:</label>
<input type="text" id="param2" name="param2">
<label for="color2">Color 2:</label>
<input type="color" id="color2" name="color2">
<input type="submit" value="Aplicar">
</form>
</div>
<button id="sidebarToggle">Toggle Sidebar</button>
<footer>
<p><a href="#">GitHub</a> | <a href="#">Telegram</a> | <a href="#">Email</a> | <a href="#">Web de Tor</a></p>
</footer>
<!-- Movemos la inclusión del script aquí, al final del body -->
<script src="output_climate_pruebas.js"></script>
</body>
</html>

View file

236
VISUALIZACION/public/eco-corp.css Executable file
View file

@ -0,0 +1,236 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
text-shadow: 10px 10px 20px rgba(0, 255, 76, 0.3);
}
body {
font-family: 'Fira Code', monospace;
background: #000000;
color: #000000;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 70px;
background-color: #000000;
color: #ffffff;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
}
.logo {
font-family: "Retrolift", sans-serif;
font-size: 4em;
font-weight: bold;
letter-spacing: 4px;
text-shadow: 6px 6px 6px #73ff00;
margin-bottom: 10px;
}
.glob-war:hover .logo { text-shadow: 2px 2px 8px #39ff14; }
.int-sec:hover .logo { text-shadow: 2px 2px 8px #ff69b4; }
.climate:hover .logo { text-shadow: 2px 2px 8px #ff4500; }
.eco-corp:hover .logo { text-shadow: 2px 2px 8px #006400; }
.popl-up:hover .logo { text-shadow: 2px 2px 8px #00008b; }
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em;
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
position: relative;
border: none;
transition: .4s ease-in;
z-index: 1;
width: 40vw;
height: 20vh;
border: 3vw;
align-items: center;
border: 3px solid;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 0 0 6px rgba(0,0,0,0.3);
border: 3px solid black;
}
.glob-war:hover { color: #39ff14; }
.int-sec:hover { color: #ff69b4; }
.climate:hover { color: #ff4500; }
.eco-corp:hover { color: #00fff2; }
.popl-up:hover { color: #0066ff; }
.glob-war { color: #FF4136; background-color: red; }
.int-sec { color: #0074D9; background-color: darkblue; }
.climate { color: #2ECC40; background-color: lightgreen; }
.eco-corp { color: #FFDC00; background-color: yellow; }
.popl-up { color: #FF851B; background-color: orange; }
.background {
display: flex;
height: 100vh;
width: 99%;
overflow-x: scroll;
}
.background a {
display: block;
width: 20%;
height: 90vh;
transition: transform 1.5s ease;
}
.background img {
width: 20%;
height: 90vh;
object-fit: cover;
transition: transform 1.5s ease;
}
.background img:hover {
transform: scale(1.1);
}
/* Sidebar con comportamiento onhover */
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-220px);
transition: transform 0.3s ease-out;
overflow: auto;
font-size: 18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar:hover {
transform: translateX(0);
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14;
background: black;
color: #39ff14;
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 6;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
/* Ajuste del iframe para que respete el espacio del sidebar */
.iframe-container {
width: calc(100% - 250px);
height: 100vh;
background: #000000;
margin: 0 auto;
border: none;
z-index: 1;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: absolute;
left: 250px;
}

View file

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Economía y Corporaciones</title>
<link rel="stylesheet" href="eco-corp.css">
<!-- Fuentes de Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
<!-- Cargar D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<!-- Cargar 3d-force-graph (incluye Three.js) -->
<script src="https://unpkg.com/3d-force-graph"></script>
</head>
<body>
<nav>
<ul class="nav-links">
<li><a href="eco-corp.html" class="eco-corp">Economía y Corporaciones</a></li>
</ul>
</nav>
<main>
<div id="ecoCorpContainer" style="position: absolute; width: 100%; height: 100%;"></div>
<div class="background">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<script>
setTimeout(function() {
var fondo = document.querySelector('.background');
fondo.classList.add('fade-out');
fondo.style.pointerEvents = 'none';
}, 1000);
</script>
</div>
</main>
<div id="sidebar">
<h2>Parámetros</h2>
<form id="paramForm">
<label for="fecha_inicio">Fecha de inicio:</label>
<input type="date" id="fecha_inicio" name="fecha_inicio">
<label for="fecha_fin">Fecha de fin:</label>
<input type="date" id="fecha_fin" name="fecha_fin">
<label for="nodos">Nodos:</label>
<input type="number" id="nodos" name="nodos" value="100">
<label for="complejidad">Complejidad:</label>
<input type="range" id="complejidad" name="complejidad" min="1" max="40" value="20">
<label for="param1">Búsqueda por palabra:</label>
<input type="text" id="param1" name="param1">
<label for="param2">Búsqueda por temática personalizada:</label>
<input type="text" id="param2" name="param2">
<input type="submit" value="Aplicar">
</form>
</div>
<button id="sidebarToggle">Toggle Sidebar</button>
<footer>
<p><a href="#">GitHub</a> | <a href="#">Telegram</a> | <a href="#">Email</a> | <a href="#">Web de Tor</a></p>
</footer>
<!-- Incluir tu script al final del body -->
<script src="output_eco_corp_pruebas.js"></script>
</body>
</html>

View file

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="eco-corp.css">
<title>ECO-CORP</title>
<script type="module" src="output.js"></script>
</head>
<body>
<nav>
<ul class="nav-links">
<li><a href="eco-corp.html" class="eco-corp">ECO-CORP</a></li>
</ul>
</nav>
<main>
<div id="canvasContainer" style="position: absolute; width: 100%; height: 100%; opacity: 0.5;"></div>
<div class="background">
<img src="/images/flujos4.jpg">
<img src="/images/flujos4.jpg">
<img src="/images/flujos4.jpg">
<img src="/images/flujos4.jpg">
<img src="/images/flujos4.jpg">
<script>
setTimeout(function() {
var fondo = document.querySelector('.background');
fondo.classList.add('fade-out');
fondo.style.pointerEvents = 'none';
}, 1000);
</script>
</div>
<div id="contentDisplay" class="content-display"></div>
</main>
<div id="sidebar">
<h2>Parámetros</h2>
<form id="paramForm">
<label for="fecha_inicio">Fecha de inicio:</label>
<input type="date" id="fecha_inicio" name="fecha_inicio">
<label for="fecha_fin">Fecha de fin:</label>
<input type="date" id="fecha_fin" name="fecha_fin">
<label for="nodos">Número máximo de nodos:</label>
<input type="number" id="nodos" name="nodos" value="200" min="50" max="500">
<label for="complejidad">Complejidad:</label>
<input type="range" id="complejidad" name="complejidad" min="1" max="10">
<label for="param1">Búsqueda por palabra clave:</label>
<input type="text" id="param1" name="param1">
<label for="param2">Filtrar por subtemática:</label>
<input type="text" id="param2" name="param2">
<input type="submit" value="Aplicar">
</form>
</div>
<button id="sidebarToggle">Toggle Sidebar</button>
<footer>
<p><a href="#">GitHub</a> | <a href="#">Telegram</a> | <a href="#">Email</a> | <a href="#">Web de Tor</a></p>
</footer>
<script>
document.getElementById('sidebarToggle').addEventListener('click', function() {
var sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('active');
});
document.getElementById('paramForm').addEventListener('submit', function(event) {
event.preventDefault();
const maxNodes = document.getElementById('nodos').value;
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;
// Ejecutar la lógica para actualizar el gráfico con los nuevos parámetros
myGraph.setMaxNodes(maxNodes);
myGraph.drawGraph(subtematica, palabraClave, fechaInicio, fechaFin);
});
</script>
</body>
</html>

331
VISUALIZACION/public/glob-war.css Executable file
View file

@ -0,0 +1,331 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
text-shadow: 10px 10px 20px rgba(0, 255, 76, 0.3);
}
body {
font-family: 'Fira Code', monospace;
background-color: #000000;
color: #000000;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 70px;
background-color: #000000;
color: #000000;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
}
.logo {
font-family: "Retrolift", sans-serif;
font-size: 4em;
font-weight: bold;
letter-spacing: 4px;
text-shadow: 6px 6px 6px #73ff00;
margin-bottom: 10px;
}
.glob-war:hover .logo { text-shadow: 2px 2px 8px #39ff14; }
.int-sec:hover .logo { text-shadow: 2px 2px 8px #ff69b4; }
.climate:hover .logo { text-shadow: 2px 2px 8px #ff4500; }
.eco-corp:hover .logo { text-shadow: 2px 2px 8px #006400; }
.popl-up:hover .logo { text-shadow: 2px 2px 8px #00008b; }
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em; /* incrementa la distancia entre los elementos */
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
/* Estilo de los botones */
position:relative;
border: none;
transition: .4s ease-in;
z-index: 1;
width: 40vw;
height: 20vh;
border:3vw;
align-items: center;
border: 3px solid ;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 0 0 6px rgba(0,0,0,0.3);
/* Estilo hover de los botones */
box-shadow: 2vw 1vw;
border: 3px solid black ;
}
.glob-war:hover { color: #39ff14; }
.int-sec:hover { color: #ff69b4; }
.climate:hover { color: #ff4500; }
.eco-corp:hover { color: #00fff2; }
.popl-up:hover { color: #0066ff; }
.glob-war {
color: #FF4136;
background-color: red;
}
.int-sec {
color: #0074D9;
background-color: darkblue;
}
.climate {
color: #2ECC40;
background-color: lightgreen;
}
.eco-corp {
color: #FFDC00;
background-color: yellow;
}
.popl-up {
color: #FF851B;
background-color: orange;
}
.background {
display: flex;
height: 100vh;
width: 99%;
overflow-x: scroll;
}
.background a {
display: block;
width: 20%;
height: 90vh;
transition: transform 1.5s ease;
}
.background img {
width: 20%;
height: 90vh;
object-fit: cover;
transition: transform 1.5s ease;
}
.background a img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 1.5s ease;
}
.background img:hover {
transform: scale(1.1);
}
#sidebar.active {
transform: translateX(0);
}
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-90%);
transition: transform 0.3s ease-out;
overflow: auto;
font-size:18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar:hover {
transform: translateX(0);
}
#sidebarToggle {
position: absolute;
left: 1em;
top: 1em;
background: #007BFF;
color: #39ff14;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
z-index: 2;
}
#sidebarToggle {
display: none;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size:20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14; /* Añade el borde verde al input */
background: black; /* Cambia el fondo del input a negro */
color: #39ff14; /* Cambia el color del texto en el input a verde */
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 1;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
#glob_war_container{
position: absolute;
width: 100%;
height: 100%;
opacity: 0.5;
}
@media screen and (max-width: 768px) {
.nav-links a {
font-size: 0.8em; /* reduce el tamaño de la fuente */
padding: 10px 20px; /* reduce el padding */
}
.nav-links {
list-style: none;
display: flex;
gap: 2em; /* incrementa la distancia entre los elementos */
}
}
.fade-out {
animation: fadeOut 3s forwards; /* Fades out over 3 seconds */
}
@keyframes fadeOut {
from {opacity: 1;}
to {opacity: 0;}
}
.grafico {
z-index: 1; /* Bring to front */
/* Rest of your styles */
}
.grafico {
width: 100%;
height: 100vh;
position: absolute;
z-index: 1;
}

View file

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Glob-War</title>
<link rel="stylesheet" href="glob-war.css">
<!-- Fuentes de Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
<!-- Cargar D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<!-- Cargar 3d-force-graph (incluye Three.js) -->
<script src="https://unpkg.com/3d-force-graph"></script>
</head>
<body>
<nav>
<ul class="nav-links">
<li><a href="glob-war.html" class="glob-war">GLOB-WAR</a></li>
</ul>
</nav>
<main>
<div id="globWarContainer" style="position: absolute; width: 100%; height: 100%;"></div>
<div class="background">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<script>
setTimeout(function() {
var fondo = document.querySelector('.background');
fondo.classList.add('fade-out');
fondo.style.pointerEvents = 'none';
}, 1000);
</script>
</div>
</main>
<div id="sidebar">
<h2>Parámetros</h2>
<form id="paramForm">
<label for="fecha_inicio">Fecha de inicio:</label>
<input type="date" id="fecha_inicio" name="fecha_inicio">
<label for="fecha_fin">Fecha de fin:</label>
<input type="date" id="fecha_fin" name="fecha_fin">
<label for="nodos">Nodos:</label>
<input type="number" id="nodos" name="nodos" value="100">
<label for="complejidad">Complejidad:</label>
<input type="range" id="complejidad" name="complejidad" min="1" max="40" value="20">
<label for="param1">Búsqueda por palabra:</label>
<input type="text" id="param1" name="param1">
<label for="color1">Color 1:</label>
<input type="color" id="color1" name="color1">
<label for="param2">Búsqueda por temática personalizada:</label>
<input type="text" id="param2" name="param2">
<label for="color2">Color 2:</label>
<input type="color" id="color2" name="color2">
<input type="submit" value="Aplicar">
</form>
</div>
<button id="sidebarToggle">Toggle Sidebar</button>
<footer>
<p><a href="#">GitHub</a> | <a href="#">Telegram</a> | <a href="#">Email</a> | <a href="#">Web de Tor</a></p>
</footer>
<!-- Incluir tu script al final del body -->
<script src="output_glob_war_pruebas.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

100
VISUALIZACION/public/index.html Executable file
View file

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>FLOWS</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Nosifer&display=swap" rel="stylesheet">
</head>
<body>
<header>
<div class="header-content">
<h1 class="title">FLOWS</h1>
<div class="header-buttons">
<a href="journalist.html" class="small-button">Journalist</a>
</div>
</div>
</header>
<nav>
<ul class="nav-links">
<li><a href="glob-war.html" class="glob-war">GLOB-WAR</a></li>
<li><a href="int-sec.html" class="int-sec">INT-SEC</a></li>
<li><a href="climate.html" class="climate">CLIMATE</a></li>
<li><a href="eco-corp.html" class="eco-corp">ECO-CORP</a></li>
<li><a href="popl-up.html" class="popl-up">Popl-up</a></li>
</ul>
</nav>
<main>
<div id="canvasContainer" style="position: absolute; width: 100%; height: 100%; opacity: 0.5;"></div>
<div class="background">
<a href="glob-war.html">
<img class="img1" src="/images/flujos.jpg">
</a>
<a href="int-sec.html">
<img class="img2" src="/images/fujos5.jpg">
</a>
<a href="climate.html">
<img class="img3" src="/images/flujos6.jpg">
</a>
<a href="eco-corp.html">
<img class="img4" src="/images/flujos4.jpg">
</a>
<a href="popl-up.html">
<img class="img5" src="/images/flujos3.jpg">
</a>
</div>
<!-- Botones sobre las imágenes -->
<div class="button-overlay">
<div class="button-column">
<a href="glob-war.html?subtematica=conflictos_internacionales" class="overlay-button">Conflictos Internacionales</a>
<a href="glob-war.html?subtematica=guerras_civiles" class="overlay-button">Guerras Civiles</a>
<a href="glob-war.html?subtematica=terrorismo" class="overlay-button">Terrorismo</a>
<a href="glob-war.html?subtematica=armas" class="overlay-button">Armas</a>
<a href="glob-war.html?subtematica=alianzas_militares" class="overlay-button">Alianzas Militares</a>
</div>
<div class="button-column">
<a href="int-sec.html?subtematica=inteligencia" class="overlay-button">Inteligencia</a>
<a href="int-sec.html?subtematica=ciberseguridad" class="overlay-button">Ciberseguridad</a>
<a href="int-sec.html?subtematica=espionaje" class="overlay-button">Espionaje</a>
<a href="int-sec.html?subtematica=seguridad_nacional" class="overlay-button">Seguridad Nacional</a>
<a href="int-sec.html?subtematica=contraterrorismo" class="overlay-button">Contraterrorismo</a>
</div>
<div class="button-column">
<a href="climate.html?subtematica=cambio_climatico" class="overlay-button">Cambio Climático</a>
<a href="climate.html?subtematica=desastres_naturales" class="overlay-button">Desastres Naturales</a>
<a href="climate.html?subtematica=conservacion" class="overlay-button">Conservación</a>
<a href="climate.html?subtematica=energia_renovable" class="overlay-button">Energía Renovable</a>
<a href="climate.html?subtematica=contaminacion" class="overlay-button">Escasez de agua</a>
</div>
<div class="button-column">
<a href="eco-corp.html?subtematica=economia_global" class="overlay-button">Economía Global</a>
<a href="eco-corp.html?subtematica=corporaciones_multinacionales" class="overlay-button">Corporaciones Multinacionales</a>
<a href="eco-corp.html?subtematica=comercio_internacional" class="overlay-button">Comercio Internacional</a>
<a href="eco-corp.html?subtematica=organismos_financieros" class="overlay-button">Organismos Financieros</a>
<a href="eco-corp.html?subtematica=desigualdad_economica" class="overlay-button">Desigualdad Económica</a>
</div>
<div class="button-column">
<a href="popl-up.html?subtematica=sobrepoblacion" class="overlay-button">Sobrepoblación</a>
<a href="popl-up.html?subtematica=covid" class="overlay-button">COVID</a>
<a href="popl-up.html?subtematica=migraciones" class="overlay-button">Migraciones</a>
<a href="popl-up.html?subtematica=urbanizacion" class="overlay-button">Urbanización</a>
<a href="popl-up.html?subtematica=distribucion_edad" class="overlay-button">Despoblaciòn rural</a>
</div>
</div>
</main>
<button id="sidebarToggle">Toggle Sidebar</button>
<footer>
<p><a href="#">GitHub</a> | <a href="#">Telegram</a> | <a href="#">Email</a> | <a href="#">Web de Tor</a></p>
</footer>
</body>
</html>

277
VISUALIZACION/public/int-sec.css Executable file
View file

@ -0,0 +1,277 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
text-shadow: 10px 10px 20px rgba(0, 255, 76, 0.3);
}
body {
font-family: 'Fira Code', monospace;
background: #000000;
color: #000000;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 70px;
background-color: #000000;
color: #ffffff;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
}
.logo {
font-family: "Retrolift", sans-serif;
font-size: 4em;
font-weight: bold;
letter-spacing: 4px;
text-shadow: 6px 6px 6px #73ff00;
margin-bottom: 10px;
}
.glob-war:hover .logo { text-shadow: 2px 2px 8px #39ff14; }
.int-sec:hover .logo { text-shadow: 2px 2px 8px #ff69b4; }
.climate:hover .logo { text-shadow: 2px 2px 8px #ff4500; }
.eco-corp:hover .logo { text-shadow: 2px 2px 8px #006400; }
.popl-up:hover .logo { text-shadow: 2px 2px 8px #00008b; }
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em;
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
position: relative;
border: none;
z-index: 1;
width: 40vw;
height: 20vh;
align-items: center;
border: 3px solid;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 2vw 1vw;
border: 3px solid black;
}
.glob-war { color: #FF4136; background-color: red; }
.int-sec { color: #0074D9; background-color: darkblue; }
.climate { color: #2ECC40; background-color: lightgreen; }
.eco-corp { color: #FFDC00; background-color: yellow; }
.popl-up { color: #FF851B; background-color: orange; }
.background {
display: flex;
height: 100vh;
width: 99%;
overflow-x: scroll;
}
.background img {
width: 20%;
height: 90vh;
object-fit: cover;
transition: transform 1.5s ease;
}
.background img:hover {
transform: scale(1.1);
}
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-90%); /* Oculta el 90%, deja 10% visible */
transition: transform 0.3s ease-out;
overflow: auto;
font-size: 18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar:hover {
transform: translateX(0); /* Aparece entero al hacer hover */
}
#sidebar.active {
transform: translateX(0);
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14;
background: black;
color: #39ff14;
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 1;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
#intSecContainer {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
background-color: #101020;
}
@media screen and (max-width: 768px) {
.nav-links a {
font-size: 0.8em;
padding: 10px 20px;
}
.nav-links {
gap: 2em;
}
}
.fade-out {
animation: fadeOut 2s forwards;
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
/* ===== Añadidos para split-screen ===== */
main.split-screen {
display: flex;
height: 100vh;
margin-left: 0; /* El sidebar está fijo encima */
}
#graphPanel {
width: 100%;
transition: width 0.3s ease;
position: relative;
}
#detailPanel {
width: 0;
overflow: hidden;
transition: width 0.3s ease;
}
/* Al hacer click en un nodo */
main.split-screen.show-detail #graphPanel {
width: 45%;
}
main.split-screen.show-detail #detailPanel {
width: 50%;
padding: 1em;
}
/* Asegurar que el grafo ocupe todo su panel */
#graphPanel #intSecContainer {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 2;
}
/* Estilos del panel de detalle */
#detailPanel {
background: #101020;
color: #fff;
box-shadow: inset 1px 0 5px rgba(0,0,0,0.5);
}
#detailPanel h2 {
margin-bottom: .5em;
color: #39ff14;
}
#detailPanel p {
line-height: 1.4;
margin-bottom: 1em;
}
#detailPanel .placeholder {
color: rgba(255,255,255,0.3);
font-style: italic;
}

View file

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Inteligencia y Seguridad</title>
<link rel="stylesheet" href="int-sec.css">
<!-- Fuentes de Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
<!-- Cargar D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<!-- Cargar 3d-force-graph (incluye Three.js) -->
<script src="https://unpkg.com/3d-force-graph"></script>
</head>
<body>
<nav>
<ul class="nav-links">
<li><a href="int-sec.html" class="int-sec">Inteligencia y Seguridad</a></li>
</ul>
</nav>
<main class="split-screen">
<!-- PANEL IZQUIERDO: el grafo -->
<div id="graphPanel">
<div id="intSecContainer"></div>
<div class="background">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<img src="/images/flujos.jpg">
<script>
setTimeout(function() {
const fondo = document.querySelector('.background');
fondo.classList.add('fade-out');
fondo.style.pointerEvents = 'none';
}, 1000);
</script>
</div>
</div>
<!-- PANEL DERECHO: detalle de la noticia -->
<div id="detailPanel">
<p class="placeholder">Haz click en un nodo para ver aquí la noticia completa.</p>
</div>
</main>
<div id="sidebar">
<h2>Parámetros</h2>
<form id="paramForm">
<label for="fecha_inicio">Fecha de inicio:</label>
<input type="date" id="fecha_inicio" name="fecha_inicio">
<label for="fecha_fin">Fecha de fin:</label>
<input type="date" id="fecha_fin" name="fecha_fin">
<label for="nodos">Nodos:</label>
<input type="number" id="nodos" name="nodos" value="100">
<label for="complejidad">Complejidad:</label>
<input type="range" id="complejidad" name="complejidad" min="1" max="40" value="20">
<label for="param1">Búsqueda por palabra:</label>
<input type="text" id="param1" name="param1">
<label for="param2">Búsqueda por temática personalizada:</label>
<input type="text" id="param2" name="param2">
<input type="submit" value="Aplicar">
</form>
</div>
<button id="sidebarToggle">Toggle Sidebar</button>
<footer>
<p>
<a href="#">GitHub</a> |
<a href="#">Telegram</a> |
<a href="#">Email</a> |
<a href="#">Web de Tor</a>
</p>
</footer>
<!-- Incluir tu script al final del body -->
<script src="output_int_sec.js"></script>
</body>
</html>

View file

@ -0,0 +1,132 @@
body {
font-family: Arial, sans-serif;
background-color: #000000;
margin: 0;
padding: 0;
color: #ffffff; /* Cambiar el color del texto a blanco */
}
/* Estilo del formulario */
.login-container {
max-width: 400px;
margin: 30px auto;
background-color: #111111; /* Fondo negro */
border: 2px solid #39ff14; /* Borde verde claro */
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: relative; /* Añadimos posición relativa para que los elementos internos se posicionen respecto a este contenedor */
}
/* ... Estilos existentes ... */
/* Estilos para los enlaces dentro del formulario */
.login-container a {
color: #39ff14; /* Cambiar el color del enlace a verde claro */
text-decoration: none;
transition: color 0.3s ease;
}
.login-container a:hover {
color: #2ECC40; /* Cambiar el color del enlace en hover a verde más claro */
}
/* ... Resto de tus estilos ... */
h1 {
text-align: center;
margin-bottom: 20px;
color: #39ff14; /* Cambiar el color del título a verde claro */
}
form {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 8px;
color: #39ff14; /* Cambiar el color de las etiquetas a verde claro */
}
input[type="text"],
input[type="password"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 15px;
background-color: #222222; /* Fondo negro */
color: #ffffff; /* Texto blanco */
}
input[type="submit"] {
background-color: #007BFF;
color: #ffffff;
border: none;
padding: 10px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
input[type="submit"]:hover {
background-color: #0056b3;
border: 2px solid #39ff14; /* Borde verde claro en hover */
}
.header {
display: flex;
justify-content: center;
margin-bottom: 0px; /* Añadir margen inferior para separar el formulario del logo */
}
.logo {
width: 400px; /* Ajustar el tamaño de la imagen según sea necesario */
height: auto;
}
/* Estilos para las imágenes de fondo */
.canvas-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
background-color: #000000;
color: #ffffff;
min-height: 100vh; /* Ajusta la altura mínima para que ocupe toda la pantalla */
margin: 20px 0;
overflow: hidden; /* Evita que las imágenes desborden del contenedor */
}
.image-left,
.image-right {
height: 100vh; /* Ajusta la altura para que ocupe toda la pantalla */
width: auto; /* Ajusta el ancho de las imágenes para mantener la relación de aspecto */
position: absolute;
top: 0;
max-width: 40%; /* Ajusta el ancho máximo de las imágenes de fondo */
}
.image-left {
left: 0;
}
.image-right {
right: 0;
}
.login-content {
/* Ajusta estos estilos según tus necesidades */
padding: 20px;
margin: 0 auto;
max-width: 300px; /* Ajusta el ancho máximo del contenido */
position: relative; /* Añadimos posición relativa para que los elementos internos se posicionen respecto a este contenedor */
}
/* Añade el siguiente estilo para evitar que las imágenes desborden del contenedor */
.login-content > img {
max-width: 100%;
height: auto;
}

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Journalist Login</title>
<link rel="stylesheet" href="journalist.css">
</head>
<body>
<div class="header">
<img class="logo" src="/images/flujos_logo5.png" alt="Flujos Logo">
</div>
<div class="canvas-container">
<img class="image-left" src="/images/journalist_fondo.jpg" alt="Fondo izquierdo">
<div class="login-container">
<h1>Journalist Login</h1>
<form onsubmit="login(event)">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<label for="publicKey">Public Key:</label>
<input type="text" id="publicKey" name="publicKey" required>
<input type="submit" value="Login">
</form>
</div>
<img class="image-right" src="/images/journalist_fondo2.jpg" alt="Fondo derecho">
</div>
<script>
function login(event) {
event.preventDefault(); // Evitar que el formulario se envíe por defecto
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const publicKey = document.getElementById('publicKey').value;
// Enviar los datos del formulario mediante una petición AJAX a nuestro endpoint de login
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password, publicKey })
})
.then(response => response.json())
.then(data => {
// Si el servidor devuelve un redireccionamiento, redirigimos al usuario
if (data.redirect) {
window.location.href = data.redirect;
} else {
// Si no hay redireccionamiento, se podría mostrar un mensaje de error
console.log('Credenciales inválidas');
}
})
.catch(error => {
console.error('Error en la petición AJAX:', error);
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,377 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
text-shadow: 10px 10px 20px rgba(0, 255, 76, 0.3);
}
body {
font-family: 'Fira Code', monospace;
background: #000000;
color: #000000;
}
header {
position: relative;
height: 100px;
background-color: #000000;
display: flex;
justify-content: center;
align-items: center;
}
.logo {
width: 400px;
height: 140px;
margin-left: 400px;
align-items: center;
}
.header-content {
display: flex;
align-items: center;
position: relative;
max-width: 1200px;
width: 100%;
}
.header-buttons {
display: flex;
align-items: center;
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
}
.small-button {
margin-left: 5px;
margin-right: 3px;
padding: 4px 8px;
font-size: 10px;
color: #ffffff;
text-decoration: none;
background-color: #000000;
border: 2px solid #ffffff;
border-radius: 20px;
transition: all 0.3s ease;
}
.small-button:hover {
background-color: #ffffff;
color: #000000;
text-shadow: none;
}
/* ... Resto de tus estilos ... */
/* ... Estilos existentes ... */
.logo {
/* Ajusta el tamaño de la imagen según tus necesidades */
width: 400px;
height: 140px;
}
.buttons {
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
display: flex;
align-items: center;
}
.buttons a {
display: block;
margin-left: 10px;
padding: 10px 15px;
color: #ffffff;
text-decoration: none;
border: 1px solid #ffffff;
border-radius: 20px;
}
.glob-war:hover .logo { text-shadow: 2px 2px 8px #39ff14; }
.int-sec:hover .logo { text-shadow: 2px 2px 8px #ff69b4; }
.climate:hover .logo { text-shadow: 2px 2px 8px #ff4500; }
.eco-corp:hover .logo { text-shadow: 2px 2px 8px #006400; }
.popl-up:hover .logo { text-shadow: 2px 2px 8px #00008b; }
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em; /* incrementa la distancia entre los elementos */
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
/* Estilo de los botones */
position:relative;
border: none;
transition: .4s ease-in;
z-index: 1;
width: 40vw;
height: 20vh;
border:3vw;
align-items: center;
border: 3px solid ;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 0 0 6px rgba(0,0,0,0.3);
/* Estilo hover de los botones */
box-shadow: 2vw 1vw;
border: 3px solid black ;
}
.glob-war:hover { color: #39ff14; }
.int-sec:hover { color: #ff69b4; }
.climate:hover { color: #ff4500; }
.eco-corp:hover { color: #00fff2; }
.popl-up:hover { color: #0066ff; }
.glob-war {
color: #FF4136;
background-color: red;
}
.int-sec {
color: #0074D9;
background-color: darkblue;
}
.climate {
color: #2ECC40;
background-color: lightgreen;
}
.eco-corp {
color: #FFDC00;
background-color: yellow;
}
.popl-up {
color: #FF851B;
background-color: orange;
}
.background {
display: flex;
height: 100vh;
width: 99%;
overflow-x: scroll;
}
.background a {
display: block;
width: 20%;
height: 90vh;
transition: transform 1.5s ease;
}
.background img {
width: 20%;
height: 90vh;
object-fit: cover;
transition: transform 1.5s ease;
}
.background a img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 1.5s ease;
}
.background img:hover {
transform: scale(1.1);
}
#sidebar.active {
transform: translateX(0);
}
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-90%);
transition: transform 0.3s ease-out;
overflow: auto;
font-size:18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar:hover {
transform: translateX(0);
}
#sidebarToggle {
position: absolute;
left: 1em;
top: 1em;
background: #007BFF;
color: #39ff14;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
z-index: 2;
}
#sidebarToggle {
display: none;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size:20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; /* Añade el borde al texto */
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14; /* Añade el borde verde al input */
background: black; /* Cambia el fondo del input a negro */
color: #39ff14; /* Cambia el color del texto en el input a verde */
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 1;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
#canvasContainer {
position: absolute;
width: 100%;
height: 100%;
opacity: 0.5;
pointer-events: none;
}
/* ... Estilos existentes ... */
@media screen and (max-width: 768px) {
.nav-links a {
font-size: 0.8em; /* reduce el tamaño de la fuente */
padding: 10px 20px; /* reduce el padding */
}
.nav-links {
list-style: none;
display: flex;
gap: 2em; /* incrementa la distancia entre los elementos */
}
}

View file

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Journalist Enter</title>
<link rel="stylesheet" href="styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<!-- Header -->
<header>
<div class="header-content">
<img src="/images/flujos_logo5.png" alt="FLUJØS 3D Logo" class="logo">
<div class="header-buttons">
<a href="login.html" class="small-button">Login/Sign Up</a>
<div class="small-button">Language</div> <!-- El botón de idioma con el dropdown se implementará más adelante -->
<a href="journalist.html" class="small-button">Journalist</a>
</div>
</div>
</header>
<!-- Sidebar Toggle Button -->
<button id="sidebarToggle">Toggle Sidebar</button>
<!-- Main Content -->
<main>
<!-- Graficos generados por el script de output.js -->
<div id="canvasContainer"></div>
<div class="background">
<!-- Enlaces a otras páginas -->
<a href="glob-war.html">
<img class="img1" src="/images/flujos.jpg">
</a>
<a href="int-sec.html">
<img class="img2" src="/images/fujos5.jpg">
</a>
<a href="climate.html">
<img class="img3" src="/images/flujos6.jpg">
</a>
<a href="eco-corp.html">
<img class="img4" src="/images/flujos4.jpg">
</a>
<a href="popl-up.html">
<img class="img5" src="/images/flujos3.jpg">
</a>
</div>
</main>
<!-- Sidebar with Parameters -->
<div id="sidebar">
<h2>Parámetros</h2>
<form>
<!-- Aquí van los parámetros específicos para añadir nodos -->
<label for="nodo_nombre">Nombre del Nodo:</label>
<input type="text" id="nodo_nombre" name="nodo_nombre" required>
<label for="nodo_descripcion">Descripción del Nodo:</label>
<input type="text" id="nodo_descripcion" name="nodo_descripcion" required>
<label for="nodo_imagen">Imagen del Nodo:</label>
<input type="file" id="nodo_imagen" name="nodo_imagen" accept="image/*" required>
<input type="submit" value="Añadir Nodo">
</form>
</div>
<!-- Footer -->
<footer>
<p><a href="#">GitHub</a> | <a href="#">Telegram</a> | <a href="#">Email</a> | <a href="#">Web de Tor</a></p>
</footer>
<!-- Script para los gráficos generados por output.js -->
<script src="output.js"></script>
</body>
</html>

View file

@ -0,0 +1,438 @@
import { AmbientLight, DirectionalLight, Vector3, REVISION } from "./three.module.js";
const three = window.THREE
? window.THREE // Prefer consumption from global THREE, if exists
: { AmbientLight, DirectionalLight, Vector3, REVISION };
import { DragControls as ThreeDragControls } from './DragControls.js';
import ThreeForceGraph from "./three-forcegraph.js";
import ThreeRenderObjects from "./three-render-objects.js";
import accessorFn from "./accessor-fn.js";
import Kapsule from "./kapsule.js";
import linkKapsule from './kapsule-link.js';
//
const CAMERA_DISTANCE2NODES_FACTOR = 170;
//
// Expose config from forceGraph
const bindFG = linkKapsule('forceGraph', ThreeForceGraph);
const linkedFGProps = Object.assign(...[
'jsonUrl',
'graphData',
'numDimensions',
'dagMode',
'dagLevelDistance',
'dagNodeFilter',
'onDagError',
'nodeRelSize',
'nodeId',
'nodeVal',
'nodeResolution',
'nodeColor',
'nodeAutoColorBy',
'nodeOpacity',
'nodeVisibility',
'nodeThreeObject',
'nodeThreeObjectExtend',
'linkSource',
'linkTarget',
'linkVisibility',
'linkColor',
'linkAutoColorBy',
'linkOpacity',
'linkWidth',
'linkResolution',
'linkCurvature',
'linkCurveRotation',
'linkMaterial',
'linkThreeObject',
'linkThreeObjectExtend',
'linkPositionUpdate',
'linkDirectionalArrowLength',
'linkDirectionalArrowColor',
'linkDirectionalArrowRelPos',
'linkDirectionalArrowResolution',
'linkDirectionalParticles',
'linkDirectionalParticleSpeed',
'linkDirectionalParticleWidth',
'linkDirectionalParticleColor',
'linkDirectionalParticleResolution',
'forceEngine',
'd3AlphaDecay',
'd3VelocityDecay',
'd3AlphaMin',
'ngraphPhysics',
'warmupTicks',
'cooldownTicks',
'cooldownTime',
'onEngineTick',
'onEngineStop'
].map(p => ({ [p]: bindFG.linkProp(p)})));
const linkedFGMethods = Object.assign(...[
'refresh',
'getGraphBbox',
'd3Force',
'd3ReheatSimulation',
'emitParticle'
].map(p => ({ [p]: bindFG.linkMethod(p)})));
// Expose config from renderObjs
const bindRenderObjs = linkKapsule('renderObjs', ThreeRenderObjects);
const linkedRenderObjsProps = Object.assign(...[
'width',
'height',
'backgroundColor',
'showNavInfo',
'enablePointerInteraction'
].map(p => ({ [p]: bindRenderObjs.linkProp(p)})));
const linkedRenderObjsMethods = Object.assign(
...[
'lights',
'cameraPosition',
'postProcessingComposer'
].map(p => ({ [p]: bindRenderObjs.linkMethod(p)})),
{
graph2ScreenCoords: bindRenderObjs.linkMethod('getScreenCoords'),
screen2GraphCoords: bindRenderObjs.linkMethod('getSceneCoords')
}
);
//
export default Kapsule({
props: {
nodeLabel: { default: 'name', triggerUpdate: false },
linkLabel: { default: 'name', triggerUpdate: false },
linkHoverPrecision: { default: 1, onChange: (p, state) => state.renderObjs.lineHoverPrecision(p), triggerUpdate: false },
enableNavigationControls: {
default: true,
onChange(enable, state) {
const controls = state.renderObjs.controls();
if (controls) {
controls.enabled = enable;
// trigger mouseup on re-enable to prevent sticky controls
enable && controls.domElement && controls.domElement.dispatchEvent(new PointerEvent('pointerup'));
}
},
triggerUpdate: false
},
enableNodeDrag: { default: true, triggerUpdate: false },
onNodeDrag: { default: () => {}, triggerUpdate: false },
onNodeDragEnd: { default: () => {}, triggerUpdate: false },
onNodeClick: { triggerUpdate: false },
onNodeRightClick: { triggerUpdate: false },
onNodeHover: { triggerUpdate: false },
onLinkClick: { triggerUpdate: false },
onLinkRightClick: { triggerUpdate: false },
onLinkHover: { triggerUpdate: false },
onBackgroundClick: { triggerUpdate: false },
onBackgroundRightClick: { triggerUpdate: false },
...linkedFGProps,
...linkedRenderObjsProps
},
methods: {
zoomToFit: function(state, transitionDuration, padding, ...bboxArgs) {
state.renderObjs.fitToBbox(
state.forceGraph.getGraphBbox(...bboxArgs),
transitionDuration,
padding
);
return this;
},
pauseAnimation: function(state) {
if (state.animationFrameRequestId !== null) {
cancelAnimationFrame(state.animationFrameRequestId);
state.animationFrameRequestId = null;
}
return this;
},
resumeAnimation: function(state) {
if (state.animationFrameRequestId === null) {
this._animationCycle();
}
return this;
},
_animationCycle(state) {
if (state.enablePointerInteraction) {
// reset canvas cursor (override dragControls cursor)
this.renderer().domElement.style.cursor = null;
}
// Frame cycle
state.forceGraph.tickFrame();
state.renderObjs.tick();
state.animationFrameRequestId = requestAnimationFrame(this._animationCycle);
},
scene: state => state.renderObjs.scene(), // Expose scene
camera: state => state.renderObjs.camera(), // Expose camera
renderer: state => state.renderObjs.renderer(), // Expose renderer
controls: state => state.renderObjs.controls(), // Expose controls
tbControls: state => state.renderObjs.tbControls(), // To be deprecated
_destructor: function() {
this.pauseAnimation();
this.graphData({ nodes: [], links: []});
},
...linkedFGMethods,
...linkedRenderObjsMethods
},
stateInit: ({ controlType, rendererConfig, extraRenderers }) => {
const forceGraph = new ThreeForceGraph();
return {
forceGraph,
renderObjs: ThreeRenderObjects({ controlType, rendererConfig, extraRenderers })
.objects([forceGraph]) // Populate scene
.lights([
new three.AmbientLight(0xcccccc, Math.PI),
new three.DirectionalLight(0xffffff, 0.6 * Math.PI)
])
}
},
init: function(domNode, state) {
// Wipe DOM
domNode.innerHTML = '';
// Add relative container
domNode.appendChild(state.container = document.createElement('div'));
state.container.style.position = 'relative';
// Add renderObjs
const roDomNode = document.createElement('div');
state.container.appendChild(roDomNode);
state.renderObjs(roDomNode);
const camera = state.renderObjs.camera();
const renderer = state.renderObjs.renderer();
const controls = state.renderObjs.controls();
controls.enabled = !!state.enableNavigationControls;
state.lastSetCameraZ = camera.position.z;
// Add info space
let infoElem;
state.container.appendChild(infoElem = document.createElement('div'));
infoElem.className = 'graph-info-msg';
infoElem.textContent = '';
// config forcegraph
state.forceGraph
.onLoading(() => { infoElem.textContent = 'Loading...' })
.onFinishLoading(() => { infoElem.textContent = '' })
.onUpdate(() => {
// sync graph data structures
state.graphData = state.forceGraph.graphData();
// re-aim camera, if still in default position (not user modified)
if (camera.position.x === 0 && camera.position.y === 0 && camera.position.z === state.lastSetCameraZ && state.graphData.nodes.length) {
camera.lookAt(state.forceGraph.position);
state.lastSetCameraZ = camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;
}
})
.onFinishUpdate(() => {
// Setup node drag interaction
if (state._dragControls) {
const curNodeDrag = state.graphData.nodes.find(node => node.__initialFixedPos && !node.__disposeControlsAfterDrag); // detect if there's a node being dragged using the existing drag controls
if (curNodeDrag) {
curNodeDrag.__disposeControlsAfterDrag = true; // postpone previous controls disposal until drag ends
} else {
state._dragControls.dispose(); // cancel previous drag controls
}
state._dragControls = undefined;
}
if (state.enableNodeDrag && state.enablePointerInteraction && state.forceEngine === 'd3') { // Can't access node positions programmatically in ngraph
const dragControls = state._dragControls = new ThreeDragControls(
state.graphData.nodes.map(node => node.__threeObj).filter(obj => obj),
camera,
renderer.domElement
);
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false; // Disable controls while dragging
// track drag object movement
event.object.__initialPos = event.object.position.clone();
event.object.__prevPos = event.object.position.clone();
const node = getGraphObj(event.object).__data;
!node.__initialFixedPos && (node.__initialFixedPos = {fx: node.fx, fy: node.fy, fz: node.fz});
!node.__initialPos && (node.__initialPos = {x: node.x, y: node.y, z: node.z});
// lock node
['x', 'y', 'z'].forEach(c => node[`f${c}`] = node[c]);
// drag cursor
renderer.domElement.classList.add('grabbable');
});
dragControls.addEventListener('drag', function (event) {
const nodeObj = getGraphObj(event.object);
if (!event.object.hasOwnProperty('__graphObjType')) {
// If dragging a child of the node, update the node object instead
const initPos = event.object.__initialPos;
const prevPos = event.object.__prevPos;
const newPos = event.object.position;
nodeObj.position.add(newPos.clone().sub(prevPos)); // translate node object by the motion delta
prevPos.copy(newPos);
newPos.copy(initPos); // reset child back to its initial position
}
const node = nodeObj.__data;
const newPos = nodeObj.position;
const translate = {x: newPos.x - node.x, y: newPos.y - node.y, z: newPos.z - node.z};
// Move fx/fy/fz (and x/y/z) of nodes based on object new position
['x', 'y', 'z'].forEach(c => node[`f${c}`] = node[c] = newPos[c]);
state.forceGraph
.d3AlphaTarget(0.3) // keep engine running at low intensity throughout drag
.resetCountdown(); // prevent freeze while dragging
node.__dragged = true;
state.onNodeDrag(node, translate);
});
dragControls.addEventListener('dragend', function (event) {
delete(event.object.__initialPos); // remove tracking attributes
delete(event.object.__prevPos);
const node = getGraphObj(event.object).__data;
// dispose previous controls if needed
if (node.__disposeControlsAfterDrag) {
dragControls.dispose();
delete(node.__disposeControlsAfterDrag);
}
const initFixedPos = node.__initialFixedPos;
const initPos = node.__initialPos;
const translate = {x: initPos.x - node.x, y: initPos.y - node.y, z: initPos.z - node.z};
if (initFixedPos) {
['x', 'y', 'z'].forEach(c => {
const fc = `f${c}`;
if (initFixedPos[fc] === undefined) {
delete(node[fc])
}
});
delete(node.__initialFixedPos);
delete(node.__initialPos);
if (node.__dragged) {
delete(node.__dragged);
state.onNodeDragEnd(node, translate);
}
}
state.forceGraph
.d3AlphaTarget(0) // release engine low intensity
.resetCountdown(); // let the engine readjust after releasing fixed nodes
if (state.enableNavigationControls) {
controls.enabled = true; // Re-enable controls
controls.domElement && controls.domElement.ownerDocument && controls.domElement.ownerDocument.dispatchEvent(
// simulate mouseup to ensure the controls don't take over after dragend
new PointerEvent('pointerup', { pointerType: 'touch' })
);
}
// clear cursor
renderer.domElement.classList.remove('grabbable');
});
}
});
// config renderObjs
three.REVISION < 155 && (state.renderObjs.renderer().useLegacyLights = false); // force behavior for three < 155
state.renderObjs
.hoverOrderComparator((a, b) => {
// Prioritize graph objects
const aObj = getGraphObj(a);
if (!aObj) return 1;
const bObj = getGraphObj(b);
if (!bObj) return -1;
// Prioritize nodes over links
const isNode = o => o.__graphObjType === 'node';
return isNode(bObj) - isNode(aObj);
})
.tooltipContent(obj => {
const graphObj = getGraphObj(obj);
return graphObj ? accessorFn(state[`${graphObj.__graphObjType}Label`])(graphObj.__data) || '' : '';
})
.hoverDuringDrag(false)
.onHover(obj => {
// Update tooltip and trigger onHover events
const hoverObj = getGraphObj(obj);
if (hoverObj !== state.hoverObj) {
const prevObjType = state.hoverObj ? state.hoverObj.__graphObjType : null;
const prevObjData = state.hoverObj ? state.hoverObj.__data : null;
const objType = hoverObj ? hoverObj.__graphObjType : null;
const objData = hoverObj ? hoverObj.__data : null;
if (prevObjType && prevObjType !== objType) {
// Hover out
const fn = state[`on${prevObjType === 'node' ? 'Node' : 'Link'}Hover`];
fn && fn(null, prevObjData);
}
if (objType) {
// Hover in
const fn = state[`on${objType === 'node' ? 'Node' : 'Link'}Hover`];
fn && fn(objData, prevObjType === objType ? prevObjData : null);
}
// set pointer if hovered object is clickable
renderer.domElement.classList[
((hoverObj && state[`on${objType === 'node' ? 'Node' : 'Link'}Click`]) || (!hoverObj && state.onBackgroundClick)) ? 'add' : 'remove'
]('clickable');
state.hoverObj = hoverObj;
}
})
.clickAfterDrag(false)
.onClick((obj, ev) => {
const graphObj = getGraphObj(obj);
if (graphObj) {
const fn = state[`on${graphObj.__graphObjType === 'node' ? 'Node' : 'Link'}Click`];
fn && fn(graphObj.__data, ev);
} else {
state.onBackgroundClick && state.onBackgroundClick(ev);
}
})
.onRightClick((obj, ev) => {
// Handle right-click events
const graphObj = getGraphObj(obj);
if (graphObj) {
const fn = state[`on${graphObj.__graphObjType === 'node' ? 'Node' : 'Link'}RightClick`];
fn && fn(graphObj.__data, ev);
} else {
state.onBackgroundRightClick && state.onBackgroundRightClick(ev);
}
});
//
// Kick-off renderer
this._animationCycle();
}
});
//
function getGraphObj(object) {
let obj = object;
// recurse up object chain until finding the graph object
while (obj && !obj.hasOwnProperty('__graphObjType')) {
obj = obj.parent;
}
return obj;
}

View file

@ -0,0 +1,410 @@
import {
Controls,
Matrix4,
Plane,
Raycaster,
Vector2,
Vector3,
MOUSE,
TOUCH
} from './three.module.js';
const _plane = new Plane();
const _pointer = new Vector2();
const _offset = new Vector3();
const _diff = new Vector2();
const _previousPointer = new Vector2();
const _intersection = new Vector3();
const _worldPosition = new Vector3();
const _inverseMatrix = new Matrix4();
const _up = new Vector3();
const _right = new Vector3();
let _selected = null, _hovered = null;
const _intersections = [];
const STATE = {
NONE: - 1,
PAN: 0,
ROTATE: 1
};
class DragControls extends Controls {
constructor( objects, camera, domElement = null ) {
super( camera, domElement );
this.objects = objects;
this.recursive = true;
this.transformGroup = false;
this.rotateSpeed = 1;
this.raycaster = new Raycaster();
// interaction
this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE };
this.touches = { ONE: TOUCH.PAN };
// event listeners
this._onPointerMove = onPointerMove.bind( this );
this._onPointerDown = onPointerDown.bind( this );
this._onPointerCancel = onPointerCancel.bind( this );
this._onContextMenu = onContextMenu.bind( this );
//
if ( domElement !== null ) {
this.connect();
}
}
connect() {
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
this.domElement.addEventListener( 'pointerup', this._onPointerCancel );
this.domElement.addEventListener( 'pointerleave', this._onPointerCancel );
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
this.domElement.style.touchAction = 'none'; // disable touch scroll
}
disconnect() {
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
this.domElement.removeEventListener( 'pointerup', this._onPointerCancel );
this.domElement.removeEventListener( 'pointerleave', this._onPointerCancel );
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
this.domElement.style.touchAction = 'auto';
this.domElement.style.cursor = '';
}
dispose() {
this.disconnect();
}
_updatePointer( event ) {
const rect = this.domElement.getBoundingClientRect();
_pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
_pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
}
_updateState( event ) {
// determine action
let action;
if ( event.pointerType === 'touch' ) {
action = this.touches.ONE;
} else {
switch ( event.button ) {
case 0:
action = this.mouseButtons.LEFT;
break;
case 1:
action = this.mouseButtons.MIDDLE;
break;
case 2:
action = this.mouseButtons.RIGHT;
break;
default:
action = null;
}
}
// determine state
switch ( action ) {
case MOUSE.PAN:
case TOUCH.PAN:
this.state = STATE.PAN;
break;
case MOUSE.ROTATE:
case TOUCH.ROTATE:
this.state = STATE.ROTATE;
break;
default:
this.state = STATE.NONE;
}
}
getRaycaster() {
console.warn( 'THREE.DragControls: getRaycaster() has been deprecated. Use controls.raycaster instead.' ); // @deprecated r169
return this.raycaster;
}
setObjects( objects ) {
console.warn( 'THREE.DragControls: setObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
this.objects = objects;
}
getObjects() {
console.warn( 'THREE.DragControls: getObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
return this.objects;
}
activate() {
console.warn( 'THREE.DragControls: activate() has been renamed to connect().' ); // @deprecated r169
this.connect();
}
deactivate() {
console.warn( 'THREE.DragControls: deactivate() has been renamed to disconnect().' ); // @deprecated r169
this.disconnect();
}
set mode( value ) {
console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
}
get mode() {
console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
}
}
function onPointerMove( event ) {
const camera = this.object;
const domElement = this.domElement;
const raycaster = this.raycaster;
if ( this.enabled === false ) return;
this._updatePointer( event );
raycaster.setFromCamera( _pointer, camera );
if ( _selected ) {
if ( this.state === STATE.PAN ) {
if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
}
} else if ( this.state === STATE.ROTATE ) {
_diff.subVectors( _pointer, _previousPointer ).multiplyScalar( this.rotateSpeed );
_selected.rotateOnWorldAxis( _up, _diff.x );
_selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
}
this.dispatchEvent( { type: 'drag', object: _selected } );
_previousPointer.copy( _pointer );
} else {
// hover support
if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
_intersections.length = 0;
raycaster.setFromCamera( _pointer, camera );
raycaster.intersectObjects( this.objects, this.recursive, _intersections );
if ( _intersections.length > 0 ) {
const object = _intersections[ 0 ].object;
_plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
if ( _hovered !== object && _hovered !== null ) {
this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
domElement.style.cursor = 'auto';
_hovered = null;
}
if ( _hovered !== object ) {
this.dispatchEvent( { type: 'hoveron', object: object } );
domElement.style.cursor = 'pointer';
_hovered = object;
}
} else {
if ( _hovered !== null ) {
this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
domElement.style.cursor = 'auto';
_hovered = null;
}
}
}
}
_previousPointer.copy( _pointer );
}
function onPointerDown( event ) {
const camera = this.object;
const domElement = this.domElement;
const raycaster = this.raycaster;
if ( this.enabled === false ) return;
this._updatePointer( event );
this._updateState( event );
_intersections.length = 0;
raycaster.setFromCamera( _pointer, camera );
raycaster.intersectObjects( this.objects, this.recursive, _intersections );
if ( _intersections.length > 0 ) {
if ( this.transformGroup === true ) {
// look for the outermost group in the object's upper hierarchy
_selected = findGroup( _intersections[ 0 ].object );
} else {
_selected = _intersections[ 0 ].object;
}
_plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
if ( this.state === STATE.PAN ) {
_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
} else if ( this.state === STATE.ROTATE ) {
// the controls only support Y+ up
_up.set( 0, 1, 0 ).applyQuaternion( camera.quaternion ).normalize();
_right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion ).normalize();
}
}
domElement.style.cursor = 'move';
this.dispatchEvent( { type: 'dragstart', object: _selected } );
}
_previousPointer.copy( _pointer );
}
function onPointerCancel() {
if ( this.enabled === false ) return;
if ( _selected ) {
this.dispatchEvent( { type: 'dragend', object: _selected } );
_selected = null;
}
this.domElement.style.cursor = _hovered ? 'pointer' : 'auto';
this.state = STATE.NONE;
}
function onContextMenu( event ) {
if ( this.enabled === false ) return;
event.preventDefault();
}
function findGroup( obj, group = null ) {
if ( obj.isGroup ) group = obj;
if ( obj.parent === null ) return group;
return findGroup( obj.parent, group );
}
export { DragControls };

View file

@ -0,0 +1,22 @@
// Version 1.5.0 accessor-fn - https://github.com/vasturiano/accessor-fn
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.accessorFn = factory());
})(this, (function () { 'use strict';
var index = (function (p) {
return typeof p === 'function' ? p // fn
: typeof p === 'string' ? function (obj) {
return obj[p];
} // property name
: function (obj) {
return p;
};
}); // constant
return index;
}));
//# sourceMappingURL=accessor-fn.js.map
export default TableCsv

View file

@ -0,0 +1,26 @@
export default function(kapsulePropName, kapsuleType) {
const dummyK = new kapsuleType(); // To extract defaults
dummyK._destructor && dummyK._destructor();
return {
linkProp: function(prop) { // link property config
return {
default: dummyK[prop](),
onChange(v, state) { state[kapsulePropName][prop](v) },
triggerUpdate: false
}
},
linkMethod: function(method) { // link method pass-through
return function(state, ...args) {
const kapsuleInstance = state[kapsulePropName];
const returnVal = kapsuleInstance[method](...args);
return returnVal === kapsuleInstance
? this // chain based on the parent object, not the inner kapsule
: returnVal;
}
}
}
}

View file

@ -0,0 +1,716 @@
// Version 1.14.4 kapsule - https://github.com/vasturiano/kapsule
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Kapsule = factory());
})(this, (function () { 'use strict';
function _iterableToArrayLimit(arr, i) {
var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (_x = (_i = _i.call(arr)).next, 0 === i) {
if (Object(_i) !== _i) return;
_n = !1;
} else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
} catch (err) {
_d = !0, _e = err;
} finally {
try {
if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
var freeGlobal$1 = freeGlobal;
/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root = freeGlobal$1 || freeSelf || Function('return this')();
var root$1 = root;
/** Built-in value references. */
var Symbol$1 = root$1.Symbol;
var Symbol$2 = Symbol$1;
/** Used for built-in method references. */
var objectProto$1 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto$1.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString$1 = objectProto$1.toString;
/** Built-in value references. */
var symToStringTag$1 = Symbol$2 ? Symbol$2.toStringTag : undefined;
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
var isOwn = hasOwnProperty.call(value, symToStringTag$1),
tag = value[symToStringTag$1];
try {
value[symToStringTag$1] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString$1.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag$1] = tag;
} else {
delete value[symToStringTag$1];
}
}
return result;
}
/** Used for built-in method references. */
var objectProto = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto.toString;
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString.call(value);
}
/** `Object#toString` result references. */
var nullTag = '[object Null]',
undefinedTag = '[object Undefined]';
/** Built-in value references. */
var symToStringTag = Symbol$2 ? Symbol$2.toStringTag : undefined;
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return (symToStringTag && symToStringTag in Object(value))
? getRawTag(value)
: objectToString(value);
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value) {
return typeof value == 'symbol' ||
(isObjectLike(value) && baseGetTag(value) == symbolTag);
}
/** Used to match a single whitespace character. */
var reWhitespace = /\s/;
/**
* Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
* character of `string`.
*
* @private
* @param {string} string The string to inspect.
* @returns {number} Returns the index of the last non-whitespace character.
*/
function trimmedEndIndex(string) {
var index = string.length;
while (index-- && reWhitespace.test(string.charAt(index))) {}
return index;
}
/** Used to match leading whitespace. */
var reTrimStart = /^\s+/;
/**
* The base implementation of `_.trim`.
*
* @private
* @param {string} string The string to trim.
* @returns {string} Returns the trimmed string.
*/
function baseTrim(string) {
return string
? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
: string;
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
/** Used as references for various `Number` constants. */
var NAN = 0 / 0;
/** Used to detect bad signed hexadecimal string values. */
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
/** Used to detect binary string values. */
var reIsBinary = /^0b[01]+$/i;
/** Used to detect octal string values. */
var reIsOctal = /^0o[0-7]+$/i;
/** Built-in method references without a dependency on `root`. */
var freeParseInt = parseInt;
/**
* Converts `value` to a number.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {number} Returns the number.
* @example
*
* _.toNumber(3.2);
* // => 3.2
*
* _.toNumber(Number.MIN_VALUE);
* // => 5e-324
*
* _.toNumber(Infinity);
* // => Infinity
*
* _.toNumber('3.2');
* // => 3.2
*/
function toNumber(value) {
if (typeof value == 'number') {
return value;
}
if (isSymbol(value)) {
return NAN;
}
if (isObject(value)) {
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
value = isObject(other) ? (other + '') : other;
}
if (typeof value != 'string') {
return value === 0 ? value : +value;
}
value = baseTrim(value);
var isBinary = reIsBinary.test(value);
return (isBinary || reIsOctal.test(value))
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
: (reIsBadHex.test(value) ? NAN : +value);
}
/**
* Gets the timestamp of the number of milliseconds that have elapsed since
* the Unix epoch (1 January 1970 00:00:00 UTC).
*
* @static
* @memberOf _
* @since 2.4.0
* @category Date
* @returns {number} Returns the timestamp.
* @example
*
* _.defer(function(stamp) {
* console.log(_.now() - stamp);
* }, _.now());
* // => Logs the number of milliseconds it took for the deferred invocation.
*/
var now = function() {
return root$1.Date.now();
};
var now$1 = now;
/** Error message constants. */
var FUNC_ERROR_TEXT = 'Expected a function';
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max,
nativeMin = Math.min;
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked. The debounced function comes with a `cancel` method to cancel
* delayed `func` invocations and a `flush` method to immediately invoke them.
* Provide `options` to indicate whether `func` should be invoked on the
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
* with the last arguments provided to the debounced function. Subsequent
* calls to the debounced function return the result of the last `func`
* invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is
* invoked on the trailing edge of the timeout only if the debounced function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `_.debounce` and `_.throttle`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to debounce.
* @param {number} [wait=0] The number of milliseconds to delay.
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=false]
* Specify invoking on the leading edge of the timeout.
* @param {number} [options.maxWait]
* The maximum time `func` is allowed to be delayed before it's invoked.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // Avoid costly calculations while the window size is in flux.
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
*
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
* jQuery(element).on('click', _.debounce(sendMail, 300, {
* 'leading': true,
* 'trailing': false
* }));
*
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
* var source = new EventSource('/stream');
* jQuery(source).on('message', debounced);
*
* // Cancel the trailing debounced invocation.
* jQuery(window).on('popstate', debounced.cancel);
*/
function debounce(func, wait, options) {
var lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = toNumber(wait) || 0;
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall;
return maxing
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
function shouldInvoke(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
var time = now$1();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge(now$1());
}
function debounced() {
var time = now$1(),
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
clearTimeout(timerId);
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
var Prop = /*#__PURE__*/_createClass(function Prop(name, _ref) {
var _ref$default = _ref["default"],
defaultVal = _ref$default === void 0 ? null : _ref$default,
_ref$triggerUpdate = _ref.triggerUpdate,
triggerUpdate = _ref$triggerUpdate === void 0 ? true : _ref$triggerUpdate,
_ref$onChange = _ref.onChange,
onChange = _ref$onChange === void 0 ? function (newVal, state) {} : _ref$onChange;
_classCallCheck(this, Prop);
this.name = name;
this.defaultVal = defaultVal;
this.triggerUpdate = triggerUpdate;
this.onChange = onChange;
});
function index (_ref2) {
var _ref2$stateInit = _ref2.stateInit,
stateInit = _ref2$stateInit === void 0 ? function () {
return {};
} : _ref2$stateInit,
_ref2$props = _ref2.props,
rawProps = _ref2$props === void 0 ? {} : _ref2$props,
_ref2$methods = _ref2.methods,
methods = _ref2$methods === void 0 ? {} : _ref2$methods,
_ref2$aliases = _ref2.aliases,
aliases = _ref2$aliases === void 0 ? {} : _ref2$aliases,
_ref2$init = _ref2.init,
initFn = _ref2$init === void 0 ? function () {} : _ref2$init,
_ref2$update = _ref2.update,
updateFn = _ref2$update === void 0 ? function () {} : _ref2$update;
// Parse props into Prop instances
var props = Object.keys(rawProps).map(function (propName) {
return new Prop(propName, rawProps[propName]);
});
return function () {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// Holds component state
var state = Object.assign({}, stateInit instanceof Function ? stateInit(options) : stateInit,
// Support plain objects for backwards compatibility
{
initialised: false
});
// keeps track of which props triggered an update
var changedProps = {};
// Component constructor
function comp(nodeElement) {
initStatic(nodeElement, options);
digest();
return comp;
}
var initStatic = function initStatic(nodeElement, options) {
initFn.call(comp, nodeElement, state, options);
state.initialised = true;
};
var digest = debounce(function () {
if (!state.initialised) {
return;
}
updateFn.call(comp, state, changedProps);
changedProps = {};
}, 1);
// Getter/setter methods
props.forEach(function (prop) {
comp[prop.name] = getSetProp(prop);
function getSetProp(_ref3) {
var prop = _ref3.name,
_ref3$triggerUpdate = _ref3.triggerUpdate,
redigest = _ref3$triggerUpdate === void 0 ? false : _ref3$triggerUpdate,
_ref3$onChange = _ref3.onChange,
onChange = _ref3$onChange === void 0 ? function (newVal, state) {} : _ref3$onChange,
_ref3$defaultVal = _ref3.defaultVal,
defaultVal = _ref3$defaultVal === void 0 ? null : _ref3$defaultVal;
return function (_) {
var curVal = state[prop];
if (!arguments.length) {
return curVal;
} // Getter mode
var val = _ === undefined ? defaultVal : _; // pick default if value passed is undefined
state[prop] = val;
onChange.call(comp, val, state, curVal);
// track changed props
!changedProps.hasOwnProperty(prop) && (changedProps[prop] = curVal);
if (redigest) {
digest();
}
return comp;
};
}
});
// Other methods
Object.keys(methods).forEach(function (methodName) {
comp[methodName] = function () {
var _methods$methodName;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return (_methods$methodName = methods[methodName]).call.apply(_methods$methodName, [comp, state].concat(args));
};
});
// Link aliases
Object.entries(aliases).forEach(function (_ref4) {
var _ref5 = _slicedToArray(_ref4, 2),
alias = _ref5[0],
target = _ref5[1];
return comp[alias] = comp[target];
});
// Reset all component props to their default value
comp.resetProps = function () {
props.forEach(function (prop) {
comp[prop.name](prop.defaultVal);
});
return comp;
};
//
comp.resetProps(); // Apply all prop defaults
state._rerender = digest; // Expose digest method
return comp;
};
}
return index;
}));
//# sourceMappingURL=kapsule.js.map
export default TableCsv

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,162 @@
// Importación de ForceGraph3D
import ForceGraph3D from './libs/3d-force-graph.min.js'; // Asegúrate de que está en la carpeta libs
// Importación del cliente de Elasticsearch
import { Client } from 'https://cdn.skypack.dev/@elastic/elasticsearch';
// Configuración de la conexión a Elasticsearch
const client = new Client({ node: 'http://localhost:9200' });
// Definir la clase Graph
class Graph {
constructor(containerId) {
this.graph = ForceGraph3D()(document.getElementById(containerId));
this.maxNodes = 200; // Número máximo de nodos a mostrar
this.minSimilarity = 5; // Umbral mínimo de similitud
}
// Configuración del número máximo de nodos
setMaxNodes(maxNodes) {
this.maxNodes = maxNodes;
}
// Configuración del umbral de similitud
setMinSimilarity(minSimilarity) {
this.minSimilarity = minSimilarity;
}
// Cargar nodos desde Elasticsearch
async loadNodes(subtematica = '', palabraClave = '', fechaInicio = '', fechaFin = '') {
const query = {
bool: {
must: [
{ match: { tema: 'corporaciones y organizaciones internacionales' } }
],
filter: []
}
};
if (subtematica) {
query.bool.must.push({ match: { subtema: subtematica } });
}
if (palabraClave) {
query.bool.must.push({ match: { contenido: palabraClave } });
}
if (fechaInicio && fechaFin) {
query.bool.filter.push({
range: {
fecha: {
gte: fechaInicio,
lte: fechaFin
}
}
});
}
const response = await client.search({
index: 'informacion', // Usamos el índice correcto
body: {
query: query,
size: this.maxNodes,
sort: { importancia: 'desc' }
}
});
return response.body.hits.hits.map(hit => ({
id: hit._source.nombre_archivo,
group: hit._source.tipo_archivo,
tema: hit._source.tema,
content: hit._source.contenido,
fecha: hit._source.fecha
}));
}
// Cargar relaciones directamente desde Elasticsearch
async loadLinks() {
const response = await client.search({
index: 'informacion', // Asegurarse de que el índice correcto se utiliza para las relaciones
body: {
query: {
match_all: {} // Obtenemos todas las relaciones almacenadas
},
size: this.maxNodes // Limitamos el tamaño de la respuesta
}
});
const links = response.body.hits.hits.map(hit => {
const content = hit._source.contenido;
const lines = content.split('\n'); // Asumiendo que las relaciones están en formato línea por línea
return lines.map(line => {
const [source, target, similarity] = line.split(',');
if (parseFloat(similarity) >= this.minSimilarity) {
return {
source: path.basename(source.trim(), '.txt'),
target: path.basename(target.trim(), '.txt'),
value: parseFloat(similarity)
};
}
}).filter(link => link); // Filtramos los enlaces válidos
});
return links.flat();
}
// Crear datos del gráfico
createGraphData(nodes, links) {
return {
nodes,
links
};
}
// Mostrar el contenido de un nodo al hacer clic
async showNodeContent(node) {
try {
const response = await client.search({
index: 'informacion',
body: {
query: {
match: { nombre_archivo: node.id }
}
},
size: 1
});
const content = response.body.hits.hits[0]._source.contenido;
const contentDiv = document.getElementById('contentDisplay');
contentDiv.innerText = content || "No hay contenido disponible.";
} catch (error) {
console.error("Error al obtener contenido:", error);
const contentDiv = document.getElementById('contentDisplay');
contentDiv.innerText = "Error al obtener contenido.";
}
}
// Dibujar el gráfico
async drawGraph(subtematica = '', palabraClave = '', fechaInicio = '', fechaFin = '') {
const nodes = await this.loadNodes(subtematica, palabraClave, fechaInicio, fechaFin);
const links = await this.loadLinks(); // Cargamos las relaciones desde Elasticsearch
const graphData = this.createGraphData(nodes, links);
this.graph
.graphData(graphData)
.backgroundColor('black')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(5)
.linkColor(() => 'yellow')
.onNodeClick(node => {
this.showNodeContent(node);
})
.forceEngine('d3')
.d3Force('charge').strength(-200)
.d3Force('link').distance(50);
}
}
// Inicializar el gráfico
const myGraph = new Graph('canvasContainer');
myGraph.drawGraph();

View file

@ -0,0 +1,126 @@
// output_climate_pruebas.js
// Obtener el contenedor del gráfico
document.addEventListener('DOMContentLoaded', () => {
const elem = document.getElementById('climateContainer');
const form = document.getElementById('paramForm');
// Inicializar el gráfico 3D
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(1)
.linkColor(() => '#42FF00')
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => { elem.style.cursor = node ? 'pointer' : null; })
.forceEngine('d3')
//.d3Force('charge', d3.forceManyBody().strength(-300))
//.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) {
console.log('Contenido del nodo:', content);
}
// 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', 'cambio climático');
// 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();
});

View file

@ -0,0 +1,120 @@
// output_eco_corp.js
// Obtener el contenedor del gráfico
document.addEventListener('DOMContentLoaded', () => {
const elem = document.getElementById('ecoCorpContainer');
const form = document.getElementById('paramForm');
// Inicializar el gráfico 3D
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(1)
.linkColor(() => 'yellow')
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => { elem.style.cursor = node ? 'pointer' : null; })
.forceEngine('d3')
//.d3Force('charge', d3.forceManyBody().strength(-300))
//.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) {
console.log('Contenido del nodo:', content);
}
// 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', 'economía y corporaciones');
// 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();
});

View file

@ -0,0 +1,123 @@
// output_glob_war.js
// Obtener el contenedor del gráfico
const elem = document.getElementById('globWarContainer');
// Inicializar el gráfico
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(2)
.linkColor(() => 'green')
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => {
elem.style.cursor = node ? 'pointer' : null;
})
.forceEngine('d3')
.d3Force('charge', d3.forceManyBody().strength(-10))
.d3Force('link', d3.forceLink().distance(30).strength(1));
// Función para obtener datos del servidor
async function getData(paramsObj = {}) {
try {
let url = '/api/data';
const params = new URLSearchParams();
// Tema por defecto: "guerra global"
params.append('tema', 'guerra global');
// Agregar parámetros del formulario si existen
for (const key in paramsObj) {
if (paramsObj[key]) {
params.append(key, paramsObj[key]);
}
}
url += `?${params.toString()}`;
const response = await fetch(url);
const data = await response.json();
console.log('Datos recibidos del servidor:', data);
// Filtrar enlaces para nodos existentes
const nodeIds = new Set(data.nodes.map(n => n.id));
data.links = data.links.filter(l => nodeIds.has(l.source) && nodeIds.has(l.target));
console.log('Enlaces tras filtrar:', data.links);
return data;
} catch (error) {
console.error('Error al obtener datos del servidor:', error);
return { nodes: [], links: [] };
}
}
// Mostrar contenido del nodo al hacer click
function showNodeContent(content) {
console.log('Contenido del nodo:', content);
// aquí podrías mostrarlo en un panel si quieres
}
// Centrar y escalar el gráfico
function centerGraph() {
setTimeout(() => {
const width = elem.clientWidth;
const height = elem.clientHeight;
const padding = Math.min(width, height) * 0.1;
graph.zoomToFit(400, padding);
}, 500);
}
// Al cambiar el tamaño de la ventana, reajustar
window.addEventListener('resize', () => {
graph.width(elem.clientWidth);
graph.height(elem.clientHeight);
centerGraph();
});
// Gestionar el submit del formulario de parámetros
document.getElementById('paramForm').addEventListener('submit', event => {
event.preventDefault();
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;
const paramsObj = {
subtematica,
palabraClave,
fechaInicio,
fechaFin,
nodos,
complejidad
};
getData(paramsObj).then(data => {
graph.graphData(data);
centerGraph();
});
});
// Cargar datos iniciales (guerra global) y renderizar
getData().then(data => {
if (data.nodes && data.nodes.length) {
console.log(`Se recibieron ${data.nodes.length} nodos.`);
graph.graphData(data);
centerGraph();
} else {
console.warn('No se recibieron nodos para "guerra global".');
}
});

View file

@ -0,0 +1,120 @@
// output_glob_war.js
// Obtener el contenedor del gráfico
document.addEventListener('DOMContentLoaded', () => {
const elem = document.getElementById('globWarContainer');
const form = document.getElementById('paramForm');
// Inicializar el gráfico 3D
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(1)
.linkColor(() => 'red')
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => { elem.style.cursor = node ? 'pointer' : null; })
.forceEngine('d3')
//.d3Force('charge', d3.forceManyBody().strength(-300))
//.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) {
console.log('Contenido del nodo:', content);
}
// 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', 'guerra global');
// 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();
});

View file

@ -0,0 +1,178 @@
// 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();
});

View file

@ -0,0 +1,122 @@
// output_popl_up.js
// Obtener el contenedor del gráfico
const elem = document.getElementById('poplUpContainer');
// Inicializar el gráfico
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(5)
.linkColor(() => 'green')
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => {
elem.style.cursor = node ? 'pointer' : null;
})
.forceEngine('d3')
.d3Force('charge', d3.forceManyBody().strength(-10))
.d3Force('link', d3.forceLink().distance(30).strength(1));
// Función para obtener datos del servidor
async function getData(paramsObj = {}) {
try {
let url = '/api/data';
const params = new URLSearchParams();
// Fijar el tema principal
params.append('tema', 'movimientos populares y levantamientos');
// Agregar otros parámetros si se han definido
for (const key in paramsObj) {
if (paramsObj[key]) {
params.append(key, paramsObj[key]);
}
}
url += `?${params.toString()}`;
const response = await fetch(url);
const data = await response.json();
console.log('📦 Datos recibidos:', data);
// Validar y limpiar links
const nodeIds = new Set(data.nodes.map(node => node.id));
data.links = data.links.filter(link => {
const valid = nodeIds.has(link.source) && nodeIds.has(link.target);
if (!valid) {
console.log(`❌ Enlace inválido eliminado: ${link.source} -> ${link.target}`);
}
return valid;
});
return data;
} catch (error) {
console.error('⚠️ Error al obtener datos:', error);
return { nodes: [], links: [] };
}
}
// Mostrar contenido del nodo en consola (puedes ampliar esta función)
function showNodeContent(content) {
console.log('🧠 Contenido del nodo:', content);
}
// Centrar el gráfico tras dibujarlo
function centerGraph() {
setTimeout(() => {
const width = elem.clientWidth;
const height = elem.clientHeight;
const padding = Math.min(width, height) * 0.1;
graph.zoomToFit(400, padding);
}, 500);
}
// Redibujar al cambiar tamaño ventana
window.addEventListener('resize', () => {
graph.width(elem.clientWidth);
graph.height(elem.clientHeight);
centerGraph();
});
// Manejar el formulario
document.getElementById('paramForm').addEventListener('submit', function(event) {
event.preventDefault();
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;
const paramsObj = {
subtematica,
palabraClave,
fechaInicio,
fechaFin,
nodos,
complejidad,
};
getData(paramsObj).then(graphData => {
if (graphData) {
console.log("✅ Redibujando con nuevos datos...");
graph.graphData(graphData);
centerGraph();
}
});
});
// Obtener datos iniciales
getData().then(graphData => {
if (graphData && graphData.nodes && graphData.nodes.length > 0) {
console.log(`✅ Se recibieron ${graphData.nodes.length} nodos.`);
graph.graphData(graphData);
centerGraph();
} else {
console.warn("⚠️ No se recibieron nodos para mostrar.");
console.log("🔍 Respuesta completa:", graphData);
}
});

View file

@ -0,0 +1,129 @@
// output_popl_up_pruebas.js
// Obtener el contenedor del gráfico
document.addEventListener('DOMContentLoaded', () => {
const elem = document.getElementById('poplUpContainer');
const form = document.getElementById('paramForm');
// Inicializar el gráfico 3D
const graph = ForceGraph3D()(elem)
.backgroundColor('#000000')
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeVal(1)
.linkColor(() => 'orange')
.onNodeClick(node => showNodeContent(node.content))
.onNodeHover(node => { elem.style.cursor = node ? 'pointer' : null; })
.forceEngine('d3')
//.d3Force('charge', d3.forceManyBody().strength(-300))
//.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) {
console.log('Contenido del nodo:', content);
}
// 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', 'demografía y sociedad');
// 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();
});

219
VISUALIZACION/public/popl-up.css Executable file
View file

@ -0,0 +1,219 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
text-shadow: 10px 10px 20px rgba(0, 255, 76, 0.3);
}
body {
font-family: 'Fira Code', monospace;
background: #000000;
color: #000000;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 70px;
background-color: #000000;
color: #ffffff;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
}
.logo {
font-family: "Retrolift", sans-serif;
font-size: 4em;
font-weight: bold;
letter-spacing: 4px;
text-shadow: 6px 6px 6px #73ff00;
margin-bottom: 10px;
}
.popl-up:hover .logo { text-shadow: 2px 2px 8px #00008b; }
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em;
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
position: relative;
border: none;
transition: .4s ease-in;
z-index: 1;
width: 40vw;
height: 20vh;
border: 3vw;
align-items: center;
border: 3px solid;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 0 0 6px rgba(0,0,0,0.3);
border: 3px solid black;
}
.popl-up:hover { color: #0066ff; }
.popl-up { color: #FF851B; background-color: orange; }
.background {
display: flex;
height: 100vh;
width: 99%;
overflow-x: scroll;
}
.background a {
display: block;
width: 20%;
height: 90vh;
transition: transform 1.5s ease;
}
.background img {
width: 20%;
height: 90vh;
object-fit: cover;
transition: transform 1.5s ease;
}
.background img:hover {
transform: scale(1.1);
}
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-220px);
transition: transform 0.3s ease-out;
overflow: auto;
font-size: 18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar:hover {
transform: translateX(0);
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14;
background: black;
color: #39ff14;
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 6;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
.iframe-container {
width: calc(100% - 250px);
height: 100vh;
background: #000000;
margin: 0 auto;
border: none;
z-index: 1;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: absolute;
left: 250px;
}

View file

@ -0,0 +1,90 @@
+<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Demografía y Sociedad</title>
<link rel="stylesheet" href="popl-up.css">
<!-- Fuentes -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
<!-- Librerías necesarias -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<script src="https://unpkg.com/3d-force-graph"></script>
</head>
<body>
<!-- Navegación -->
<nav>
<ul class="nav-links">
<li><a href="popl-up.html" class="popl-up">Demografía y Sociedad</a></li>
</ul>
</nav>
<!-- Contenedor principal -->
<main>
<div id="poplUpContainer" style="position: absolute; width: 100%; height: 100%; z-index: 0;"></div>
<!-- Fondo animado -->
<div class="background">
<img src="/images/flujos3.jpg">
<img src="/images/flujos3.jpg">
<img src="/images/flujos3.jpg">
<img src="/images/flujos3.jpg">
<img src="/images/flujos3.jpg">
</div>
<script>
setTimeout(() => {
const fondo = document.querySelector('.background');
fondo.classList.add('fade-out');
fondo.style.pointerEvents = 'none';
}, 1000);
</script>
</main>
<!-- Barra lateral de filtros -->
<div id="sidebar">
<h2>Parámetros</h2>
<form id="paramForm">
<label for="fecha_inicio">Fecha de inicio:</label>
<input type="date" id="fecha_inicio" name="fecha_inicio">
<label for="fecha_fin">Fecha de fin:</label>
<input type="date" id="fecha_fin" name="fecha_fin">
<label for="nodos">Nodos:</label>
<input type="number" id="nodos" name="nodos" value="100">
<label for="complejidad">Complejidad:</label>
<input type="range" id="complejidad" name="complejidad" min="1" max="40" value="20">
<label for="param1">Búsqueda por palabra:</label>
<input type="text" id="param1" name="param1">
<label for="param2">Búsqueda por temática personalizada:</label>
<input type="text" id="param2" name="param2">
<input type="submit" value="Aplicar">
</form>
</div>
<!-- Botón para colapsar la barra -->
<button id="sidebarToggle">Toggle Sidebar</button>
<!-- Footer -->
<footer>
<p>
<a href="#">GitHub</a> |
<a href="#">Telegram</a> |
<a href="#">Email</a> |
<a href="#">Web de Tor</a>
</p>
</footer>
<!-- Script principal -->
<script src="output_popl_up_pruebas.js"></script>
</body>
</html>

View file

@ -0,0 +1,72 @@
:root {
--cube-size: 70px; /* Tamaño reducido de los cubos */
--line-color: #333;
--hover-color: #2ecc71;
}
.graph-container {
position: relative;
width: 100vw;
height: 100vh;
background-color: #000;
border: 1px solid #ddd;
margin: 20px auto;
perspective: 1000px;
}
.cube-container {
position: absolute;
width: var(--cube-size);
height: var(--cube-size);
transform-style: preserve-3d;
transition: transform 1s;
}
.cube-container:hover {
transform: rotateX(360deg) rotateY(360deg);
}
.cube {
position: absolute;
width: var(--cube-size);
height: var(--cube-size);
transform-style: preserve-3d;
}
.face {
position: absolute;
width: var(--cube-size);
height: var(--cube-size);
background-color: var(--hover-color);
border: 1px solid #000;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
color: white;
}
/* Posiciones de las caras del cubo */
.front { transform: translateZ(calc(var(--cube-size) / 2)); }
.back { transform: rotateY(180deg) translateZ(calc(var(--cube-size) / 2)); }
.left { transform: rotateY(-90deg) translateZ(calc(var(--cube-size) / 2)); }
.right { transform: rotateY(90deg) translateZ(calc(var(--cube-size) / 2)); }
.top { transform: rotateX(90deg) translateZ(calc(var(--cube-size) / 2)); }
.bottom { transform: rotateX(-90deg) translateZ(calc(var(--cube-size) / 2)); }
.lines {
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
.line-path {
stroke: var(--line-color);
stroke-width: 2;
transition: stroke 0.3s;
}
.line-path:hover {
stroke: #e74c3c;
}

View file

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Noticias de Guerras y Conflictos Políticos - Disposición Aleatoria</title>
<link rel="stylesheet" type="text/css" href="script_eco-corp.css">
</head>
<body>
<h1>Noticias de Guerras y Conflictos Políticos - Últimos 10 años</h1>
<div class="graph-container" id="graph-container">
<!-- Los cubos serán añadidos dinámicamente con JavaScript -->
<svg class="lines" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" id="svg-lines">
<!-- Las flechas también serán generadas dinámicamente -->
</svg>
</div>
<script>
const numCubes = 5; // Número de cubos
const graphContainer = document.getElementById('graph-container');
const svgLines = document.getElementById('svg-lines');
let cubePositions = [];
// Función para generar una posición aleatoria dentro del contenedor
function getRandomPosition(maxWidth, maxHeight) {
return {
x: Math.floor(Math.random() * (maxWidth - 100)),
y: Math.floor(Math.random() * (maxHeight - 100))
};
}
// Función para generar cubos aleatoriamente
function generateCubes(num) {
for (let i = 0; i < num; i++) {
const position = getRandomPosition(window.innerWidth, window.innerHeight);
const cubeContainer = document.createElement('div');
cubeContainer.classList.add('cube-container');
cubeContainer.style.top = position.y + 'px';
cubeContainer.style.left = position.x + 'px';
cubeContainer.innerHTML = `
<div class="cube">
<div class="face front">País ${i+1}</div>
<div class="face back">Detalle ${i+1}</div>
<div class="face left">Año ${2010 + i}</div>
<div class="face right">Muertos ${(i+1)*1000}</div>
<div class="face top">ONU</div>
<div class="face bottom">Fuente</div>
</div>
`;
cubePositions.push({x: position.x + 35, y: position.y + 35}); // Ajustar al centro del cubo
graphContainer.appendChild(cubeContainer);
}
}
// Función para generar las flechas que conectan los cubos
function generateLines() {
for (let i = 0; i < cubePositions.length - 1; i++) {
const x1 = cubePositions[i].x;
const y1 = cubePositions[i].y;
const x2 = cubePositions[i + 1].x;
const y2 = cubePositions[i + 1].y;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x1);
line.setAttribute('y1', y1);
line.setAttribute('x2', x2);
line.setAttribute('y2', y2);
line.setAttribute('stroke', '#333');
line.setAttribute('stroke-width', '2');
svgLines.appendChild(line);
}
}
// Generar cubos y flechas al cargar la página
generateCubes(numCubes);
generateLines();
</script>
</body>
</html>

995
VISUALIZACION/public/styles.css Executable file
View file

@ -0,0 +1,995 @@
@font-face {
font-family: "Retrolift";
src: url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff2") format("woff2"),
url("/home/pancho/PROGRAMACION/FLUJOS/retrolift.woff") format("woff");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Fira Code';
}
body {
font-family: 'Fira Code', monospace;
background: #000000;
color: #000000;
}
header {
position: relative;
height: 120px;
background-color: #000000;
display: flex;
justify-content: center;
align-items: center;
padding-top: 20px;
}
.title {
font-family: 'Nosifer';
color: #ffffff;
font-size: 6.5rem;
margin: 0 auto;
line-height: 1.2;
align-self: center;
}
.header-content {
display: flex;
align-items: center;
position: relative;
max-width: 1200px;
width: 100%;
}
.header-buttons {
display: flex;
align-items: center;
position: absolute;
top: 70%;
right: 10px;
transform: translateY(-50%);
}
.small-button {
margin-left: 5px;
margin-right: 3px;
padding: 4px 8px;
font-size: 10px;
color: #ffffff;
text-decoration: none;
background-color: #000000;
border: 2px solid #ffffff;
border-radius: 20px;
transition: all 0.3s ease;
}
.small-button:hover {
background-color: #ffffff;
color: #000000;
text-shadow: none;
}
nav {
display: flex;
justify-content: center;
background-color: #000000;
color: #ff1a1a;
padding: 10px 0;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1);
margin-top: 40px;
margin-bottom: 5px;
}
.nav-links {
list-style: none;
display: flex;
gap: 14em;
}
.nav-links a {
color: #000000;
text-decoration: none;
font-size: 1em;
font-weight: bold;
transition: color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
padding: 20px 40px;
box-shadow: 0 0 0px rgba(28, 103, 241, 0);
position: relative;
border: none;
transition: .4s ease-in;
z-index: 1;
width: 40vw;
height: 20vh;
border: 3vw;
align-items: center;
border: 3px solid;
}
.nav-links a:hover {
transform: scale(1.05);
box-shadow: 0 0 6px rgba(0,0,0,0.3);
box-shadow: 2vw 1vw;
border: 3px solid black;
}
.glob-war:hover { color: #39ff14; }
.int-sec:hover { color: #ff69b4; }
.climate:hover { color: #ff4500; }
.eco-corp:hover { color: #00fff2; }
.popl-up:hover { color: #0066ff; }
.glob-war {
color: #FF4136;
background-color: red;
}
.int-sec {
color: #0074D9;
background-color: darkblue;
}
.climate {
color: #2ECC40;
background-color: lightgreen;
}
.eco-corp {
color: #FFDC00;
background-color: yellow;
}
.popl-up {
color: #FF851B;
background-color: orange;
}
.background {
display: flex;
justify-content: center; /* Centrar horizontalmente */
height: 100vh;
width: 100%;
overflow-x: hidden; /* Oculta cualquier posible desplazamiento horizontal */
margin: 0 auto;
padding: 0;
}
.background a {
display: block;
flex: 1; /* Asegura que cada imagen se expanda uniformemente para ocupar todo el espacio disponible */
height: 100%;
transition: transform 1.5s ease;
}
.background img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 1.5s ease;
margin: 0; /* Elimina cualquier margen alrededor de las imágenes */
}
.background img:hover {
transform: scale(1.1);
}
#sidebar.active {
transform: translateX(0);
}
#sidebar {
position: fixed;
left: 0;
top: 0;
width: 250px;
height: 100vh;
background-image: url("/images/flujos7.jpg");
background-size: cover;
color: #39ff14;
padding: 30px;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(-90%);
transition: transform 0.3s ease-out;
overflow: auto;
font-size: 18px;
z-index: 3;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar h2 {
color: #39ff14;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 15px;
text-shadow: -1px 0 black, 0 3px black, 3px 0 black, 0 -1px black;
}
#sidebar:hover {
transform: translateX(0);
}
#sidebarToggle {
position: absolute;
left: 1em;
top: 1em;
background: #007BFF;
color: #39ff14;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
z-index: 2;
}
#sidebarToggle {
display: none;
}
#sidebar form {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 20px;
}
#sidebar label {
color: #39ff14;
font-size: 0.9em;
font-weight: bold;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#sidebar input {
padding: 10px;
border: 2px solid #39ff14;
background: black;
color: #39ff14;
border-radius: 5px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease;
}
#sidebar input:hover {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.3);
}
#sidebar input[type="submit"] {
color: #39ff14;
background: #ff6600;
cursor: pointer;
}
#sidebar input[type="submit"]:hover {
background: #ff6600;
}
footer {
width: 100%;
background-color: #000000;
color: #39ff14;
text-align: center;
padding: 10px 0;
position: fixed;
bottom: 0;
font-family: 'Courier New', Courier, monospace;
z-index: 1;
}
footer a {
color: #39ff14;
text-decoration: none;
transition: color 0.3s ease;
}
footer a:hover {
color: #2ECC40;
}
footer p {
margin: 0;
}
#canvasContainer {
position: absolute;
width: 100%;
height: 100%;
opacity: 0.5;
pointer-events: none;
}
.button-overlay {
position: absolute;
top: 100px;
left: 0;
width: 100%;
height: calc(100% - 100px);
display: flex;
justify-content: space-around;
align-items: center;
}
.button-column {
display: flex;
flex-direction: column;
gap: 20px; /* Incrementa el espacio entre botones */
width: 13%; /* Reducido el ancho de las columnas de botones */
}
.overlay-button {
display: block;
padding: 10px 15px; /* Ajustar para reducir el tamaño del botón */
color: #ffffff;
font-weight: bold; /* Hacer el texto más grueso */
text-decoration: none;
border-radius: 30px; /* Bordes redondeados para un efecto de cilindro */
font-size: 1em;
border: 2px solid #ffffff;
background-color: black; /* Fondo negro */
transition: transform 0.3s ease, box-shadow 0.3s ease;
text-align: center;
}
.overlay-button:hover {
transform: scale(1.05);
color: #ffffff;
background-color: transparent;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
/* Media Queries for larger screens */
@media screen and (max-width: 1920px) {
.background a {
width: 18%;
}
.button-column {
width: 18%;
}
.overlay-button {
font-size: 1.1em;
padding: 16px 20px;
}
.title {
font-size: 7rem;
}
.small-button {
font-size: 1em;
padding: 8px 14px;
}
.nav-links {
list-style: none;
display: flex;
gap: 14em;
}
}
@media screen and (max-width: 1600px) {
.background a {
width: 20%;
}
.button-column {
width: 20%;
}
.overlay-button {
font-size: 1em;
padding: 14px 18px;
}
.title {
font-size: 6.5rem;
}
.small-button {
font-size: 0.9em;
padding: 7px 13px;
}
.nav-links {
list-style: none;
display: flex;
gap: 6em;
}
}
@media screen and (max-width: 1440px) {
.background a {
width: 22%;
}
.button-column {
width: 22%;
}
.overlay-button {
font-size: 0.95em;
padding: 12px 16px;
}
.title {
font-size: 6rem;
}
.small-button {
font-size: 0.85em;
padding: 6px 12px;
}
.nav-links a {
font-size: 0.85em;
padding: 16px 30px;
}
}
@media screen and (max-width: 1280px) {
.background a {
width: 25%;
}
.button-column {
width: 25%;
}
.overlay-button {
font-size: 0.5em;
padding: 10px 14px;
}
.title {
font-size: 5.5rem;
}
.small-button {
font-size: 0.4em;
padding: 6px 11px;
}
.nav-links {
list-style: none;
display: flex;
gap: 5em;
}
}
@media screen and (max-width: 1024px) {
.background a {
width: 30%;
}
.button-column {
width: 30%;
}
.overlay-button {
font-size: 0.35em;
padding: 9px 12px;
}
.title {
font-size: 5rem;
}
.small-button {
font-size: 0.75em;
padding: 5px 10px;
}
.nav-links a {
font-size: 0.75em;
padding: 12px 20px;
}
}
/* Para teléfonos de 768px de ancho en portrait */
@media screen and (max-width: 768px) and (orientation: portrait) {
.background a {
width: 90%;
}
.button-column {
width: 90%;
gap: 10px;
}
.overlay-button {
font-size: 0.6rem;
padding: 6px 8px;
}
.title {
font-size: 3rem;
}
.small-button {
font-size: 0.3rem;
padding: 2px 2px;
}
.nav-links {
gap: 2em; /* Reduce el espacio entre los enlaces del nav */
}
.nav-links a {
font-size: 0.8rem;
padding: 8px 16px;
}
}
/* Para teléfonos de 576px de ancho en portrait */
@media screen and (max-width: 576px) and (orientation: portrait) {
.background a {
width: 95%;
}
.button-column {
width: 95%;
gap: 30px;
}
.overlay-button {
font-size: 0.3rem;
padding: 5px 7px;
}
.title {
font-size: 4rem;
}
.small-button {
font-size: 0.3rem;
padding: 4px 7px;
}
.nav-links {
gap: 1.5em;
}
.nav-links a {
font-size: 0.4rem;
padding: 7px 14px;
}
}
/* Para teléfonos de 411px de ancho en portrait */
@media screen and (max-width: 411px) and (orientation: portrait) {
.background a {
width: 100%;
}
.button-column {
width: 100%;
gap: 5px;
}
.overlay-button {
font-size: 0.4rem;
padding: 4px 5px;
}
.title {
font-size: 1.5rem;
}
.small-button {
font-size: 0.3rem;
padding: 4px 5px;
}
.nav-links {
gap: 1em;
}
.nav-links a {
font-size: 0.6rem;
padding: 6px 12px;
}
}
/* Para teléfonos de 375px de ancho en portrait */
@media screen and (max-width: 375px) and (orientation: portrait) {
.background a {
width: 100%;
}
.button-column {
width: 100%;
gap: 3px;
}
.overlay-button {
font-size: 0.35rem;
padding: 3px 4px;
}
.title {
font-size: 1.3rem;
}
.small-button {
font-size: 0.35rem;
padding: 3px 4px;
}
.nav-links {
gap: 0.75em;
}
.nav-links a {
font-size: 0.5rem;
padding: 5px 10px;
}
}
/* Para teléfonos de 320px de ancho en portrait */
@media screen and (max-width: 320px) and (orientation: portrait) {
.background a {
width: 100%;
}
.button-column {
width: 100%;
gap: 2px;
}
.overlay-button {
font-size: 0.3rem;
padding: 2px 3px;
}
.title {
font-size: 1.2rem;
}
.small-button {
font-size: 0.3rem;
padding: 2px 3px;
}
.nav-links {
gap: 0.5em;
}
.nav-links a {
font-size: 0.4rem;
padding: 4px 8px;
}
}
/* For devices with smaller screens (e.g., 600px - 800px width) */
@media screen and (min-width: 600px) and (max-width: 800px) and (max-height: 400px) and (orientation: landscape) {
.background {
display: flex;
justify-content: center;
height: 100vh;
width: 100%;
overflow-x: auto;
}
.button-overlay {
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 100%;
padding-bottom: 0px;
}
.button-column {
width: 15%;
display: flex;
flex-direction: column;
justify-content: flex-end;
height: auto;
}
.overlay-button {
font-size: 0.3rem;
padding: 2px 2px;
}
.title {
font-size: 1rem;
}
.small-button {
font-size: 0.3rem;
padding: 2px 2px;
}
.nav-links a {
font-size: 0.3rem; /* Reduced font size */
padding: 15px 30px; /* Adjusted padding */
}
}
/* For medium-sized devices (e.g., 800px - 1000px width) */
@media screen and (min-width: 800px) and (max-width: 1000px) and (max-height: 450px) and (orientation: landscape) {
.background {
display: flex;
justify-content: center;
height: 100vh;
width: 100%;
overflow-x: auto;
}
.button-overlay {
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 100%;
padding-bottom: 0px;
}
.button-column {
width: 16%;
display: flex;
flex-direction: column;
justify-content: flex-end;
height: auto;
}
.overlay-button {
font-size: 0.35rem;
padding: 3px 3px;
}
.title {
font-size: 4rem;
}
.small-button {
font-size: 0.35rem;
padding: 3px 3px;
}
.nav-links a {
font-size: 0.3rem; /* Reduced font size */
padding: 15px 30px; /* Adjusted padding */
}
}
/* For larger devices (e.g., 1000px - 1200px width) */
@media screen and (min-width: 1000px) and (max-width: 1200px) and (max-height: 500px) and (orientation: landscape) {
.background {
display: flex;
justify-content: center;
height: 100vh;
width: 100%;
overflow-x: auto;
}
.button-overlay {
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 100%;
padding-bottom: 0px;
}
.button-column {
width: 17%;
display: flex;
flex-direction: column;
justify-content: flex-end;
height: auto;
}
.overlay-button {
font-size: 0.4rem;
padding: 3px 4px;
}
.title {
font-size: 4rem;
}
.small-button {
font-size: 0.35rem;
padding: 3px 4px;
}
.nav-links a {
font-size: 0.3rem; /* Reduced font size */
padding: 15px 30px; /* Adjusted padding */
}
}
/* For even larger devices (e.g., 1200px - 1500px width) */
@media screen and (min-width: 1200px) and (max-width: 1500px) and (max-height: 700px) and (orientation: landscape) {
.background {
display: flex;
justify-content: center;
height: 100vh;
width: 100%;
overflow-x: auto;
}
.button-overlay {
display: flex;
justify-content: space-around;
align-items: flex-end;
height: 100%;
padding-bottom: 0px;
}
.button-column {
width: 18%;
display: flex;
flex-direction: column;
justify-content: flex-end;
height: auto;
}
.overlay-button {
font-size: 0.45rem;
padding: 4px 5px;
}
.title {
font-size: 4rem;
}
.small-button {
font-size: 0.35rem;
padding: 4px 5px;
}
.nav-links a {
font-size: 0.3rem; /* Reduced font size */
padding: 15px 30px; /* Adjusted padding */
}
}
@media screen and (max-width: 800px) and (orientation: landscape) {
.background a {
width: 100%;
}
.button-column {
width: 20%;
gap: 5px;
margin-top: 40px; /* Push buttons down to avoid navbar */
}
.overlay-button {
font-size: 0.35rem;
padding: 2px 4px;
}
.title {
font-size: 1.4rem;
}
.small-button {
font-size: 0.35rem;
padding: 2px 4px;
}
.nav-links a {
font-size: 0.35rem;
padding: 2px 4px;
}
}
/* For devices with width around 700px */
@media screen and (max-width: 700px) and (orientation: landscape) {
.background a {
width: 100%;
}
.button-column {
width: 25%;
gap: 5px;
margin-top: 50px; /* Further adjustment */
}
.overlay-button {
font-size: 0.3rem;
padding: 2px 3px;
}
.title {
font-size: 1.3rem;
}
.small-button {
font-size: 0.3rem;
padding: 2px 3px;
}
.nav-links a {
font-size: 0.3rem;
padding: 2px 3px;
}
}
/* For smaller devices with width around 600px */
@media screen and (max-width: 600px) and (orientation: landscape) {
.background a {
width: 100%;
}
.button-column {
width: 30%;
gap: 5px;
margin-top: 60px; /* Ensures the buttons are well below the navbar */
}
.overlay-button {
font-size: 0.4rem;
padding: 3px 5px;
}
.title {
font-size: 1.2rem;
}
.small-button {
font-size: 0.5rem;
padding: 3px 5px;
}
.nav-links a {
font-size: 0.5rem;
padding: 3px 5px;
z-index: 2;
}
}
/* Landscape mode for screens up to 480px wide */
@media screen and (max-width: 480px) and (orientation: landscape) {
.button-overlay {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
display: flex;
justify-content: space-around;
align-items: flex-end;
padding-bottom: 5px;
z-index: 1;
}
.button-column {
width: 28%;
gap: 2px;
margin-top:150px;
}
.overlay-button {
font-size: 0.45rem;
padding: 3px 4px;
}
.title {
font-size: 1rem;
}
.small-button {
font-size: 0.45rem;
padding: 3px 4px;
}
.nav-links a {
font-size: 0.45rem;
padding: 3px 4px;
z-index: 2;
}
}