anycoder-2e621246 / index.html
chos443's picture
Upload folder using huggingface_hub
96121f0 verified
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Cube Data Visualization</title>
<style>
/*
* MODERN CSS RESET & BASE STYLES
*/
:root {
--bg-color: #eeeeee;
--text-color: #000000;
--accent-color: #3b82f6;
--ui-bg: rgba(255, 255, 255, 0.9);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none; /* Prevent text selection during interaction */
-webkit-user-drag: none;
}
body {
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
overflow: hidden; /* Prevent scrollbars */
width: 100vw;
height: 100vh;
}
/*
* 3D CANVAS CONTAINER
*/
#canvas-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
outline: none;
}
/*
* UI OVERLAY LAYER
*/
#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
pointer-events: none; /* Let clicks pass through to canvas */
display: flex;
flex-direction: column;
justify-content: space-between;
}
/*
* HEADER
*/
header {
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
pointer-events: auto;
background: linear-gradient(to bottom, rgba(238,238,238,0.8), transparent);
}
h1 {
font-size: 1.2rem;
font-weight: 600;
letter-spacing: -0.5px;
opacity: 0.8;
}
.built-with {
font-size: 0.8rem;
color: #555;
text-decoration: none;
transition: color 0.3s ease;
}
.built-with:hover {
color: var(--accent-color);
font-weight: bold;
}
/*
* INFO PANEL (Bottom Left)
*/
.info-panel {
padding: 20px;
pointer-events: auto;
background: var(--ui-bg);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
max-width: 300px;
backdrop-filter: blur(10px);
transform: translateY(20px);
opacity: 0;
animation: slideUp 0.8s ease-out forwards 0.5s;
}
@keyframes slideUp {
to {
transform: translateY(0);
opacity: 1;
}
}
.info-panel h2 {
font-size: 1rem;
margin-bottom: 8px;
}
.info-panel p {
font-size: 0.85rem;
line-height: 1.5;
color: #444;
}
/*
* LOADING OVERLAY
*/
#loader {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-color);
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: opacity 0.5s ease;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #ccc;
border-top: 4px solid var(--text-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/*
* ANNOTATION LABELS (CSS 2D Renderer)
*/
.label {
color: #000;
font-family: sans-serif;
font-size: 12px;
font-weight: 500;
background: rgba(255, 255, 255, 0.8);
padding: 2px 6px;
border-radius: 4px;
pointer-events: none; /* Let clicks pass through */
white-space: nowrap;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
opacity: 0.7;
transition: opacity 0.2s;
}
.label.hidden {
opacity: 0;
}
</style>
<!-- Import Three.js and Addons via ES Modules -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
</head>
<body>
<!-- Loading Screen -->
<div id="loader">
<div class="spinner"></div>
<p>데이터를 로딩 중입니다...</p>
</div>
<!-- 3D Scene Container -->
<div id="canvas-container"></div>
<!-- UI Overlay -->
<div id="ui-layer">
<header>
<h1>3D Cube Data Points</h1>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a>
</header>
<div class="info-panel">
<h2>인터랙션 가이드</h2>
<p><strong>회전:</strong> 마우스 드래그</p>
<p><strong>줌:</strong> 마우스 휠</p>
<p><strong>줌인:</strong> 포인트 클릭</p>
</div>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
// --- Configuration ---
const CONFIG = {
pointCount: 100,
pointColor: 0x000000,
lineColor: 0x000000,
bgColor: 0xeeeeee,
cubeSize: 10,
cameraZ: 25
};
// --- Data Generation (100 Unique Animal Names) ---
const animalNames = [
"Tiger", "Lion", "Bear", "Wolf", "Fox", "Rabbit", "Deer", "Horse",
"Eagle", "Shark", "Whale", "Dolphin", "Panda", "Koala", "Kangaroo",
"Penguin", "Owl", "Falcon", "Eagle", "Crab", "Turtle", "Snake", "Lizard",
"Frog", "Toad", "Cat", "Dog", "Bird", "Fish", "Shrimp", "Cricket",
"Ant", "Bee", "Butterfly", "Dragonfly", "Spider", "Scorpion", "Centipede",
"Mosquito", "Beetle", "Wasp", "Hornet", "Fly", "Moth", "Ladybug",
"Bee", "Honey", "Hive", "Nest", "Honey", "Bee", "Honey", "Bee"
];
// Generate unique pairs if needed, or just pick random ones
const uniqueNames = Array.from(new Set(animalNames)).slice(0, CONFIG.pointCount);
// --- Scene Setup ---
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
scene.background = new THREE.Color(CONFIG.bgColor);
scene.fog = new THREE.Fog(CONFIG.bgColor, 20, 50);
// --- Camera ---
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, CONFIG.cameraZ);
// --- Renderers ---
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.pointerEvents = 'none'; // Crucial for clicks
container.appendChild(labelRenderer.domElement);
// --- Controls ---
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enablePan = false;
controls.minDistance = 2; // Don't get too close
controls.maxDistance = 50;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;
// --- Lighting ---
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(10, 20, 10);
dirLight.castShadow = true;
scene.add(dirLight);
// --- Objects ---
// 1. The Cube (Wireframe)
const geometry = new THREE.BoxGeometry(CONFIG.cubeSize, CONFIG.cubeSize, CONFIG.cubeSize);
const edges = new THREE.EdgesGeometry(geometry);
const lineMaterial = new THREE.LineBasicMaterial({ color: CONFIG.lineColor, linewidth: 2 });
const wireframeCube = new THREE.LineSegments(edges, lineMaterial);
scene.add(wireframeCube);
// 2. The Points and Labels
const pointsGroup = new THREE.Group();
scene.add(pointsGroup);
const pointGeometry = new THREE.SphereGeometry(0.15, 16, 16);
const pointMaterial = new THREE.MeshBasicMaterial({ color: CONFIG.pointColor });
// Helper to calculate position on surface
// We use a simple rejection sampling to place points on the faces of the cube
function getRandomPointOnCube() {
const halfSize = CONFIG.cubeSize / 2;
let x, y, z;
// Randomly choose a face (0: +x, 1: -x, 2: +y, 3: -y, 4: +z, 5: -z)
const face = Math.floor(Math.random() * 6);
switch(face) {
case 0: x = halfSize; y = (Math.random() - 0.5) * CONFIG.cubeSize; z = (Math.random() - 0.5) * CONFIG.cubeSize; break;
case 1: x = -halfSize; y = (Math.random() - 0.5) * CONFIG.cubeSize; z = (Math.random() - 0.5) * CONFIG.cubeSize; break;
case 2: x = (Math.random() - 0.5) * CONFIG.cubeSize; y = halfSize; z = (Math.random() - 0.5) * CONFIG.cubeSize; break;
case 3: x = (Math.random() - 0.5) * CONFIG.cubeSize; y = -halfSize; z = (Math.random() - 0.5) * CONFIG.cubeSize; break;
case 4: x = (Math.random() - 0.5) * CONFIG.cubeSize; y = (Math.random() - 0.5) * CONFIG.cubeSize; z = halfSize; break;
case 5: x = (Math.random() - 0.5) * CONFIG.cubeSize; y = (Math.random() - 0.5) * CONFIG.cubeSize; z = -halfSize; break;
}
return new THREE.Vector3(x, y, z);
}
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const clickablePoints = []; // Store meshes for raycasting
uniqueNames.forEach((name, index) => {
// Create 3D Point
const pos = getRandomPointOnCube();
const pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
pointMesh.position.copy(pos);
pointMesh.userData = { id: index, name: name }; // Store data for interaction
pointsGroup.add(pointMesh);
clickablePoints.push(pointMesh);
// Create HTML Label
const div = document.createElement('div');
div.className = 'label';
div.textContent = name;
const label = new CSS2DObject(div);
label.position.set(0, 0.3, 0); // Offset slightly above point
pointMesh.add(label);
});
// --- Interaction Logic ---
function onMouseClick(event) {
// Calculate mouse position in normalized device coordinates
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
// Intersect with our clickable points
const intersects = raycaster.intersectObjects(clickablePoints);
if (intersects.length > 0) {
const target = intersects[0].object;
const targetPos = target.position.clone();
// Calculate distance to target
const distance = camera.position.distanceTo(targetPos);
// Desired distance (zoom in)
// We want to be closer to the surface of the cube, minus the radius of the point
const targetDistance = CONFIG.cubeSize / 2 + 2;
// Animate Camera Position (Simple Linear Interpolation via TWEEN logic or manual loop)
// Here we use a simple GSAP-like manual tween for zero-dependency
const startPos = camera.position.clone();
const endPos = targetPos.clone().normalize().multiplyScalar(targetDistance);
let alpha = 0;
const duration = 1000; // ms
const startTime = performance.now();
function animateZoom(currentTime) {
const elapsed = currentTime - startTime;
alpha = Math.min(elapsed / duration, 1);
// Ease out cubic
const ease = 1 - Math.pow(1 - alpha, 3);
camera.position.lerpVectors(startPos, endPos, ease);
controls.target.lerp(targetPos, ease * 0.5); // Look at target
if (alpha < 1) {
requestAnimationFrame(animateZoom);
} else {
// Stop auto-rotation when zoomed in for better UX
controls.autoRotate = false;
}
}
requestAnimationFrame(animateZoom);
}
}
// --- Event Listeners ---
window.addEventListener('resize', onWindowResize);
window.addEventListener('click', onMouseClick);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
}
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
// --- Init ---
// Hide loader when scene is ready
setTimeout(() => {
document.getElementById('loader').style.opacity = '0';
setTimeout(() => {
document.getElementById('loader').style.display = 'none';
}, 500);
animate();
}, 1000);
</script>
</body>
</html>