[ Launch: asd ] c3c98b497afa902c075a4c23da029385 by ErikBjare
-
-
Save ErikBjare/c3c98b497afa902c075a4c23da029385 to your computer and use it in GitHub Desktop.
knowtree-graph
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
{"description":"knowtree-graph","endpoint":"","display":"svg","public":true,"require":[],"fileconfigs":{"inlet.js":{"default":true,"vim":false,"emacs":false,"fontSize":12},"_.md":{"default":true,"vim":false,"emacs":false,"fontSize":12},"config.json":{"default":true,"vim":false,"emacs":false,"fontSize":12}},"fullscreen":false,"play":false,"loop":false,"restart":false,"autoinit":true,"pause":true,"loop_type":"pingpong","bv":false,"nclones":15,"clone_opacity":0.4,"duration":3000,"ease":"linear","dt":0.01,"ajax-caching":true,"thumbnail":"http://i.imgur.com/uvTEcaF.png","inline-console":true} |
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
// Force-directed graph: http://bl.ocks.org/rkirsling/5001347 | |
// set up SVG for D3 | |
var width = 960, | |
height = 500, | |
colors = d3.scale.category10(); | |
var nodes = [ | |
{id: 0, reflexive: false}, | |
{id: 1, reflexive: true }, | |
{id: 2, reflexive: false} | |
], | |
lastNodeId = 2, | |
links = [ | |
{source: nodes[0], target: nodes[1], left: false, right: true }, | |
{source: nodes[1], target: nodes[2], left: false, right: true } | |
]; | |
var svg = d3.select("svg") | |
svg.append("rect") | |
.attr({ | |
width: 100, | |
height: 100, | |
x: 200, | |
y: 62, | |
fill: "#3ba454" | |
}); | |
// init D3 force layout | |
var force = d3.layout.force() | |
.nodes(nodes) | |
.links(links) | |
.size([width, height]) | |
.linkDistance(150) | |
.charge(-500) | |
.on('tick', tick) | |
// define arrow markers for graph links | |
svg.append('svg:defs').append('svg:marker') | |
.attr('id', 'end-arrow') | |
.attr('viewBox', '0 -5 10 10') | |
.attr('refX', 6) | |
.attr('markerWidth', 3) | |
.attr('markerHeight', 3) | |
.attr('orient', 'auto') | |
.append('svg:path') | |
.attr('d', 'M0,-5L10,0L0,5') | |
.attr('fill', '#000'); | |
svg.append('svg:defs').append('svg:marker') | |
.attr('id', 'start-arrow') | |
.attr('viewBox', '0 -5 10 10') | |
.attr('refX', 4) | |
.attr('markerWidth', 3) | |
.attr('markerHeight', 3) | |
.attr('orient', 'auto') | |
.append('svg:path') | |
.attr('d', 'M10,-5L0,0L10,5') | |
.attr('fill', '#000'); | |
// line displayed when dragging new nodes | |
var drag_line = svg.append('svg:path') | |
.attr('class', 'link dragline hidden') | |
.attr('d', 'M0,0L0,0'); | |
// handles to link and node element groups | |
var path = svg.append('svg:g').selectAll('path'), | |
circle = svg.append('svg:g').selectAll('g'); | |
// mouse event vars | |
var selected_node = null, | |
selected_link = null, | |
mousedown_link = null, | |
mousedown_node = null, | |
mouseup_node = null; | |
function resetMouseVars() { | |
mousedown_node = null; | |
mouseup_node = null; | |
mousedown_link = null; | |
} | |
// update force layout (called automatically each iteration) | |
function tick() { | |
// draw directed edges with proper padding from node centers | |
path.attr('d', function(d) { | |
var deltaX = d.target.x - d.source.x, | |
deltaY = d.target.y - d.source.y, | |
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY), | |
normX = deltaX / dist, | |
normY = deltaY / dist, | |
sourcePadding = d.left ? 17 : 12, | |
targetPadding = d.right ? 17 : 12, | |
sourceX = d.source.x + (sourcePadding * normX), | |
sourceY = d.source.y + (sourcePadding * normY), | |
targetX = d.target.x - (targetPadding * normX), | |
targetY = d.target.y - (targetPadding * normY); | |
return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY; | |
}); | |
circle.attr('transform', function(d) { | |
return 'translate(' + d.x + ',' + d.y + ')'; | |
}); | |
} | |
// update graph (called when needed) | |
function restart() { | |
// path (link) group | |
path = path.data(links); | |
// update existing links | |
path.classed('selected', function(d) { return d === selected_link; }) | |
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; }) | |
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; }); | |
// add new links | |
path.enter().append('svg:path') | |
.attr('class', 'link') | |
.classed('selected', function(d) { return d === selected_link; }) | |
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; }) | |
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; }) | |
.on('mousedown', function(d) { | |
if(d3.event.ctrlKey) return; | |
// select link | |
mousedown_link = d; | |
if(mousedown_link === selected_link) selected_link = null; | |
else selected_link = mousedown_link; | |
selected_node = null; | |
restart(); | |
}); | |
// remove old links | |
path.exit().remove(); | |
// circle (node) group | |
// NB: the function arg is crucial here! nodes are known by id, not by index! | |
circle = circle.data(nodes, function(d) { return d.id; }); | |
// update existing nodes (reflexive & selected visual states) | |
circle.selectAll('circle') | |
.style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); }) | |
.classed('reflexive', function(d) { return d.reflexive; }); | |
// add new nodes | |
var g = circle.enter().append('svg:g'); | |
g.append('svg:circle') | |
.attr('class', 'node') | |
.attr('r', 12) | |
.style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); }) | |
.style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); }) | |
.classed('reflexive', function(d) { return d.reflexive; }) | |
.on('mouseover', function(d) { | |
if(!mousedown_node || d === mousedown_node) return; | |
// enlarge target node | |
d3.select(this).attr('transform', 'scale(1.1)'); | |
}) | |
.on('mouseout', function(d) { | |
if(!mousedown_node || d === mousedown_node) return; | |
// unenlarge target node | |
d3.select(this).attr('transform', ''); | |
}) | |
.on('mousedown', function(d) { | |
if(d3.event.ctrlKey) return; | |
// select node | |
mousedown_node = d; | |
if(mousedown_node === selected_node) selected_node = null; | |
else selected_node = mousedown_node; | |
selected_link = null; | |
// reposition drag line | |
drag_line | |
.style('marker-end', 'url(#end-arrow)') | |
.classed('hidden', false) | |
.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y); | |
restart(); | |
}) | |
.on('mouseup', function(d) { | |
if(!mousedown_node) return; | |
// needed by FF | |
drag_line | |
.classed('hidden', true) | |
.style('marker-end', ''); | |
// check for drag-to-self | |
mouseup_node = d; | |
if(mouseup_node === mousedown_node) { resetMouseVars(); return; } | |
// unenlarge target node | |
d3.select(this).attr('transform', ''); | |
// add link to graph (update if exists) | |
// NB: links are strictly source < target; arrows separately specified by booleans | |
var source, target, direction; | |
if(mousedown_node.id < mouseup_node.id) { | |
source = mousedown_node; | |
target = mouseup_node; | |
direction = 'right'; | |
} else { | |
source = mouseup_node; | |
target = mousedown_node; | |
direction = 'left'; | |
} | |
var link; | |
link = links.filter(function(l) { | |
return (l.source === source && l.target === target); | |
})[0]; | |
if(link) { | |
link[direction] = true; | |
} else { | |
link = {source: source, target: target, left: false, right: false}; | |
link[direction] = true; | |
links.push(link); | |
} | |
// select new link | |
selected_link = link; | |
selected_node = null; | |
restart(); | |
}); | |
// show node IDs | |
g.append('svg:text') | |
.attr('x', 0) | |
.attr('y', 4) | |
.attr('class', 'id') | |
.text(function(d) { return d.id; }); | |
// remove old nodes | |
circle.exit().remove(); | |
// set the graph in motion | |
force.start(); | |
} | |
function mousedown() { | |
// prevent I-bar on drag | |
//d3.event.preventDefault(); | |
// because :active only works in WebKit? | |
svg.classed('active', true); | |
if(d3.event.ctrlKey || mousedown_node || mousedown_link) return; | |
// insert new node at point | |
var point = d3.mouse(this), | |
node = {id: ++lastNodeId, reflexive: false}; | |
node.x = point[0]; | |
node.y = point[1]; | |
nodes.push(node); | |
restart(); | |
} | |
function mousemove() { | |
if(!mousedown_node) return; | |
// update drag line | |
drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]); | |
restart(); | |
} | |
function mouseup() { | |
if(mousedown_node) { | |
// hide drag line | |
drag_line | |
.classed('hidden', true) | |
.style('marker-end', ''); | |
} | |
// because :active only works in WebKit? | |
svg.classed('active', false); | |
// clear mouse event vars | |
resetMouseVars(); | |
} | |
function spliceLinksForNode(node) { | |
var toSplice = links.filter(function(l) { | |
return (l.source === node || l.target === node); | |
}); | |
toSplice.map(function(l) { | |
links.splice(links.indexOf(l), 1); | |
}); | |
} | |
// only respond once per keydown | |
var lastKeyDown = -1; | |
function keydown() { | |
d3.event.preventDefault(); | |
if(lastKeyDown !== -1) return; | |
lastKeyDown = d3.event.keyCode; | |
// ctrl | |
if(d3.event.keyCode === 17) { | |
circle.call(force.drag); | |
svg.classed('ctrl', true); | |
} | |
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; | |
restart(); | |
break; | |
case 66: // B | |
if(selected_link) { | |
// set link direction to both left and right | |
selected_link.left = true; | |
selected_link.right = true; | |
} | |
restart(); | |
break; | |
case 76: // L | |
if(selected_link) { | |
// set link direction to left only | |
selected_link.left = true; | |
selected_link.right = false; | |
} | |
restart(); | |
break; | |
case 82: // R | |
if(selected_node) { | |
// toggle node reflexivity | |
selected_node.reflexive = !selected_node.reflexive; | |
} else if(selected_link) { | |
// set link direction to right only | |
selected_link.left = false; | |
selected_link.right = true; | |
} | |
restart(); | |
break; | |
} | |
} | |
function keyup() { | |
lastKeyDown = -1; | |
// ctrl | |
if(d3.event.keyCode === 17) { | |
circle | |
.on('mousedown.drag', null) | |
.on('touchstart.drag', null); | |
svg.classed('ctrl', false); | |
} | |
} | |
// app starts here | |
svg.on('mousedown', mousedown) | |
.on('mousemove', mousemove) | |
.on('mouseup', mouseup); | |
d3.select(window) | |
.on('keydown', keydown) | |
.on('keyup', keyup); | |
restart(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment