Last active
March 4, 2019 08:07
-
-
Save zthxxx/268b31396cbd29d8b7d267468efd5b25 to your computer and use it in GitHub Desktop.
D3js5.9-Threejs102-WebGL-force-directed-graph Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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