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.
forked from benzguo's block: Force Editor + Pan/Zoom
| license: mit |
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.
forked from benzguo's block: Force Editor + Pan/Zoom
| 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]) | |
| .linkDistance(50) | |
| .charge(-200) | |
| .on("tick", tick); | |
| setData(force); | |
| // 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); | |
| var g = node.enter().insert("circle") | |
| .attr("class", "node"); | |
| g.attr("r", 20); | |
| // node.enter() | |
| // .insert("circle") | |
| // .attr("class", "node") | |
| g.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", 10); | |
| // g.style("fill", function (d) { | |
| // return color(-2.8); | |
| // }); | |
| node.exit().transition() | |
| .attr("r", 0) | |
| .remove(); | |
| node | |
| .classed("node_selected", function(d) { return d === selected_node; }); | |
| node.append("text") | |
| .attr("dx", 10) | |
| .attr("dy", ".35em") | |
| .text(function(d) { return 'd.name' }); | |
| 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; | |
| } | |
| } | |
| } | |
| function setData(force) | |
| { | |
| force.nodes(getNodes()); | |
| force.links(getLinks()); | |
| } | |
| function getNodes() { | |
| //return [{}]; | |
| return [{ | |
| "name": "Joe", | |
| "group": 1 | |
| }, { | |
| "name": "Anna", | |
| "group": 1 | |
| }, { | |
| "name": "Jackson", | |
| "group": 1 | |
| }, { | |
| "name": "Jelly", | |
| "group": 1 | |
| }, { | |
| "name": "Heather", | |
| "group": 2 | |
| }, { | |
| "name": "Clifford", | |
| "group": 2 | |
| } | |
| ]; | |
| } | |
| function getLinks(){ | |
| return ([ | |
| {"source": 0, "target": 1, "value": 1}, | |
| {"source": 0, "target": 2, "value": 1}, | |
| {"source": 0, "target": 3, "value": 1}, | |
| {"source": 0, "target": 4, "value": 1}, | |
| {"source": 0, "target": 5, "value": 1}, | |
| ]) | |
| } | |
| <!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> |