Skip to content

Instantly share code, notes, and snippets.

@makyo
Last active June 27, 2024 15:31
Show Gist options
  • Save makyo/6754267 to your computer and use it in GitHub Desktop.
Save makyo/6754267 to your computer and use it in GitHub Desktop.
Mapping poly relationships with force directed layouts

I freely admit that this is outrageously geeky. A conversation with folks on twitter led to attempts to try and describe, visually, polyamorous relationships. We kept finding ourselves annotating the links between us, notably with each end of the link, and adding other types of relationships, not all formal or even romantic. This is simply the standard FDL example, with info added about relationship meaning when you hover over the relationship link itself.

{
"nodes":
[
{"name": "Makyo"},
{"name": "JD"},
{"name": "Lexy"},
{"name": "Forneus"},
{"name": "Des"},
{"name": "Peri"},
{"name": "The Cloud™",
"attrs": {"x": -20, "y": -20, "r": 20, "fill": "#eee",
"stroke": "#000", "stroke-width": "1px",
"stroke-dasharray": "5,5"}},
{"name": "Cullen"},
{"name": "Osric"},
{"name": "Bel"},
{"name": "Sean"},
{"name": "Kader"}
],
"links":
[
{"source": 0, "target": 1, "value": 1, "targetMeaning": "husbandog",
"sourceMeaning": "wifox", "linkMeaning": "partners, civil union"},
{"source": 0, "target": 3, "value": 2, "targetMeaning": "catfriend",
"sourceMeaning": "foxfriend", "linkMeaning": "partners"},
{"source": 3, "target": 4, "value": 1, "targetMeaning": "catfriend",
"sourceMeaning": "catfriend", "linkMeaning": "partners"},
{"source": 0, "target": 2, "value": 2, "targetMeaning": "pet",
"sourceMeaning": "owner", "linkMeaning": "puppydog and her howner"},
{"source": 0, "target": 7, "value": 3, "dashed": true,
"linkMeaning": "a little too far apart and a little too alike"},
{"source": 0, "target": 5, "value": 4, "dashed": true},
{"source": 0, "target": 9, "value": 4, "dashed": true,
"linkMeaning": "another universe, maybe"},
{"source": 1, "target": 8, "value": 4, "dashed": true,
"linkMeaning": "another universe, maybe"},
{"source": 8, "target": 9, "value": 1, "sourceMeaning": "husband",
"targetMeaning": "husband", "linkMeaning": "married"},
{"source": 0, "target": 6, "value": 4, "dashed": true,
"targetMeaning": "squishees", "sourceMeaning": "squisher"},
{"source": 4, "target": 10, "value": 2, "linkMeaning": "partner"},
{"source": 10, "target": 11, "value": 1, "linkMeaning": "partner"}
]
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link line {
stroke: rgba(0,0,0,0.35);
}
.link .meaning {
fill: #00F;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.hidden {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);
d3.json("graph.json", function(error, json) {
force
.nodes(json.nodes)
.links(json.links)
.distance(function(d) { return Math.log(d.value * 3) * 50; })
.start();
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("g")
.attr("class", "link");
link.append('text')
.attr('class', 'sourceMeaning meaning hidden')
.text(function(d) { return d.sourceMeaning });
link.append('text')
.attr('class', 'targetMeaning meaning hidden')
.text(function(d) { return d.targetMeaning });
link.append('text')
.attr('class', 'linkMeaning meaning hidden')
.text(function(d) { return d.linkMeaning || ''; });
link.append('line')
.attr("stroke-width", function(d) {
return 10 / (d.value / 1);
})
.attr("stroke-dasharray", function(d) {
if (d.dashed) {
return "5,5";
}
})
.on('mouseover', function(d) {
d3.select(this.parentNode).selectAll('.meaning')
.classed('hidden', false);
})
.on('mouseout', function(d) {
d3.select(this.parentNode).selectAll('.meaning')
.classed('hidden', true);
});
var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.each(function(d) {
var attrs = d.attrs ? d.attrs : {
x: -10,
y: -10,
r: 10,
fill: '#888'
};
d3.select(this).attr(attrs);
});
node.append("text")
.attr("dx", function(d) { return d.attrs ? d.attrs.r + 2 : 12; })
.attr("dy", ".35em")
.text(function(d) { return d.name });
force.on("tick", function() {
link.select('line')
.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; });
link.select('.sourceMeaning')
.attr('dx', function(d) { return d.source.x + 12})
.attr('dy', function(d) { return d.source.y + 24});
link.select('.targetMeaning')
.attr('dx', function(d) { return d.target.x + 12})
.attr('dy', function(d) { return d.target.y + 24});
link.select('.linkMeaning')
.attr('dx', function(d) {
return (d.source.x + ((d.target.x - d.source.x) / 2));
})
.attr('dy', function(d) {
return (d.source.y + ((d.target.y - d.source.y) / 2));
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment