Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save zthxxx/268b31396cbd29d8b7d267468efd5b25 to your computer and use it in GitHub Desktop.
Save zthxxx/268b31396cbd29d8b7d267468efd5b25 to your computer and use it in GitHub Desktop.
D3js5.9-Threejs102-WebGL-force-directed-graph Demo
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
#root,
body,
html {
width: 100%;
height: 100%;
}
body {
margin: 0;
}
.render-screen,
canvas {
width: 100%;
height: 100%
}
#states {
position: fixed;
top: 0px;
left: 0px;
cursor: pointer;
opacity: 0.9;
z-index: 10000;
}
</style>
</head>
<div class='render-screen' id='render-screen'>
<canvas id='canvas'></canvas>
</div>
<body>
<script type="x-shader/x-vertex" id="pointVertexShader">
attribute float size;
uniform vec3 color;
uniform float alpha;
uniform float zoom;
varying vec4 vColor;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * zoom;
vColor = vec4(color, alpha);
}
</script>
<script type="x-shader/x-fragment" id="pointFragmentShader">
varying vec4 vColor;
void main() {
vec2 center = vec2(0.5, 0.5);
float r = abs(distance(gl_PointCoord, center));
if (r > 0.5) {
discard;
return;
}
gl_FragColor = vColor;
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.1/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js" charset="utf-8"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js" charset="utf-8"></script>
<script>
const particles = 100;
// D3.js config
const nodes = d3.range(particles).map(d => ({
id: d,
}))
const links = d3.range(particles).map(d => ({
source: ~~(Math.random() * particles),
target: ~~(Math.random() * particles),
}))
const nodeIndex = new Map()
const linksIndex = [];
nodes.map((node, index) => nodeIndex.set(node.id, index))
links.map(({source, target}) => linksIndex.push(nodeIndex.get(source), nodeIndex.get(target)))
const sizes = new Float32Array(particles);
const mouseNode = nodes[particles-1]
function charge(d, i) {
if (d === mouseNode) {
return -500
}
return -sizes[i] * 8
}
function gravity(d, i) {
if (d === mouseNode) {
return 50
}
return sizes[i] * 0.1
}
for(let i = 0; i < particles; i++) sizes[i] = Math.random() * 10 + 3;
const simulation = d3.forceSimulation(nodes)
.alphaTarget(0.3)
.force('link', d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(charge).distanceMax(100))
.force("gravity", d3.forceManyBody().strength(gravity).distanceMin(10))
// .force("center", d3.forceCenter(0, 0))
// ------ Three.js config ----------
const renderScreen = document.getElementById('render-screen')
const canvas = document.getElementById('canvas')
const positions = new Float32Array(particles * 3);
const scene = new THREE.Scene()
const camera = new THREE.OrthographicCamera(renderScreen.clientWidth / - 2, renderScreen.clientWidth / 2, renderScreen.clientHeight / 2, renderScreen.clientHeight / - 2, 1, 10000)
scene.add(camera)
camera.position.z = 1000
function uniforms(opts) {
opts = opts || {}
return {
color: {
type: 'c',
value: new THREE.Color(0x3498db)
},
alpha: { type: 'f', value: 0.9 },
zoom: { type: 'f', value: camera.zoom },
}
}
const pointGeom = new THREE.BufferGeometry();
pointGeom.addAttribute('position', new THREE.BufferAttribute(positions, 3));
pointGeom.addAttribute('size', new THREE.BufferAttribute(sizes, 1));
const pointMaterial = new THREE.ShaderMaterial({
uniforms: uniforms(),
vertexShader: d3.select('#pointVertexShader').node().textContent,
fragmentShader: d3.select('#pointFragmentShader').node().textContent,
transparent: true,
})
// const pointMaterial = new THREE.PointsMaterial( { size: 15, sizeAttenuation: false, color: 0x3498db } );
const points = new THREE.Points(pointGeom, pointMaterial);
const lineGeom = new THREE.BufferGeometry();
lineGeom.addAttribute('position', new THREE.BufferAttribute(positions, 3));
lineGeom.setIndex(new THREE.BufferAttribute(new Uint16Array(linksIndex), 1));
var lineMaterial = new THREE.LineBasicMaterial( { color: 0x14689b } );
const lines = new THREE.LineSegments(lineGeom, lineMaterial);
scene.add(lines)
scene.add(points)
const renderer = new THREE.WebGLRenderer({alpha: true, canvas});
const controls = new THREE.OrbitControls( camera, renderer.domElement );
renderer.setSize(renderScreen.clientWidth, renderScreen.clientHeight)
function updateThreePos() {
for (let i = 0; i < nodes.length; i++) {
positions[i * 3] = nodes[i].x
positions[i * 3 + 1] = nodes[i].y
}
pointGeom.attributes.position.needsUpdate = true;
lineGeom.attributes.position.needsUpdate = true;
}
function updateScreenSize() {
camera.right = renderScreen.clientWidth / 2
camera.left = -camera.right
camera.top = renderScreen.clientHeight / 2
camera.bottom = -camera.top
controls.update()
renderer.setSize(renderScreen.clientWidth, renderScreen.clientHeight)
}
function updateCamera() {
pointMaterial.uniforms.zoom.value = camera.zoom;
}
controls.addEventListener( 'change', updateCamera );
// -------- run -------------
stats = new Stats();
stats.domElement.id = 'states';
renderScreen.appendChild( stats.domElement );
function updateMousePos() {
const pos = d3.mouse(this)
mouseNode.fx = (pos[0] - renderScreen.clientWidth / 2) / camera.zoom;
mouseNode.fy = (renderScreen.clientHeight / 2 - pos[1]) / camera.zoom;
}
function onTicked() {
updateThreePos();
renderer.render(scene, camera);
stats.update();
}
simulation.on('tick', onTicked)
d3.select('#canvas').on('mousemove', updateMousePos)
d3.select(window).on('resize', updateScreenSize)
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment