|
function selectableForceDirectedGraph() { |
|
var width = 960, |
|
|
|
height = 500, |
|
shiftKey, ctrlKey; |
|
|
|
var nodeGraph = null; |
|
var xScale = d3.scale.linear() |
|
.domain([0,width]).range([0,width]); |
|
var yScale = d3.scale.linear() |
|
.domain([0,height]).range([0, height]); |
|
|
|
var svg = d3.select("#d3_selectable_force_directed_graph") |
|
.attr("tabindex", 1) |
|
.on("keydown.brush", keydown) |
|
.on("keyup.brush", keyup) |
|
.each(function() { this.focus(); }) |
|
.append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var zoomer = d3.behavior.zoom(). |
|
scaleExtent([0.1,10]). |
|
x(xScale). |
|
y(yScale). |
|
on("zoomstart", zoomstart). |
|
on("zoom", redraw); |
|
|
|
function zoomstart() { |
|
node.each(function(d) { |
|
d.selected = false; |
|
d.previouslySelected = false; |
|
}); |
|
node.classed("selected", false); |
|
} |
|
|
|
function redraw() { |
|
vis.attr("transform", |
|
"translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); |
|
} |
|
|
|
var brusher = d3.svg.brush() |
|
//.x(d3.scale.identity().domain([0, width])) |
|
//.y(d3.scale.identity().domain([0, height])) |
|
.x(xScale) |
|
.y(yScale) |
|
.on("brushstart", function(d) { |
|
node.each(function(d) { |
|
d.previouslySelected = shiftKey && d.selected; }); |
|
}) |
|
.on("brush", function() { |
|
var extent = d3.event.target.extent(); |
|
|
|
node.classed("selected", function(d) { |
|
return d.selected = d.previouslySelected ^ |
|
(extent[0][0] <= d.x && d.x < extent[1][0] |
|
&& extent[0][1] <= d.y && d.y < extent[1][1]); |
|
}); |
|
}) |
|
.on("brushend", function() { |
|
d3.event.target.clear(); |
|
d3.select(this).call(d3.event.target); |
|
}); |
|
|
|
var svg_graph = svg.append('svg:g') |
|
.call(zoomer) |
|
//.call(brusher) |
|
|
|
var rect = svg_graph.append('svg:rect') |
|
.attr('width', width) |
|
.attr('height', height) |
|
.attr('fill', 'transparent') |
|
//.attr('opacity', 0.5) |
|
.attr('stroke', 'transparent') |
|
.attr('stroke-width', 1) |
|
//.attr("pointer-events", "all") |
|
.attr("id", "zrect") |
|
|
|
var brush = svg_graph.append("g") |
|
.datum(function() { return {selected: false, previouslySelected: false}; }) |
|
.attr("class", "brush"); |
|
|
|
var vis = svg_graph.append("svg:g"); |
|
|
|
vis.attr('fill', 'red') |
|
.attr('stroke', 'black') |
|
.attr('stroke-width', 1) |
|
.attr('opacity', 0.5) |
|
.attr('id', 'vis') |
|
|
|
|
|
brush.call(brusher) |
|
.on("mousedown.brush", null) |
|
.on("touchstart.brush", null) |
|
.on("touchmove.brush", null) |
|
.on("touchend.brush", null); |
|
|
|
brush.select('.background').style('cursor', 'auto'); |
|
|
|
var link = vis.append("g") |
|
.attr("class", "link") |
|
.selectAll("line"); |
|
|
|
var node = vis.append("g") |
|
.attr("class", "node") |
|
.selectAll("circle"); |
|
|
|
center_view = function() { |
|
// Center the view on the molecule(s) and scale it so that everything |
|
// fits in the window |
|
|
|
if (nodeGraph === null) |
|
return; |
|
|
|
var nodes = nodeGraph.nodes; |
|
|
|
//no molecules, nothing to do |
|
if (nodes.length === 0) |
|
return; |
|
|
|
// Get the bounding box |
|
min_x = d3.min(nodes.map(function(d) {return d.x;})); |
|
min_y = d3.min(nodes.map(function(d) {return d.y;})); |
|
|
|
max_x = d3.max(nodes.map(function(d) {return d.x;})); |
|
max_y = d3.max(nodes.map(function(d) {return d.y;})); |
|
|
|
|
|
// The width and the height of the graph |
|
mol_width = max_x - min_x; |
|
mol_height = max_y - min_y; |
|
|
|
// how much larger the drawing area is than the width and the height |
|
width_ratio = width / mol_width; |
|
height_ratio = height / mol_height; |
|
|
|
// we need to fit it in both directions, so we scale according to |
|
// the direction in which we need to shrink the most |
|
min_ratio = Math.min(width_ratio, height_ratio) * 0.8; |
|
|
|
// the new dimensions of the molecule |
|
new_mol_width = mol_width * min_ratio; |
|
new_mol_height = mol_height * min_ratio; |
|
|
|
// translate so that it's in the center of the window |
|
x_trans = -(min_x) * min_ratio + (width - new_mol_width) / 2; |
|
y_trans = -(min_y) * min_ratio + (height - new_mol_height) / 2; |
|
|
|
|
|
// do the actual moving |
|
vis.attr("transform", |
|
"translate(" + [x_trans, y_trans] + ")" + " scale(" + min_ratio + ")"); |
|
|
|
// tell the zoomer what we did so that next we zoom, it uses the |
|
// transformation we entered here |
|
zoomer.translate([x_trans, y_trans ]); |
|
zoomer.scale(min_ratio); |
|
|
|
}; |
|
|
|
function dragended(d) { |
|
//d3.select(self).classed("dragging", false); |
|
node.filter(function(d) { return d.selected; }) |
|
.each(function(d) { d.fixed &= ~6; }) |
|
|
|
} |
|
|
|
d3.json("graph.json", function(error, graph) { |
|
nodeGraph = graph; |
|
|
|
graph.links.forEach(function(d) { |
|
d.source = graph.nodes[d.source]; |
|
d.target = graph.nodes[d.target]; |
|
}); |
|
|
|
link = link.data(graph.links).enter().append("line") |
|
.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; }); |
|
|
|
|
|
var force = d3.layout.force() |
|
.charge(-120) |
|
.linkDistance(30) |
|
.nodes(graph.nodes) |
|
.links(graph.links) |
|
.size([width, height]) |
|
.start(); |
|
|
|
function dragstarted(d) { |
|
d3.event.sourceEvent.stopPropagation(); |
|
if (!d.selected && !shiftKey) { |
|
// if this node isn't selected, then we have to unselect every other node |
|
node.classed("selected", function(p) { return p.selected = p.previouslySelected = false; }); |
|
} |
|
|
|
d3.select(this).classed("selected", function(p) { d.previouslySelected = d.selected; return d.selected = true; }); |
|
|
|
node.filter(function(d) { return d.selected; }) |
|
.each(function(d) { d.fixed |= 2; }) |
|
} |
|
|
|
function dragged(d) { |
|
node.filter(function(d) { return d.selected; }) |
|
.each(function(d) { |
|
d.x += d3.event.dx; |
|
d.y += d3.event.dy; |
|
|
|
d.px += d3.event.dx; |
|
d.py += d3.event.dy; |
|
}) |
|
|
|
force.resume(); |
|
} |
|
node = node.data(graph.nodes).enter().append("circle") |
|
.attr("r", 4) |
|
.attr("cx", function(d) { return d.x; }) |
|
.attr("cy", function(d) { return d.y; }) |
|
.on("dblclick", function(d) { d3.event.stopPropagation(); }) |
|
.on("click", function(d) { |
|
if (d3.event.defaultPrevented) return; |
|
|
|
if (!shiftKey) { |
|
//if the shift key isn't down, unselect everything |
|
node.classed("selected", function(p) { return p.selected = p.previouslySelected = false; }) |
|
} |
|
|
|
// always select this node |
|
d3.select(this).classed("selected", d.selected = !d.previouslySelected); |
|
}) |
|
|
|
.on("mouseup", function(d) { |
|
//if (d.selected && shiftKey) d3.select(this).classed("selected", d.selected = false); |
|
}) |
|
.call(d3.behavior.drag() |
|
.on("dragstart", dragstarted) |
|
.on("drag", dragged) |
|
.on("dragend", dragended)); |
|
|
|
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; }); |
|
|
|
}; |
|
|
|
force.on("tick", tick); |
|
|
|
}); |
|
|
|
|
|
function keydown() { |
|
shiftKey = d3.event.shiftKey || d3.event.metaKey; |
|
ctrlKey = d3.event.ctrlKey; |
|
|
|
console.log('d3.event', d3.event) |
|
|
|
if (d3.event.keyCode == 67) { //the 'c' key |
|
center_view(); |
|
} |
|
|
|
if (shiftKey) { |
|
svg_graph.call(zoomer) |
|
.on("mousedown.zoom", null) |
|
.on("touchstart.zoom", null) |
|
.on("touchmove.zoom", null) |
|
.on("touchend.zoom", null); |
|
|
|
//svg_graph.on('zoom', null); |
|
vis.selectAll('g.gnode') |
|
.on('mousedown.drag', null); |
|
|
|
brush.select('.background').style('cursor', 'crosshair') |
|
brush.call(brusher); |
|
} |
|
} |
|
|
|
function keyup() { |
|
shiftKey = d3.event.shiftKey || d3.event.metaKey; |
|
ctrlKey = d3.event.ctrlKey; |
|
|
|
brush.call(brusher) |
|
.on("mousedown.brush", null) |
|
.on("touchstart.brush", null) |
|
.on("touchmove.brush", null) |
|
.on("touchend.brush", null); |
|
|
|
brush.select('.background').style('cursor', 'auto') |
|
svg_graph.call(zoomer); |
|
} |
|
} |
Hey, how are you doing? Awesome work here, I was trying something and I'd like to add some texts labels to each node, I've been doing something like this
What am I doing wrong? Do I need to change something in your code? I'm newbie using d3.js
Thanks!