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> | |
d3 force layouts and foreignObjects