<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.node .selected {
stroke: black;
}
.link {
stroke: #999;
}
.brush .extent {
fill-opacity: .1;
stroke: #fff;
shape-rendering: crispEdges;
}
</style>
<body>
<div align='center' id="d3_selectable_force_directed_graph"></div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="plot.js"></script>
// Code goes here
/* Styles go here */
{
"nodes":[
{
"x":444,
"y":275
},
{
"x":378,
"y":324
},
{
"x":478,
"y":278
},
{
"x":471,
"y":256
},
{
"x":382,
"y":269
},
{
"x":371,
"y":247
},
{
"x":359,
"y":276
},
{
"x":364,
"y":302
},
{
"x":400,
"y":330
},
{
"x":388,
"y":298
},
{
"x":524,
"y":296
},
{
"x":570,
"y":243
},
{
"x":552,
"y":159
},
{
"x":502,
"y":287
},
{
"x":511,
"y":313
},
{
"x":513,
"y":265
},
{
"x":602,
"y":132
},
{
"x":610,
"y":90
},
{
"x":592,
"y":91
},
{
"x":575,
"y":89
},
{
"x":607,
"y":73
},
{
"x":591,
"y":68
},
{
"x":574,
"y":73
},
{
"x":589,
"y":149
},
{
"x":620,
"y":205
},
{
"x":621,
"y":230
},
{
"x":589,
"y":234
},
{
"x":602,
"y":223
},
{
"x":548,
"y":188
},
{
"x":532,
"y":196
},
{
"x":548,
"y":114
},
{
"x":575,
"y":174
},
{
"x":497,
"y":250
},
{
"x":576,
"y":196
},
{
"x":504,
"y":201
},
{
"x":494,
"y":186
},
{
"x":482,
"y":199
},
{
"x":505,
"y":219
},
{
"x":486,
"y":216
},
{
"x":590,
"y":306
},
{
"x":677,
"y":169
},
{
"x":657,
"y":258
},
{
"x":667,
"y":205
},
{
"x":552,
"y":227
},
{
"x":518,
"y":173
},
{
"x":473,
"y":125
},
{
"x":796,
"y":260
},
{
"x":731,
"y":272
},
{
"x":642,
"y":288
},
{
"x":576,
"y":269
},
{
"x":605,
"y":187
},
{
"x":559,
"y":289
},
{
"x":544,
"y":356
},
{
"x":505,
"y":365
},
{
"x":579,
"y":289
},
{
"x":619,
"y":282
},
{
"x":574,
"y":329
},
{
"x":664,
"y":306
},
{
"x":627,
"y":304
},
{
"x":643,
"y":327
},
{
"x":664,
"y":348
},
{
"x":665,
"y":327
},
{
"x":653,
"y":317
},
{
"x":650,
"y":338
},
{
"x":622,
"y":321
},
{
"x":633,
"y":338
},
{
"x":647,
"y":357
},
{
"x":718,
"y":362
},
{
"x":636,
"y":240
},
{
"x":640,
"y":227
},
{
"x":617,
"y":249
},
{
"x":631,
"y":254
},
{
"x":566,
"y":213
},
{
"x":713,
"y":322
},
{
"x":716,
"y":298
},
{
"x":666,
"y":241
},
{
"x":627,
"y":355
}
],
"links":[
{
"source":1,
"target":0
},
{
"source":2,
"target":0
},
{
"source":3,
"target":0
},
{
"source":3,
"target":2
},
{
"source":4,
"target":0
},
{
"source":5,
"target":0
},
{
"source":6,
"target":0
},
{
"source":7,
"target":0
},
{
"source":8,
"target":0
},
{
"source":9,
"target":0
},
{
"source":11,
"target":10
},
{
"source":11,
"target":3
},
{
"source":11,
"target":2
},
{
"source":11,
"target":0
},
{
"source":12,
"target":11
},
{
"source":13,
"target":11
},
{
"source":14,
"target":11
},
{
"source":15,
"target":11
},
{
"source":17,
"target":16
},
{
"source":18,
"target":16
},
{
"source":18,
"target":17
},
{
"source":19,
"target":16
},
{
"source":19,
"target":17
},
{
"source":19,
"target":18
},
{
"source":20,
"target":16
},
{
"source":20,
"target":17
},
{
"source":20,
"target":18
},
{
"source":20,
"target":19
},
{
"source":21,
"target":16
},
{
"source":21,
"target":17
},
{
"source":21,
"target":18
},
{
"source":21,
"target":19
},
{
"source":21,
"target":20
},
{
"source":22,
"target":16
},
{
"source":22,
"target":17
},
{
"source":22,
"target":18
},
{
"source":22,
"target":19
},
{
"source":22,
"target":20
},
{
"source":22,
"target":21
},
{
"source":23,
"target":16
},
{
"source":23,
"target":17
},
{
"source":23,
"target":18
},
{
"source":23,
"target":19
},
{
"source":23,
"target":20
},
{
"source":23,
"target":21
},
{
"source":23,
"target":22
},
{
"source":23,
"target":12
},
{
"source":23,
"target":11
},
{
"source":24,
"target":23
},
{
"source":24,
"target":11
},
{
"source":25,
"target":24
},
{
"source":25,
"target":23
},
{
"source":25,
"target":11
},
{
"source":26,
"target":24
},
{
"source":26,
"target":11
},
{
"source":26,
"target":16
},
{
"source":26,
"target":25
},
{
"source":27,
"target":11
},
{
"source":27,
"target":23
},
{
"source":27,
"target":25
},
{
"source":27,
"target":24
},
{
"source":27,
"target":26
},
{
"source":28,
"target":11
},
{
"source":28,
"target":27
},
{
"source":29,
"target":23
},
{
"source":29,
"target":27
},
{
"source":29,
"target":11
},
{
"source":30,
"target":23
},
{
"source":31,
"target":30
},
{
"source":31,
"target":11
},
{
"source":31,
"target":23
},
{
"source":31,
"target":27
},
{
"source":32,
"target":11
},
{
"source":33,
"target":11
},
{
"source":33,
"target":27
},
{
"source":34,
"target":11
},
{
"source":34,
"target":29
},
{
"source":35,
"target":11
},
{
"source":35,
"target":34
},
{
"source":35,
"target":29
},
{
"source":36,
"target":34
},
{
"source":36,
"target":35
},
{
"source":36,
"target":11
},
{
"source":36,
"target":29
},
{
"source":37,
"target":34
},
{
"source":37,
"target":35
},
{
"source":37,
"target":36
},
{
"source":37,
"target":11
},
{
"source":37,
"target":29
},
{
"source":38,
"target":34
},
{
"source":38,
"target":35
},
{
"source":38,
"target":36
},
{
"source":38,
"target":37
},
{
"source":38,
"target":11
},
{
"source":38,
"target":29
},
{
"source":39,
"target":25
},
{
"source":40,
"target":25
},
{
"source":41,
"target":24
},
{
"source":41,
"target":25
},
{
"source":42,
"target":41
},
{
"source":42,
"target":25
},
{
"source":42,
"target":24
},
{
"source":43,
"target":11
},
{
"source":43,
"target":26
},
{
"source":43,
"target":27
},
{
"source":44,
"target":28
},
{
"source":44,
"target":11
},
{
"source":45,
"target":28
},
{
"source":47,
"target":46
},
{
"source":48,
"target":47
},
{
"source":48,
"target":25
},
{
"source":48,
"target":27
},
{
"source":48,
"target":11
},
{
"source":49,
"target":26
},
{
"source":49,
"target":11
},
{
"source":50,
"target":49
},
{
"source":50,
"target":24
},
{
"source":51,
"target":49
},
{
"source":51,
"target":26
},
{
"source":51,
"target":11
},
{
"source":52,
"target":51
},
{
"source":52,
"target":39
},
{
"source":53,
"target":51
},
{
"source":54,
"target":51
},
{
"source":54,
"target":49
},
{
"source":54,
"target":26
},
{
"source":55,
"target":51
},
{
"source":55,
"target":49
},
{
"source":55,
"target":39
},
{
"source":55,
"target":54
},
{
"source":55,
"target":26
},
{
"source":55,
"target":11
},
{
"source":55,
"target":16
},
{
"source":55,
"target":25
},
{
"source":55,
"target":41
},
{
"source":55,
"target":48
},
{
"source":56,
"target":49
},
{
"source":56,
"target":55
},
{
"source":57,
"target":55
},
{
"source":57,
"target":41
},
{
"source":57,
"target":48
},
{
"source":58,
"target":55
},
{
"source":58,
"target":48
},
{
"source":58,
"target":27
},
{
"source":58,
"target":57
},
{
"source":58,
"target":11
},
{
"source":59,
"target":58
},
{
"source":59,
"target":55
},
{
"source":59,
"target":48
},
{
"source":59,
"target":57
},
{
"source":60,
"target":48
},
{
"source":60,
"target":58
},
{
"source":60,
"target":59
},
{
"source":61,
"target":48
},
{
"source":61,
"target":58
},
{
"source":61,
"target":60
},
{
"source":61,
"target":59
},
{
"source":61,
"target":57
},
{
"source":61,
"target":55
},
{
"source":62,
"target":55
},
{
"source":62,
"target":58
},
{
"source":62,
"target":59
},
{
"source":62,
"target":48
},
{
"source":62,
"target":57
},
{
"source":62,
"target":41
},
{
"source":62,
"target":61
},
{
"source":62,
"target":60
},
{
"source":63,
"target":59
},
{
"source":63,
"target":48
},
{
"source":63,
"target":62
},
{
"source":63,
"target":57
},
{
"source":63,
"target":58
},
{
"source":63,
"target":61
},
{
"source":63,
"target":60
},
{
"source":63,
"target":55
},
{
"source":64,
"target":55
},
{
"source":64,
"target":62
},
{
"source":64,
"target":48
},
{
"source":64,
"target":63
},
{
"source":64,
"target":58
},
{
"source":64,
"target":61
},
{
"source":64,
"target":60
},
{
"source":64,
"target":59
},
{
"source":64,
"target":57
},
{
"source":64,
"target":11
},
{
"source":65,
"target":63
},
{
"source":65,
"target":64
},
{
"source":65,
"target":48
},
{
"source":65,
"target":62
},
{
"source":65,
"target":58
},
{
"source":65,
"target":61
},
{
"source":65,
"target":60
},
{
"source":65,
"target":59
},
{
"source":65,
"target":57
},
{
"source":65,
"target":55
},
{
"source":66,
"target":64
},
{
"source":66,
"target":58
},
{
"source":66,
"target":59
},
{
"source":66,
"target":62
},
{
"source":66,
"target":65
},
{
"source":66,
"target":48
},
{
"source":66,
"target":63
},
{
"source":66,
"target":61
},
{
"source":66,
"target":60
},
{
"source":67,
"target":57
},
{
"source":68,
"target":25
},
{
"source":68,
"target":11
},
{
"source":68,
"target":24
},
{
"source":68,
"target":27
},
{
"source":68,
"target":48
},
{
"source":68,
"target":41
},
{
"source":69,
"target":25
},
{
"source":69,
"target":68
},
{
"source":69,
"target":11
},
{
"source":69,
"target":24
},
{
"source":69,
"target":27
},
{
"source":69,
"target":48
},
{
"source":69,
"target":41
},
{
"source":70,
"target":25
},
{
"source":70,
"target":69
},
{
"source":70,
"target":68
},
{
"source":70,
"target":11
},
{
"source":70,
"target":24
},
{
"source":70,
"target":27
},
{
"source":70,
"target":41
},
{
"source":70,
"target":58
},
{
"source":71,
"target":27
},
{
"source":71,
"target":69
},
{
"source":71,
"target":68
},
{
"source":71,
"target":70
},
{
"source":71,
"target":11
},
{
"source":71,
"target":48
},
{
"source":71,
"target":41
},
{
"source":71,
"target":25
},
{
"source":72,
"target":26
},
{
"source":72,
"target":27
},
{
"source":72,
"target":11
},
{
"source":73,
"target":48
},
{
"source":74,
"target":48
},
{
"source":74,
"target":73
},
{
"source":75,
"target":69
},
{
"source":75,
"target":68
},
{
"source":75,
"target":25
},
{
"source":75,
"target":48
},
{
"source":75,
"target":41
},
{
"source":75,
"target":70
},
{
"source":75,
"target":71
},
{
"source":76,
"target":64
},
{
"source":76,
"target":65
},
{
"source":76,
"target":66
},
{
"source":76,
"target":63
},
{
"source":76,
"target":62
},
{
"source":76,
"target":48
},
{
"source":76,
"target":58
}
]
}
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 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;
console.log(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);
}
}
selectableForceDirectedGraph();