(function() { const dom = document.getElementById('chart-scatter-3d'); const width = dom.clientWidth || 800; const height = 500; const scene = new THREE.Scene(); scene.background = new THREE.Color(0x1e293b); const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); camera.position.set(15, 12, 20); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); dom.appendChild(renderer.domElement); // 坐标轴辅助 const axesHelper = new THREE.AxesHelper(10); scene.add(axesHelper); // 网格地面 const gridHelper = new THREE.GridHelper(16, 20, 0x334155, 0x1e293b); scene.add(gridHelper); // 光源 scene.add(new THREE.AmbientLight(0xffffff, 0.6)); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(10, 20, 10); scene.add(dirLight); // 散点 const scatterData = [ [3.5, 5.2, 2.1, '#3b82f6'], [-2.8, 3.1, -1.5, '#ef4444'], [1.2, -2.5, 4.3, '#22c55e'], [-4.1, -1.8, -3.2, '#f59e0b'], [5.0, 1.2, -2.8, '#a855f7'], [-0.5, 4.5, 1.8, '#3b82f6'], [2.8, -3.5, -0.5, '#22c55e'], [-3.5, 0.8, 3.0, '#ef4444'] ]; scatterData.forEach(([x, y, z, color]) => { const geometry = new THREE.SphereGeometry(0.25, 16, 16); const material = new THREE.MeshPhongMaterial({ color, emissive: color, emissiveIntensity: 0.3 }); const sphere = new THREE.Mesh(geometry, material); sphere.position.set(x, y, z); scene.add(sphere); }); // 动画循环 function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); // 鼠标旋转交互(简易) let isDragging = false, prevX = 0, prevY = 0; renderer.domElement.addEventListener('mousedown', e => { isDragging = true; prevX = e.clientX; prevY = e.clientY; }); renderer.domElement.addEventListener('mouseup', () => { isDragging = false; }); renderer.domElement.addEventListener('mousemove', e => { if (!isDragging) return; const dx = e.clientX - prevX, dy = e.clientY - prevY; camera.position.x += dx * 0.05; camera.position.y -= dy * 0.05; camera.lookAt(0, 0, 0); prevX = e.clientX; prevY = e.clientY; }); renderer.domElement.addEventListener('wheel', e => { camera.position.z += e.deltaY * 0.02; camera.position.z = Math.max(8, Math.min(40, camera.position.z)); e.preventDefault(); }); window.addEventListener('resize', () => { const w = dom.clientWidth || 800; camera.aspect = w / height; camera.updateProjectionMatrix(); renderer.setSize(w, height); }); })();