# Creating + viewing a html visualisation

I’m hoping to display in Juno’s plot panel a `three.js` widget consisting of a few particles in space, that the user could manipulate in 3D to get a sense of the geometry.

I have a working example and can produce the relevant particle definitions with the wrapper below, but I don’t know how to go about integrating this with, say, WebIO, to display as a standalone widget in Juno or in Documenter pages. Is there a simple example out there I could use as a starting point? Most examples I’ve found are much more complicated or don’t run anymore.

Code I’m using to generate the particles:

``````
using StaticArrays

struct Cluster{T1,T2,T3}
positions::Vector{SVector{3,T1}}
angles::Vector{SVector{3,T2}}
sizes::Vector{SVector{3,T3}}
end

function cluster_helix(N, a, b, c, R, Λ, δ = π/4, δ_0 = 0, handedness="left")

sizes = [SVector(a, b, c) for ii in 1:N]

s = handedness == "left" ? -1 : +1
ϕ = collect(1:N) * δ .+ δ_0
x = R * cos.(ϕ)
y = R * sin.(ϕ)
z = s * ϕ * Λ/(2π)
z .-= s*maximum(s * z)/2

# angles calculation
x′ =  - y
y′ =   x
z′ =  s * Λ / (2π)
n = @. sqrt(x′^2 + y′^2 + z′^2)

φ =  atan.(y′, x′)
θ =  acos.(z′ ./ n)
ψ = 0.0 # don't care for axisymmetric particles

positions = SVector.(x,y,z)
angles = SVector.(φ,θ,ψ)

Cluster(positions, angles, sizes)
end

function visualise(cl)

s = []

for i ∈ 1:length(cl.positions)
x = cl.positions[i][1]
y = cl.positions[i][2]
z = cl.positions[i][3]

a = cl.sizes[i][1]
b = cl.sizes[i][2]
c = cl.sizes[i][3]

α = pi/2+cl.angles[i][1]
β = cl.angles[i][2]
γ = cl.angles[i][3]
push!(s,
sphere\$i.position.x = \$x;
sphere\$i.position.y = \$y;
sphere\$i.position.z = \$z;
sphere\$i.scale.set(\$a,\$b,\$c);
sphere\$i.rotation.z = \$α;
sphere\$i.rotation.x = \$β;
sphere\$i.rotation.y = \$γ;
sphere\$i.rotation.order = 'ZXY';
)
end
s
end

cl = cluster_helix(10, 10, 10, 20, 50, 200)

p = visualise(cl)

io = open("particles.txt", "w");
close(io);

``````

The full self-contained example would go something like this,

``````<!doctype html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<style>
body { margin: 0; overflow: hidden; background-color: #fff; }
.tm  { position: absolute; top: 10px; right: 10px; }
.webgl-error { font: 15px/30px monospace; text-align: center; color: #fff; margin: 50px; }
.webgl-error a { color: #fff; }
</style>
<body>
<div id="webgl"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://unpkg.com/three@0.85.0/examples/js/controls/TrackballControls.js"></script>
<script src="https://unpkg.com/three@0.85.0/examples/js/Detector.js"></script>

<script>

(function () {

var webglEl = document.getElementById('webgl');

if (!Detector.webgl) {
return;
}

var width  = 600,
height = 600;

// sphere params
segments = 36,
rotation = 6;

var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xeeeeee );

var camera = new THREE.PerspectiveCamera(100, width / height, 0.01, 1000);

camera.position.z = 200;

var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);

const frontSpot = new THREE.SpotLight(0xeeeece);
frontSpot.position.set(1000, 1000, 1000);

const frontSpot2 = new THREE.SpotLight(0xddddce);
frontSpot2.position.set(-500, -500, -500);

var xDistance = 2;
var yDistance = 2;
//initial offset so does not start in middle.
var xOffset = 0;
var yOffset = 1;
sphere1.position.x = 35.35533905932738;
sphere1.position.y = 35.35533905932737;
sphere1.position.z = 99.99999999999999;
sphere1.scale.set(10,10,20);
sphere1.rotation.z = 3.9269908169872414;
sphere1.rotation.x = 2.137707831735906;
sphere1.rotation.y = 0.0;
sphere1.rotation.order = 'ZXY';
sphere2.position.x = 3.061616997868383e-15;
sphere2.position.y = 50.0;
sphere2.position.z = 74.99999999999999;
sphere2.scale.set(10,10,20);
sphere2.rotation.z = 4.71238898038469;
sphere2.rotation.x = 2.137707831735906;
sphere2.rotation.y = 0.0;
sphere2.rotation.order = 'ZXY';
sphere3.position.x = -35.35533905932737;
sphere3.position.y = 35.35533905932738;
sphere3.position.z = 49.999999999999986;
sphere3.scale.set(10,10,20);
sphere3.rotation.z = -0.7853981633974483;
sphere3.rotation.x = 2.137707831735906;
sphere3.rotation.y = 0.0;
sphere3.rotation.order = 'ZXY';
sphere4.position.x = -50.0;
sphere4.position.y = 6.123233995736766e-15;
sphere4.position.z = 24.999999999999986;
sphere4.scale.set(10,10,20);
sphere4.rotation.z = -2.220446049250313e-16;
sphere4.rotation.x = 2.137707831735906;
sphere4.rotation.y = 0.0;
sphere4.rotation.order = 'ZXY';
sphere5.position.x = -35.355339059327385;
sphere5.position.y = -35.35533905932737;
sphere5.position.z = 0.0;
sphere5.scale.set(10,10,20);
sphere5.rotation.z = 0.7853981633974481;
sphere5.rotation.x = 2.137707831735906;
sphere5.rotation.y = 0.0;
sphere5.rotation.order = 'ZXY';
sphere6.position.x = -9.184850993605149e-15;
sphere6.position.y = -50.0;
sphere6.position.z = -25.000000000000014;
sphere6.scale.set(10,10,20);
sphere6.rotation.z = 1.5707963267948963;
sphere6.rotation.x = 2.137707831735906;
sphere6.rotation.y = 0.0;
sphere6.rotation.order = 'ZXY';
sphere7.position.x = 35.35533905932737;
sphere7.position.y = -35.355339059327385;
sphere7.position.z = -49.999999999999986;
sphere7.scale.set(10,10,20);
sphere7.rotation.z = 2.356194490192345;
sphere7.rotation.x = 2.137707831735906;
sphere7.rotation.y = 0.0;
sphere7.rotation.order = 'ZXY';
sphere8.position.x = 50.0;
sphere8.position.y = -1.2246467991473532e-14;
sphere8.position.z = -75.00000000000001;
sphere8.scale.set(10,10,20);
sphere8.rotation.z = 3.141592653589793;
sphere8.rotation.x = 2.137707831735906;
sphere8.rotation.y = 0.0;
sphere8.rotation.order = 'ZXY';
sphere9.position.x = 35.355339059327385;
sphere9.position.y = 35.35533905932737;
sphere9.position.z = -100.00000000000001;
sphere9.scale.set(10,10,20);
sphere9.rotation.z = 3.9269908169872414;
sphere9.rotation.x = 2.137707831735906;
sphere9.rotation.y = 0.0;
sphere9.rotation.order = 'ZXY';
sphere10.position.x = 1.5308084989341916e-14;
sphere10.position.y = 50.0;
sphere10.position.z = -124.99999999999999;
sphere10.scale.set(10,10,20);
sphere10.rotation.z = 4.71238898038469;
sphere10.rotation.x = 2.137707831735906;
sphere10.rotation.y = 0.0;
sphere10.rotation.order = 'ZXY';

//var axesHelper = new THREE.AxesHelper( 5 );

scene.add( new THREE.AxesHelper( 1 ) );
var controls = new THREE.TrackballControls(camera);

webglEl.appendChild(renderer.domElement);

render();

function render() {
controls.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
}

const material = new THREE.MeshStandardMaterial({
color: 0xfcc742,
emissive: 0x111111,
specular: 0xffffff,
metalness: 0.8,
roughness: 0.6,
});
return new THREE.Mesh(