Created
December 17, 2012 16:11
-
-
Save puleos/4319480 to your computer and use it in GitHub Desktop.
Node Manipulation using D3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Ben Bederson | |
// June 2012 | |
// www.cs.umd.edu/~bederson | |
$(function() { | |
var svg_w = 550, | |
svg_h = 400, | |
node_w = 70, | |
node_h = 20, | |
node_active_h = 50, | |
node_stroke = "8, 2", | |
cursor_r = 30, | |
rx = 2, | |
active_rx = 5, | |
src_r = 7, | |
nodesrc_stroke = "3, 3", | |
fill = d3.scale.category20(), | |
max_id = 1, | |
nodes = [], | |
links = []; | |
var svg = d3.select("#svg").append("svg:svg") | |
.attr("id", "svg") | |
.attr("width", svg_w) | |
.attr("height", svg_h); | |
var bgnd = svg.append("svg:rect") | |
.attr("id", "bgnd") | |
.attr("width", svg_w) | |
.attr("height", svg_h); | |
var force = d3.layout.force() | |
.nodes(nodes) | |
.links(links) | |
.size([svg_w, svg_h]) | |
.charge(-200); | |
force.on("tick", function() { | |
svg.selectAll("g.node") | |
.attr("x", function(d) { return d.x; }) | |
.attr("y", function(d) { return d.y; }) | |
.attr("transform", function(d) { return "translate(" + d.x + ", " + d.y + ")"; }); | |
svg.selectAll("line.link") | |
.attr("x1", function(d) { return parseInt(d.source.attr("x")) + node_w/2; }) | |
.attr("y1", function(d) { return parseInt(d.source.attr("y")) + node_h/2; }) | |
.attr("x2", function(d) { return parseInt(d.target.attr("x")) + node_w/2; }) | |
.attr("y2", function(d) { return parseInt(d.target.attr("y")) + node_h/2; }); | |
}); | |
bgnd.on("mousedown", function() { | |
var point = d3.mouse(this), | |
node = {id: max_id++, x: point[0], y: point[1]}; | |
nodes.push(node); | |
restart(); | |
}); | |
// Mouse moves over the background (thus exiting nodes) | |
bgnd.on("mouseover", function(d, i) { | |
unselectNodes(); | |
}); | |
restart(); | |
function restart() { | |
// Create a group for each node | |
var groups = svg.selectAll("g.node") | |
.data(nodes) | |
.enter() | |
.append("svg:g") | |
.attr("id", function(d) { | |
return "node" + d.id; | |
}) | |
.attr("class", "node nodeInactive") | |
.attr("width", node_w) | |
.attr("height", node_h) | |
.on("mouseover", function(d, i) { | |
selectNode(d3.select(this)); | |
}); | |
// .call(force.drag); | |
createNodeContent(groups); | |
// Create links | |
svg.selectAll("line.link") | |
.data(links) | |
.enter().insert("svg:line", "g.node") | |
.attr("class", "link") | |
.attr("x1", 0) | |
.attr("y1", 0) | |
.attr("x2", 0) | |
.attr("y2", 0); | |
force.start(); | |
} | |
function createNodeContent(groups) { | |
// Create node rectangles | |
groups | |
.append("svg:rect") | |
.attr("class", "nodeBgnd nodeBgndInactive") | |
.attr("width", node_w) | |
.attr("height", node_h) | |
.attr("stroke-dasharray", node_stroke) | |
.attr("rx", rx); | |
// Create node labels | |
groups | |
.append("svg:text") | |
.attr("class", "nodeLabel nodeLabelInactive") | |
.attr("x", function(d) { return 5; }) | |
.attr("y", function(d) { return 15; }) | |
.style("-moz-user-select", "none") | |
.style("-webkit-user-select", "none") | |
.text(function(d) { return "(" + Math.floor(d.x) + ", " + Math.floor(d.y) + ")"}); | |
} | |
function selectNode(node) { | |
var activeLine = []; | |
var drawingLine = false; | |
var sourceMouseDown; | |
if (node.attr("class").indexOf("nodeInactive") != -1) { | |
unselectNodes(); | |
} | |
node | |
.attr("class", "node nodeActive") | |
.attr("height", node_active_h); | |
node.on("mouseover", null); | |
node.select("rect") | |
.attr("class", "nodeBgnd nodeBgndActive") | |
.attr("stroke-dasharray", "0") | |
.transition() | |
.attr("rx", active_rx) | |
.attr("height", node_active_h); | |
node.select(".nodeLabel") | |
.attr("class", "nodeLabel nodeLabelActive") | |
.attr("orig", node.select(".nodeLabel").text()) | |
.text("More detail"); | |
var nodeSrc = node.selectAll("circle.nodeSrc"); | |
if (nodeSrc.empty()) { | |
var line = d3.svg.line() | |
.x(function(d, i) { return d.x; }) | |
.y(function(d, i) { return d.y; }) | |
var nodeSrc = node.append("svg:circle") | |
.attr("class", "nodeSrc nodeSrcInactive") | |
.attr("stroke-dasharray", nodesrc_stroke) | |
.attr("cx", node_w) | |
.attr("cy", 0) | |
.attr("r", 0) | |
.on("mouseover", function(d, i) { | |
if (!drawingLine) { | |
d3.select(this) | |
.attr("class", "nodeSrc nodeSrcActive") | |
.attr("stroke-dasharray", "0"); | |
} | |
}) | |
.on("mouseout", function(d, i) { | |
if (!drawingLine) { | |
d3.select(this) | |
.attr("class", "nodeSrc nodeSrcInactive") | |
.attr("stroke-dasharray", nodesrc_stroke); | |
} | |
}) | |
.on("mousedown", function(d, i) { | |
drawingLine = true; | |
node.attr("class", "node nodeSelected"); | |
node.select("rect") | |
.attr("class", "nodeBgnd nodeBgndSelected"); | |
d3.select(".activeLine").remove(); // Only want one targetting line | |
var cx = parseInt(node.attr("x")) + parseInt(d3.select(this).attr("cx")); | |
var cy = parseInt(node.attr("y")) + parseInt(d3.select(this).attr("cy")); | |
var dx = - d3.mouse(svg.node())[0]; | |
var dy = - d3.mouse(svg.node())[1]; | |
sourceMouseDown = [d3.event.clientX + dx, d3.event.clientY + dy]; | |
activeLine = [{x:cx, y:cy}, {x:cx, y:cy}]; | |
svg | |
.append("svg:path") | |
.data([activeLine]) | |
.attr("class", "activeLine") | |
.attr("d", line); | |
// Use JQuery events here so they are active even when mouse over SVG elements | |
$("#svg").mousemove(function(evt) { | |
if (evt.which > 0) { | |
var pt = [evt.clientX, evt.clientY]; | |
pt[0] -= sourceMouseDown[0]; | |
pt[1] -= sourceMouseDown[1]; | |
activeLine[1] = {x:pt[0], y:pt[1]}; | |
d3.select(".activeLine") | |
.data([activeLine]) | |
.attr("d", line); | |
} | |
}); | |
$("#svg").mouseup(function(evt) { | |
drawingLine = false; | |
// First get rid of line drawing event handlers | |
$("#svg").unbind('mousemove'); | |
$("#svg").unbind('mouseup'); | |
// Then update graph connectivity | |
d3.select(".activeLine").remove(); | |
var target = d3.select(".nodeActive"); | |
if (!target.empty()) { | |
links.push({source: node, target: target}); | |
restart(); | |
} | |
// Update classes | |
d3.select(".nodeSelected") | |
.attr("class", "node nodeActive"); | |
d3.select(".nodeBgndSelected") | |
.attr("class", "nodeBgnd nodeBgndActive"); | |
unselectNodes(); | |
// Update node event handlers | |
}); | |
}) | |
.transition() | |
.attr("cy", node_active_h/2) | |
.attr("r", src_r) | |
.each("end", function() { | |
restart(); | |
}); | |
} | |
} | |
function unselectNodes() { | |
var nodes = d3.selectAll(".nodeActive") | |
if (!nodes.empty()) { | |
nodes | |
.attr("class", "node nodeInactive") | |
.attr("height", 20) | |
.on("mouseover", function(d, i) { | |
selectNode(d3.select(this)); | |
}); | |
nodes.select("rect") | |
.attr("class", "nodeBgnd nodeBgndInactive") | |
.attr("stroke-dasharray", node_stroke) | |
.transition() | |
.attr("rx", rx) | |
.attr("height", 20); | |
nodes.select(".nodeLabel") | |
.attr("class", "nodeLabel nodeLabelInactive") | |
.text(nodes.select(".nodeLabel").attr("orig")); | |
d3.selectAll(".nodeSrc") | |
.transition() | |
.attr("cy", 0) | |
.attr("r", 0) | |
.remove(); | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment