I am developing a Dash app with a ThreeJS component embedded. This is not the final project, but an example. I dont know to fix this, since I have tried a lot of things and ChatGPT, Gemini and Claude dont have an enswer either.
import dash
from dash import html, dcc, Input, Output, clientside_callback
import dash_bootstrap_components as dbc
import json
import numpy as np
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.index_string = '''
<!DOCTYPE html>
<html>
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js"></script>
</head>
<body>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>
'''
app.layout = dbc.Container([
html.H1("Interactive 3D Scatter Plot with Three.js"),
dbc.Button("Plot 3D Scene", id="plot-button", color="primary", className="mb-3"),
html.Div(id="threejs-container", style={"width": "100%", "height": "600px"}),
html.Div(id="error-log", style={"color": "red"}),
dcc.Store(id="point-data"),
html.Div(id="scene_initiation"),
])
@app.callback(
Output("point-data", "data"),
Input("plot-button", "n_clicks")
)
def generate_points(n_clicks):
if n_clicks is None:
return dash.no_update
num_points = 30000
points = np.random.randn(num_points, 3) * 50
return json.dumps(points.tolist())
app.clientside_callback(
"""
function(pointData) {
const container = document.getElementById('threejs-container');
const errorLog = document.getElementById('error-log');
let scene, camera, renderer, pointCloud, controls;
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setSize(container.clientWidth, container.clientHeight);
container.innerHTML = '';
container.appendChild(renderer.domElement);
camera.position.z = 100;
// Add OrbitControls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.maxDistance = 500;
controls.minDistance = 10;
animate();
}
function plotPoints(points) {
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array(points.flat());
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
const material = new THREE.PointsMaterial({color: 0x00ff00, size: 0.5});
pointCloud = new THREE.Points(geometry, material);
scene.add(pointCloud);
}
function animate() {
requestAnimationFrame(animate);
if (renderer && scene && camera) {
controls.update(); // Update controls in the animation loop
renderer.render(scene, camera);
}
}
function onWindowResize() {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
initScene();
if (pointData) {
const points = JSON.parse(pointData);
plotPoints(points);
}
window.addEventListener('resize', onWindowResize, false);
return '';
}
""",
Output("threejs-container", "children"),
Input("point-data", "data"),
Input("scene_initiation", "children")
)
if __name__ == '__main__':
app.run_server(debug=True)
When I dont use OrbitControls and use
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
instead of
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js"></script>
It works, but the 3d plot is not interactive. What could i do?