Word2Vec / index.html
13Aluminium's picture
Update index.html
eed70d8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Word Embedding Visualization</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
body, html {
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Inter', sans-serif;
background: radial-gradient(ellipse at center, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
}
#info {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
color: #fff;
padding: 16px 20px;
border-radius: 12px;
font-size: 14px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
#info p {
margin: 0 0 8px 0;
font-weight: 400;
}
#info p:last-child {
margin-bottom: 0;
}
#countDisplay {
color: #64ffda;
font-weight: 600;
}
#wordInfo {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(15px);
color: #fff;
padding: 20px 24px;
border-radius: 12px;
display: none;
font-size: 14px;
border: 1px solid rgba(100, 255, 218, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
min-width: 200px;
}
#wordInfo strong {
color: #64ffda;
font-size: 18px;
font-weight: 600;
display: block;
margin-bottom: 12px;
}
#wordInfo .coord {
margin: 4px 0;
font-family: 'Courier New', monospace;
color: #b0bec5;
}
canvas {
display: block;
cursor: grab;
}
canvas:active {
cursor: grabbing;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #64ffda;
font-size: 18px;
font-weight: 500;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
</style>
</head>
<body>
<div id="loading" class="pulse">Loading word embeddings...</div>
<div id="info" style="display: none;">
<p>Visualizing <span id="countDisplay"></span> most frequent words</p>
<p><strong>Controls:</strong> Rotate: drag • Zoom: scroll • Pan: right-click + drag</p>
<p>Click any word sphere to inspect details</p>
</div>
<div id="wordInfo"></div>
<script type="importmap">
{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.module.min.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const MAX_WORDS = 4000;
let scene, camera, renderer, controls;
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let spheres = [];
let selectedSphere = null;
let originalMaterials = new Map();
let hoveredSphere = null;
init();
animate();
function init() {
document.getElementById('countDisplay').textContent = MAX_WORDS.toLocaleString();
// Scene setup with better background
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0a);
scene.fog = new THREE.Fog(0x0a0a0a, 50, 200);
// Camera with better initial position
camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(15, 10, 25);
// Renderer with enhanced settings
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.body.appendChild(renderer.domElement);
// Enhanced controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 5;
controls.maxDistance = 100;
controls.maxPolarAngle = Math.PI;
// Event listeners
window.addEventListener('resize', onResize);
window.addEventListener('pointermove', onPointerMove, false);
window.addEventListener('pointerdown', onClick, false);
// Load data
fetch('word_vectors_3d.json')
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(data => {
const subset = data.slice(0, MAX_WORDS);
createEnhancedSpheres(subset);
document.getElementById('loading').style.display = 'none';
document.getElementById('info').style.display = 'block';
})
.catch(e => {
console.error(e);
document.getElementById('loading').textContent = 'Error loading data. Make sure word_vectors_3d.json exists.';
});
}
function createEnhancedSpheres(data) {
// Compute bounds and normalize
let mins = { x: Infinity, y: Infinity, z: Infinity };
let maxs = { x: -Infinity, y: -Infinity, z: -Infinity };
data.forEach(p => {
mins.x = Math.min(mins.x, p.x);
mins.y = Math.min(mins.y, p.y);
mins.z = Math.min(mins.z, p.z);
maxs.x = Math.max(maxs.x, p.x);
maxs.y = Math.max(maxs.y, p.y);
maxs.z = Math.max(maxs.z, p.z);
});
// Scale factor for better visualization
const scale = 40;
// Create instanced geometry for better performance
const radius = 0.15;
const geometry = new THREE.SphereGeometry(radius, 12, 8);
// Create particle system for background effect
createStarField();
let group = new THREE.Group();
data.forEach((p, index) => {
// Normalize coordinates
const x = ((p.x - mins.x) / (maxs.x - mins.x) - 0.5) * scale;
const y = ((p.y - mins.y) / (maxs.y - mins.y) - 0.5) * scale;
const z = ((p.z - mins.z) / (maxs.z - mins.z) - 0.5) * scale;
// Color based on position with enhanced palette
const hue = (p.x - mins.x) / (maxs.x - mins.x);
const saturation = 0.7 + 0.3 * ((p.y - mins.y) / (maxs.y - mins.y));
const lightness = 0.4 + 0.4 * ((p.z - mins.z) / (maxs.z - mins.z));
const color = new THREE.Color().setHSL(hue * 0.8 + 0.1, saturation, lightness);
// Enhanced material with emissive properties
const material = new THREE.MeshPhysicalMaterial({
color: color,
emissive: color.clone().multiplyScalar(0.1),
metalness: 0.1,
roughness: 0.4,
clearcoat: 0.3,
clearcoatRoughness: 0.2,
transparent: true,
opacity: 0.8
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
mesh.userData = { ...p, index };
mesh.castShadow = true;
mesh.receiveShadow = true;
// Store original material
originalMaterials.set(mesh, material.clone());
spheres.push(mesh);
group.add(mesh);
});
scene.add(group);
setupLighting();
}
function createStarField() {
const starsGeometry = new THREE.BufferGeometry();
const starsMaterial = new THREE.PointsMaterial({
color: 0x888888,
size: 0.5,
transparent: true,
opacity: 0.3
});
const starsVertices = [];
for (let i = 0; i < 1000; i++) {
const x = (Math.random() - 0.5) * 200;
const y = (Math.random() - 0.5) * 200;
const z = (Math.random() - 0.5) * 200;
starsVertices.push(x, y, z);
}
starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3));
const stars = new THREE.Points(starsGeometry, starsMaterial);
scene.add(stars);
}
function setupLighting() {
// Ambient light for overall illumination
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
scene.add(ambientLight);
// Main directional light
const mainLight = new THREE.DirectionalLight(0xffffff, 0.8);
mainLight.position.set(20, 20, 20);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
scene.add(mainLight);
// Fill light
const fillLight = new THREE.DirectionalLight(0x64ffda, 0.3);
fillLight.position.set(-20, -20, -20);
scene.add(fillLight);
// Rim light
const rimLight = new THREE.DirectionalLight(0xff6b6b, 0.2);
rimLight.position.set(0, 20, -20);
scene.add(rimLight);
}
function onPointerMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(spheres);
if (intersects.length > 0) {
renderer.domElement.style.cursor = 'pointer';
} else {
renderer.domElement.style.cursor = 'grab';
}
}
function onClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(spheres);
// Reset previous selection
if (selectedSphere) {
resetSphereAppearance(selectedSphere);
}
const wordInfo = document.getElementById('wordInfo');
if (intersects.length > 0) {
selectedSphere = intersects[0].object;
const data = selectedSphere.userData;
// Highlight selected sphere
highlightSphere(selectedSphere, 'selected');
// Show word info with enhanced styling
wordInfo.innerHTML = `
<strong>${data.word}</strong>
<div class="coord">x: ${data.x.toFixed(3)}</div>
<div class="coord">y: ${data.y.toFixed(3)}</div>
<div class="coord">z: ${data.z.toFixed(3)}</div>
<div style="margin-top: 8px; color: #90a4ae; font-size: 12px;">
Index: ${data.index + 1} / ${MAX_WORDS.toLocaleString()}
</div>
`;
wordInfo.style.display = 'block';
} else {
selectedSphere = null;
wordInfo.style.display = 'none';
}
}
function highlightSphere(sphere, type) {
const material = sphere.material;
if (type === 'selected') {
material.emissive.setHex(0x64ffda);
material.emissiveIntensity = 0.5;
sphere.scale.setScalar(1.5);
// Add pulsing animation
const originalScale = sphere.scale.clone();
function pulse() {
if (sphere === selectedSphere) {
sphere.scale.multiplyScalar(1.02);
if (sphere.scale.x > originalScale.x * 1.1) {
sphere.scale.copy(originalScale);
}
requestAnimationFrame(pulse);
}
}
pulse();
}
}
function resetSphereAppearance(sphere) {
const originalMaterial = originalMaterials.get(sphere);
if (originalMaterial) {
sphere.material.emissive.copy(originalMaterial.emissive);
sphere.material.emissiveIntensity = originalMaterial.emissiveIntensity || 0.1;
}
sphere.scale.setScalar(1);
}
function animateCamera(targetPosition, lookAtPosition) {
// Removed smooth camera movement function
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
// Gentle rotation of the star field
if (scene.children.length > 0) {
const stars = scene.children.find(child => child.type === 'Points');
if (stars) {
stars.rotation.y += 0.0005;
}
}
controls.update();
renderer.render(scene, camera);
}
</script>
</body>
</html>