Skip to content

Instantly share code, notes, and snippets.

@nacmonad
Last active January 8, 2016 19:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nacmonad/d2ca82e7a36f6362139b to your computer and use it in GitHub Desktop.
Save nacmonad/d2ca82e7a36f6362139b to your computer and use it in GitHub Desktop.
D3 Force Layouts and ForeignObjects

introductory fun with force layouts

check repo and DL for example with the image files... having issues getting github to serve the png's

<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<style>
.control-bar {
padding-top:150px;
}
.svgs {
}
.svgs :hover {
}
.link {
stroke: #999;
stroke-opacity: .6;
}
.nodegroup {
fill-opacity:0.75;
overflow:;
}
.nodegroup :hover {
fill-opacity:0.95;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.innerText {
font-size:12px;
}
.foreign{
opacity:0;
overflow:hidden;
}
.foreign body {
height:106px;
background-color:rgba(255,255,255,0);
border-color:white;
border-style: solid;
border-width:2px;
border-radius:10px;
padding:10px;
overflow:hidden;
}
</style>
<body>
<div class="container">
<div class="row">
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 vis"></div>
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 control-bar">
<p>
<label for="charge"
style="display: inline-block; width: 240px; text-align: right">
Charge = <span id="charge-value">-404</span>
</label>
<input type="range" min="-999" max="999" step="1" value="-404" id="charge">
</p>
<p>
<label for="gravity"
style="display: inline-block; width: 240px; text-align: right">
Gravity = <span id="gravity-value">0.04</span>
</label>
<input type="range" min="0" max="1" step="0.01" value="0.04" id="gravity">
</p>
<button class ="btn btn-default" onclick="getNewNodes()">New Nodes</button>
</div>
</div>
</div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>
var WIDTH = 800;
var HEIGHT = 600;
var c10 = d3.scale.category10();
//var data = genNodes();
var data = [{id: 01,
name: "herb",
amount:"220",
imgURL:"./data/face00.png"},
{id: 02,
name: "derp",
amount:"330",
imgURL:"./data/face01.png"},
{id: 03,
name: "foo",
amount:"490",
imgURL:"./data/face02.png"},
{id: 04,
name: "bar",
amount:"290",
imgURL:"./data/face03.png"},
{id: 05,
name: "blip",
amount:"490",
imgURL:"./data/face04.png"},
{id: 06,
name: "asdflk",
amount:"390",
imgURL:"./data/face05.png"}];
var force = d3.layout.force()
.nodes(data)
.size([WIDTH, HEIGHT])
.charge(-300)
.gravity(0.1)
.alpha(0.1)
.on("tick", tick)
.start();
var vis = d3.select('.vis').append('g');
vis.attr('class','svggroup');
var svgs = vis.append('svg')
.attr('class','svgs')
.attr('height', HEIGHT)
.attr('width', WIDTH);
var groups = svgs.selectAll("g")
.data(force.nodes())
.enter()
.append("g")
.attr("class","nodegroup");
//enter
var node = groups
.append("circle")
.style("fill", function(d,i) { return c10(i);})
.attr("r", function(d) { return Math.sqrt(d.amount)})
.attr("class", "node")
.call(force.drag);
node
.on("mouseover", function(selected) {
//force.stop();
d3.select(this).transition()
.duration(500)
.attr("r", 75);
//bring node to front
d3.selectAll('.nodegroup')
.sort(function(a, b) {
if (a.id === selected.id) {
return 1;
} else {
if (b.id === selected.id) {
return -1;
} else {
return 0;
}
}
});
//force.resume();
//append text to parent group, not the circle
//d3.select(this.parentNode).append("text")
// .attr("class", "innerText")
// .attr("text-anchor", "middle")
// .attr("fill", "black")
// .text(function(d) {
// return d.name;
// })
// .attr("pointer-events","none") // this eliminates the loss of :hover when directly over the text
// .attr("transform", function(d) {
// return 'translate(' + [d.x, d.y] + ')';
// });
//append foreign html to group
d3.select(this.parentNode).append("foreignObject")
.attr("width", 150/Math.sqrt(2))
.attr("height", 150/Math.sqrt(2))
.attr("class","foreign")
.attr("pointer-events","none")
.attr("transform", function(d) {
return 'translate(' + [(d.x-75/Math.sqrt(2)), (d.y-75/Math.sqrt(2))] + ')';
})
.append("xhtml:body")
.style("font", "8px 'Helvetica Neue'")
.html(function(d,i) {
return "<img src="+d.imgURL+" style='width:48px;height:48px;float:right;padding-bottom:5px'> "+"<bold>Name:</bold> " + d.name +"<br><bold>Description:</bold>In imperdiet rutrum porttitor. In nec risus eu leo sodales lobortis."
});
d3.select(".foreign").transition()
.delay(300)
.duration(1000)
.style("opacity",1);
})
.on("mouseout", function(){
d3.select(this).transition()
.duration(500)
.attr("r", function(d) { return Math.sqrt(d.amount)});
//remove text console.log(this.getBBox());
//d3.select(this.parentNode).select("text").remove();
d3.select(".foreign").remove();
});
//setup inputs
d3.selectAll("input").on("input", function() {
update(this.id, this.value);
});
function update(property, value) {
d3.select("#"+property+"-value").text(value);
//force.stop(); //is this neccessary?
if(property == "charge") {
force.charge(value);
force.start();
console.log("changing force.charge(" +value+")");
}
else if (property == "gravity") {
force.gravity(value);
force.start();
console.log("changing force.gravity(" +value+")");
}
}
//tick
function tick () {
//here be the magic
node.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y});
//d3.selectAll('text').attr("transform", function(d) {
// return 'translate(' + [d.x, d.y] + ')';
// })
d3.selectAll('.foreign').attr("transform", function(d) {
return 'translate(' + [(d.x-75/Math.sqrt(2)), (d.y-75/Math.sqrt(2))] + ')';
})
}
function charge(d) {
return d.amount * d.amount * -0.25;
}
//generate random nodes
function genNode() {
var maxAmt = 400;
var amt = Math.floor(Math.random()*maxAmt+1);
return {amount:amt};
}
function genNodes() {
var newNodes = [];
var maxNodes = 10;
var n = Math.floor(Math.random()*maxNodes+4);
for (var i =0; i< n; i++) {
newNodes.push(genNode());
}
return newNodes;
}
function getNewNodes() { location.reload();}
</script>
@nacmonad
Copy link
Author

nacmonad commented Jan 2, 2016

d3 force layouts and foreignObjects

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment