Instantly share code, notes, and snippets.

@benzguo /README.md
Last active Oct 12, 2016

Embed
What would you like to do?
Force Editor + Pan/Zoom

Drag from an existing node to add a new node or link. Click to select/deselect nodes/links. Hit the DELETE key to remove the selected node or link. Drag to pan. Scroll to zoom.

Built with D3.js.

var width = 960,
height = 500,
fill = d3.scale.category20();
// mouse event vars
var selected_node = null,
selected_link = null,
mousedown_link = null,
mousedown_node = null,
mouseup_node = null;
// init svg
var outer = d3.select("#chart")
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all");
var vis = outer
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", rescale))
.on("dblclick.zoom", null)
.append('svg:g')
.on("mousemove", mousemove)
.on("mousedown", mousedown)
.on("mouseup", mouseup);
vis.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'white');
// init force layout
var force = d3.layout.force()
.size([width, height])
.nodes([{}]) // initialize with a single node
.linkDistance(50)
.charge(-200)
.on("tick", tick);
// line displayed when dragging new nodes
var drag_line = vis.append("line")
.attr("class", "drag_line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 0);
// get layout properties
var nodes = force.nodes(),
links = force.links(),
node = vis.selectAll(".node"),
link = vis.selectAll(".link");
// add keyboard callback
d3.select(window)
.on("keydown", keydown);
redraw();
// focus on svg
// vis.node().focus();
function mousedown() {
if (!mousedown_node && !mousedown_link) {
// allow panning if nothing is selected
vis.call(d3.behavior.zoom().on("zoom"), rescale);
return;
}
}
function mousemove() {
if (!mousedown_node) return;
// update drag line
drag_line
.attr("x1", mousedown_node.x)
.attr("y1", mousedown_node.y)
.attr("x2", d3.svg.mouse(this)[0])
.attr("y2", d3.svg.mouse(this)[1]);
}
function mouseup() {
if (mousedown_node) {
// hide drag line
drag_line
.attr("class", "drag_line_hidden")
if (!mouseup_node) {
// add node
var point = d3.mouse(this),
node = {x: point[0], y: point[1]},
n = nodes.push(node);
// select new node
selected_node = node;
selected_link = null;
// add link to mousedown node
links.push({source: mousedown_node, target: node});
}
redraw();
}
// clear mouse event vars
resetMouseVars();
}
function resetMouseVars() {
mousedown_node = null;
mouseup_node = null;
mousedown_link = null;
}
function tick() {
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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// rescale g
function rescale() {
trans=d3.event.translate;
scale=d3.event.scale;
vis.attr("transform",
"translate(" + trans + ")"
+ " scale(" + scale + ")");
}
// redraw force layout
function redraw() {
link = link.data(links);
link.enter().insert("line", ".node")
.attr("class", "link")
.on("mousedown",
function(d) {
mousedown_link = d;
if (mousedown_link == selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
redraw();
})
link.exit().remove();
link
.classed("link_selected", function(d) { return d === selected_link; });
node = node.data(nodes);
node.enter().insert("circle")
.attr("class", "node")
.attr("r", 5)
.on("mousedown",
function(d) {
// disable zoom
vis.call(d3.behavior.zoom().on("zoom"), null);
mousedown_node = d;
if (mousedown_node == selected_node) selected_node = null;
else selected_node = mousedown_node;
selected_link = null;
// reposition drag line
drag_line
.attr("class", "link")
.attr("x1", mousedown_node.x)
.attr("y1", mousedown_node.y)
.attr("x2", mousedown_node.x)
.attr("y2", mousedown_node.y);
redraw();
})
.on("mousedrag",
function(d) {
// redraw();
})
.on("mouseup",
function(d) {
if (mousedown_node) {
mouseup_node = d;
if (mouseup_node == mousedown_node) { resetMouseVars(); return; }
// add link
var link = {source: mousedown_node, target: mouseup_node};
links.push(link);
// select new link
selected_link = link;
selected_node = null;
// enable zoom
vis.call(d3.behavior.zoom().on("zoom"), rescale);
redraw();
}
})
.transition()
.duration(750)
.ease("elastic")
.attr("r", 6.5);
node.exit().transition()
.attr("r", 0)
.remove();
node
.classed("node_selected", function(d) { return d === selected_node; });
if (d3.event) {
// prevent browser's default behavior
d3.event.preventDefault();
}
force.start();
}
function spliceLinksForNode(node) {
toSplice = links.filter(
function(l) {
return (l.source === node) || (l.target === node); });
toSplice.map(
function(l) {
links.splice(links.indexOf(l), 1); });
}
function keydown() {
if (!selected_node && !selected_link) return;
switch (d3.event.keyCode) {
case 8: // backspace
case 46: { // delete
if (selected_node) {
nodes.splice(nodes.indexOf(selected_node), 1);
spliceLinksForNode(selected_node);
}
else if (selected_link) {
links.splice(links.indexOf(selected_link), 1);
}
selected_link = null;
selected_node = null;
redraw();
break;
}
}
}
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>Force Editor</title>
<!-- <script src="d3.v2.min.js"></script> -->
<script src="http://d3js.org/d3.v2.min.js"></script>
<!-- <script src="jquery-1.8.3.min.js"></script> -->
<style>
body {
font: 13px sans-serif;
position: relative;
width: 960px;
height: 500px;
}
.node {
fill: #000;
cursor: crosshair;
}
.node_selected {
fill: #ff7f0e;
stroke: #ff7f0e;
}
.drag_line {
stroke: #999;
stroke-width: 5;
pointer-events: none;
}
.drag_line_hidden {
stroke: #999;
stroke-width: 0;
pointer-events: none;
}
.link {
stroke: #999;
stroke-width: 5;
cursor: crosshair;
}
.link_selected {
stroke: #ff7f0e;
}
</style>
<head>
<body>
<div id="chart">
</div>
<script src="force_view.js"></script>
</body>
</html>
@lukemt

This comment has been minimized.

lukemt commented Mar 9, 2014

that's funny :)

@owendall

This comment has been minimized.

owendall commented Jul 21, 2016

Nice work!

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