Another attempt at better bounding box collision for d3 force.
forked from emeeks's block: More bounding box collide
xxxxxxxxxx
<html>
<head>
<title>d3v4 Simple Word Cloud</title>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3-force.js"></script>
</head>
<style>
svg {
height: 768px;
width: 768px;
border: 1px solid gray;
}
</style>
<body>
<div id="viz">
<svg class="main">
<defs>
<linearGradient id="green">
<stop offset="0%" stop-color="#92E6CB"/>
<stop offset="100%" stop-color="#61D3A5"/>
</linearGradient>
<linearGradient id="yellow">
<stop offset="0%" stop-color="#FFB140"/>
<stop offset="100%" stop-color="#E19930"/>
</linearGradient>
<linearGradient id="red">
<stop offset="0%" stop-color="#EE6767"/>
<stop offset="100%" stop-color="#D14C4C"/>
</linearGradient>
<linearGradient id="gray">
<stop offset="0%" stop-color="#8D98A2"/>
<stop offset="100%" stop-color="#76818C"/>
</linearGradient>
<marker id="green-end" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,-5L10,0L0,5" fill="#8BE3C5"></path>
</marker>
<marker id="yellow-end" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,-5L10,0L0,5" fill="#FFB140"></path>
</marker>
<marker id="red-end" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,-5L10,0L0,5" fill="#E55B5A"></path>
</marker>
<marker id="green-start" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,-5L10,0L0,5" fill="#8BE3C5"></path>
</marker>
<filter id="dropshadow" filterUnits="userSpaceOnUse">
<feGaussianBlur in="SourceAlpha" stdDeviation="20">
</feGaussianBlur>
<feOffset dx="0" dy="20"></feOffset>
<feComponentTransfer>
<feFuncA type="linear" slope="0.3"></feFuncA>
</feComponentTransfer>
<feMerge>
<feMergeNode></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
</svg>
</div>
</body>
<footer>
<script>
MARKER_GREEN = '#8BE3C5';
MARKER_YELLOW = '#FFB140'
MARKER_RED = '#E55B5A'
function startMarkerColor(idx) {
switch (idx % 4) {
case 1: return 'url(#green-start)'
case 2: return null
case 3: return null
default: return null
}
}
function endMarkerColor(idx) {
switch (idx % 4) {
case 1: return null
case 2: return 'url(#yellow-end)'
case 3: return 'url(#red-end)'
default: return null
}
}
function lineColor(idx) {
switch (idx % 4) {
case 1: return MARKER_GREEN
case 2: return MARKER_YELLOW
case 3: return MARKER_RED
default: return null
}
}
function nodeColor(node, idx) {
// console.log('node=', node)
switch (idx % 4) {
case 1: return 'url(#green)'
case 2: return 'url(#yellow)'
case 3: return 'url(#red)'
default: return 'url(#gray)'
}
}
const HEIGHT = 768;
const WIDTH = 768;
const STRONG = 390;
const USER_CENTER_X = WIDTH / 2
const USER_CENTER_Y = 10 + STRONG / 2
const GREEN = '#61D3A5';
const GRAY = '#8D98A2';
const FONT = 'Verdana';
const USER_RADIUS = 40;
const svg = d3.select("svg.main")
const colors = d3.scaleOrdinal(d3.schemeCategory20)
const total = 20
const nodes = d3.range(total).map(i => {
let n = { index: i, r: 30, x: WIDTH / 2, y: HEIGHT, name: `N${i}` }
if (i == 0) n.r = USER_RADIUS
return n
});
const links = d3.range(total).map(i => {
let n
if ((i % 4) == 1) {
n = { source: i, target: 0, value: i }
} else {
n = { source: 0, target: i, value: i }
}
return n
});
// scales
const vScale = d3.scaleLinear()
.domain([0, total])
.range([USER_RADIUS, WIDTH / 2])
// svg
const outerG = svg.append('g')
.attr('transform',`translate(${WIDTH/2},${HEIGHT/2})`)
const outerCirle = outerG.append('circle')
.attr('x', WIDTH / 2)
.attr('y', HEIGHT / 2)
.attr('r', WIDTH / 2)
.attr('fill', 'none')
.attr('stroke', '#8D98A2')
.attr('stroke-opacity', '0.2')
const outerText = outerG.append('text')
.text('Weak')
.attr('text-anchor', 'middle')
.attr('y', HEIGHT / 6)
.attr('fill', GRAY)
.attr('style', `font-family: ${FONT}; font-size: 16px;`)
//
const innerG = svg.append('g')
.attr('transform', `translate(${USER_CENTER_X},${USER_CENTER_Y})`)
const innerCircle = innerG.append('circle')
.attr('x', WIDTH / 2)
.attr('y', HEIGHT / 2)
.attr('r', STRONG / 2)
.attr('fill', GREEN)
.attr('fill-opacity', '0.1')
.attr('stroke', GREEN)
.attr('stroke-opacity', '0.8')
const innerText = innerG.append('text')
.text('Strong')
.attr('text-anchor', 'middle')
.attr('y', 110)
.attr('fill', GREEN)
.attr('style', `font-family: ${FONT}; font-size: 16px;`)
//
const centerForce = d3.forceCenter(WIDTH / 2, HEIGHT / 2)
const forceX = d3.forceX(USER_CENTER_X).strength(0.005)
const forceY = d3.forceY(USER_CENTER_Y).strength(0.005)
const collideForce = d3.forceCollide().radius(70).strength(0.2).iterations(100)
const bodyForce = d3.forceManyBody().strength(-0.2)
const linkForce = d3.forceLink().strength(0.3).iterations(100).distance(l => vScale(l.value))
const simulation = d3.forceSimulation()
.velocityDecay(0.9)
.force('charge', bodyForce)
.force('collide', collideForce)
.force('center', centerForce)
.force('links', linkForce)
.force('forceX', forceX)
.force('forceY', forceY)
const nodeCirles = svg.append("g")
.attr("class", "nodes")
.selectAll('circle')
.filter('.friend')
.data(nodes)
.enter().append('circle')
.attr('r', d => d.r)
.attr('class', 'friend')
.attr('fill', (d, i) => nodeColor(d, i))
.attr('style', 'filter: url("#dropshadow")')
const nodeLinks = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("marker-start", (d, i) => startMarkerColor(i))
.attr("marker-end", (d, i) => endMarkerColor(i))
.attr("stroke-width", d => 2)
// .style("stroke", (d, i) => lineColor(i))
const nodeLabels = svg.selectAll("text")
.filter('.node-label')
.data(nodes)
.enter()
.append("text")
.attr('class', 'node-label')
.text(function (d) { return d.name; })
.attr('fill', '#fff')
.attr('style', `font-family: ${FONT}; font-size: 16px;`)
simulation
.nodes(nodes)
.on('tick', ticked)
simulation.force("links").links(links)
// let mutex = true
function ticked (e) {
nodeLinks
.attr("x1", function(d) { return d.source.x })
.attr("y1", function(d) { return d.source.y })
.attr("x2", function(d) { return d.target.x })
.attr("y2", function(d) { return d.target.y });
nodeCirles
.attr('cx', d => {
// if (d.index == 1 && mutex) {
// console.log(d)
// mutex = false;
// }
return d.x
})
.attr('cy', d => d.y)
nodeLabels
.attr("x", function(d){ return d.x })
.attr("y", function (d) {return d.y + 5.5; })
.style("text-anchor", "middle")
nodes[0].x = USER_CENTER_X
nodes[0].y = USER_CENTER_Y
}
</script>
</footer>
</html>
https://d3js.org/d3.v4.min.js