Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 8, 2017 12:43
Show Gist options
  • Save nitaku/f659c8283946ac885ddd913e7517b0d1 to your computer and use it in GitHub Desktop.
Save nitaku/f659c8283946ac885ddd913e7517b0d1 to your computer and use it in GitHub Desktop.
Collision + foci

This example elaborates on the previous one by adding an attraction force towards arbitrary points, using d3-force-attract by Eric Socolofsky. The plugin, as well as various other useful insights on how to use d3 v4 force layout, is described in this post on hi.stamen.

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height
vis = svg.append 'g'
.attrs
transform: "translate(#{width/2},#{height/2})"
MIN_PADDING = 3
data = [
{r: 45, foc_x: -60, foc_y: -130}
{r: 5, foc_x: -60, foc_y: -90}
{r: 10, foc_x: -70, foc_y: -93}
{r: 25, foc_x: 60, foc_y: 110}
{r: 20, foc_x: 80, foc_y: 100}
{r: 30, foc_x: 100, foc_y: 100}
{r: 30, foc_x: -110, foc_y: 103}
{r: 50, foc_x: -90, foc_y: 60}
]
# each node starts from its focus point
data.forEach (d) ->
d.x = d.foc_x
d.y = d.foc_y
# Layout
simulation = d3.forceSimulation()
.force 'collision', d3.forceCollide (d) -> d.r + MIN_PADDING
.force 'attract', d3.forceAttract().target (d) -> [d.foc_x, d.foc_y]
nodes = vis.selectAll '.node'
.data data
en_nodes = nodes.enter().append 'circle'
.attrs
class: 'node'
r: (d) -> d.r
# Simulation
simulation
.nodes(data)
.on 'tick', () ->
en_nodes.attrs
transform: (d) -> "translate(#{d.x}, #{d.y})"
body, html {
padding: 0;
margin: 0;
height: 100%;
}
svg {
width: 100%;
height: 100%;
background: white;
}
.node {
fill: orange;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Collision + foci</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script src="https://unpkg.com/d3-force-attract@latest"></script>
</head>
<body>
<svg></svg>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
var MIN_PADDING, data, en_nodes, height, nodes, simulation, svg, vis, width;
svg = d3.select('svg');
width = svg.node().getBoundingClientRect().width;
height = svg.node().getBoundingClientRect().height;
vis = svg.append('g').attrs({
transform: "translate(" + (width / 2) + "," + (height / 2) + ")"
});
MIN_PADDING = 3;
data = [
{
r: 45,
foc_x: -60,
foc_y: -130
}, {
r: 5,
foc_x: -60,
foc_y: -90
}, {
r: 10,
foc_x: -70,
foc_y: -93
}, {
r: 25,
foc_x: 60,
foc_y: 110
}, {
r: 20,
foc_x: 80,
foc_y: 100
}, {
r: 30,
foc_x: 100,
foc_y: 100
}, {
r: 30,
foc_x: -110,
foc_y: 103
}, {
r: 50,
foc_x: -90,
foc_y: 60
}
];
data.forEach(function(d) {
d.x = d.foc_x;
return d.y = d.foc_y;
});
simulation = d3.forceSimulation().force('collision', d3.forceCollide(function(d) {
return d.r + MIN_PADDING;
})).force('attract', d3.forceAttract().target(function(d) {
return [d.foc_x, d.foc_y];
}));
nodes = vis.selectAll('.node').data(data);
en_nodes = nodes.enter().append('circle').attrs({
"class": 'node',
r: function(d) {
return d.r;
}
});
simulation.nodes(data).on('tick', function() {
return en_nodes.attrs({
transform: function(d) {
return "translate(" + d.x + ", " + d.y + ")";
}
});
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment