|
/* setup */ |
|
var width = window.innerWidth; |
|
var height = window.innerHeight; |
|
var center = [width / 2, height / 2] |
|
var heart = createHeart(); |
|
|
|
/* DOM init */ |
|
var svg = |
|
d3.select('body').append('svg') |
|
.attr('width', width) |
|
.attr('height', height) |
|
|
|
var g = svg.append('g') |
|
.attr('transform', 'translate(' + center + ')') |
|
|
|
var g_path = |
|
g.append('g').attr('id', 'path') |
|
.on('click', toggleNetworkVisibility) |
|
|
|
var g_links = |
|
g.append('g').attr('id', 'links') |
|
.style('display', heart.showNetwork ? null : 'none') |
|
|
|
var g_nodes = |
|
g.append('g').attr('id', 'nodes') |
|
.style('display', heart.showNetwork ? null : 'none') |
|
|
|
var node = g_nodes.selectAll('circle').data(heart.nodes) |
|
node.exit().remove() |
|
node = node.enter().append('circle') |
|
.attr('r', 5) |
|
.merge(node) |
|
|
|
var link = g_links.selectAll('line').data(heart.links) |
|
link.exit().remove() |
|
link = link.enter().append('line').merge(link) |
|
|
|
var path = g_path.append('path').data([heart.nodes]) |
|
|
|
/* simulation */ |
|
d3.forceSimulation(heart.nodes) |
|
.drag(0.2) |
|
.alphaDecay(0.05) |
|
.force('charge', d3.forceManyBody().strength(-50)) |
|
.force('link', d3.forceLink(heart.links).distance(0).strength(0.1)) |
|
.force('X', d3.forceX().x(function(d) { return d._x })) |
|
.force('Y', d3.forceY().y(function(d) { return d._y })) |
|
.on('tick', update) |
|
.on('end', pulse); |
|
|
|
function update() { |
|
node |
|
.attr('cx', function(d) { return d.x }) |
|
.attr('cy', function(d) { return d.y }) |
|
|
|
link |
|
.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 }) |
|
|
|
path.attr('d', heart.line) |
|
} |
|
|
|
function pulse() { |
|
contract.apply(this) |
|
|
|
function contract() { |
|
this |
|
.alphaMin(0.5) |
|
.drag(0.1) |
|
.force('X', d3.forceX().x(function(d) { return 0.8 * d._x })) |
|
.force('Y', d3.forceY().y(function(d) { return 0.8 * d._y })) |
|
.restart() |
|
.on('end', expand); |
|
} |
|
|
|
function expand() { |
|
this |
|
.alphaMin(0.005) |
|
.drag(0.2) |
|
.force('X', d3.forceX().x(function(d) { return d._x })) |
|
.force('Y', d3.forceY().y(function(d) { return d._y })) |
|
.restart() |
|
.on('end', contract); |
|
} |
|
} |
|
|
|
function createHeart() { |
|
var π = Math.PI |
|
var R = 0.4 * Math.min(width, height) / 2 |
|
var h = 2 * R |
|
var N = 10 |
|
var alpha = Math.acos(R / h) |
|
var startAngle = -5 * π / 6 |
|
var endAngle = π / 2 - alpha |
|
var dAngle = (endAngle - startAngle) / N |
|
|
|
var lobe = d3.range(N).map(function(i) { |
|
return { |
|
x: R * Math.cos(π / 6) + R * Math.cos(startAngle + i * dAngle), |
|
y: -R * Math.sin(π / 6) + R * Math.sin(startAngle + i * dAngle), |
|
} |
|
}) |
|
|
|
var nodes = lobe.map(function(node, index) { |
|
return { |
|
i: index, |
|
x: 0.1 * node.x, |
|
y: 0.1 * node.y, |
|
_x: node.x, |
|
_y: node.y |
|
} |
|
}) |
|
nodes.push({ |
|
i: N, |
|
x: 0, |
|
y: 0.1 * h, |
|
_x: 0, |
|
_y: h |
|
}); |
|
nodes = nodes.concat( |
|
lobe.slice(1) |
|
.reverse() |
|
.map(function(node, index) { |
|
return { |
|
i: N + 1 + index, |
|
x: -0.1 * node.x, |
|
y: 0.1 * node.y, |
|
_x: -node.x, |
|
_y: node.y |
|
} |
|
}) |
|
) |
|
|
|
var line = d3.line() |
|
.x(function(d) { return d.x }) |
|
.y(function(d) { return d.y }) |
|
.curve(d3.curveCatmullRom); |
|
|
|
return { |
|
nodes: nodes, |
|
links: d3.range(nodes.length).map(function(i) { |
|
return { source: i, target: i < nodes.length - 1 ? i + 1 : 0 }; |
|
}), |
|
line: function (d) { |
|
|
|
var rightLobe = d.slice(0, N + 1) |
|
var leftLobe = d.slice(N).concat([d[0]]) |
|
|
|
return [ |
|
line(rightLobe), |
|
line(leftLobe), |
|
'Z' |
|
].join(' ') |
|
}, |
|
showNetwork: false |
|
} |
|
} |
|
|
|
function toggleNetworkVisibility(d) { |
|
heart.showNetwork = !heart.showNetwork |
|
g_links.style('display', heart.showNetwork ? null : 'none') |
|
g_nodes.style('display', heart.showNetwork ? null : 'none') |
|
} |