我已经尝试了好一阵子了(几天……),我似乎很亲密,但是显然不了解联接。我基于几个示例构建了此可视化,并且大多数情况下都可以正常工作。我似乎对节点和链接的不连续图有很好的力模拟。我可以单击并拖动图上的节点,也可以使用滚轮输入进行缩放。
当我向节点添加点击处理程序时,就会出现问题。加载JSON数据时,一次调用了update函数,然后我希望调用相同的update函数来更新图形数据,然后在可视化中查看已更新的更改...即像this example和this example一样崩溃的节点,再加上this last example,并以this question和this question为灵感。
在有一组节点的情况下,我得到这种奇怪的行为,它们表现得很好,但是当我单击时,模拟冻结,然后如果再次单击一个节点,则会在现有节点的顶部填充整个新节点集冻结的节点。我想我缺少了一些关键的东西,但是我一直在与连接玩耍,似乎没有任何效果。
我希望这可以说明问题:
任何提示都会很棒。
示例JSON数据:
{"directed": true, "multigraph": false, "graph": {}, "nodes": [{"type": "root", "time": 0, "elapsed": 0.02, "name": "redacted", "id": 0}, {"time": 0, "elapsed": 0.0, "type": "", "name": "redacted", "id": 1}, {"time": 0, "elapsed": 0.0, "type": "", "name": "redacted", "id": 2}, {"time": 0, "elapsed": 0.01, "type": "", "name": "redacted", "id": 3}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 4}, {"time": 0, "elapsed": 0.11, "type": "", "name": "redacted", "id": 5}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 6}, {"time": 0, "elapsed": 0.0, "type": "", "name": "redacted", "id": 7}, {"time": 0, "elapsed": 0.02, "type": "", "name": "redacted", "id": 8}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 9}, {"time": 0, "elapsed": 0.0, "type": "", "name": "redacted", "id": 10}, {"time": 0, "elapsed": 0.02, "type": "", "name": "redacted", "id": 11}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 12}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 13}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 14}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 15}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 16}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 17}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 18}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 19}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 20}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 21}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 22}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 23}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 24}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 25}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 26}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 27}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 28}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 29}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 30}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 31}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 32}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 33}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 34}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 35}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 36}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 37}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 38}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 39}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 40}, {"time": 0, "elapsed": 1.19, "type": "leaf", "name": "redacted", "id": 41}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 42}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 43}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 44}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 45}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 46}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 47}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 48}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 49}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 50}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 51}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 52}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 53}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 54}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 55}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 56}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 57}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 58}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 59}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 60}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 61}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 62}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 63}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 64}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 65}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 66}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 67}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 68}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 69}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 70}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 71}, {"time": 0, "elapsed": 0.0, "type": "leaf", "name": "redacted", "id": 72}], "links": [{"source": 0, "target": 1}, {"source": 1, "target": 2}, {"source": 2, "target": 3}, {"source": 2, "target": 5}, {"source": 2, "target": 7}, {"source": 3, "target": 4}, {"source": 5, "target": 6}, {"source": 7, "target": 8}, {"source": 8, "target": 9}, {"source": 8, "target": 10}, {"source": 10, "target": 11}, {"source": 11, "target": 12}, {"source": 11, "target": 13}, {"source": 11, "target": 14}, {"source": 11, "target": 15}, {"source": 11, "target": 16}, {"source": 11, "target": 17}, {"source": 11, "target": 18}, {"source": 11, "target": 19}, {"source": 11, "target": 20}, {"source": 11, "target": 21}, {"source": 11, "target": 22}, {"source": 11, "target": 23}, {"source": 11, "target": 24}, {"source": 11, "target": 25}, {"source": 11, "target": 26}, {"source": 11, "target": 27}, {"source": 11, "target": 28}, {"source": 11, "target": 29}, {"source": 11, "target": 30}, {"source": 11, "target": 31}, {"source": 11, "target": 32}, {"source": 11, "target": 33}, {"source": 11, "target": 34}, {"source": 11, "target": 35}, {"source": 11, "target": 36}, {"source": 11, "target": 37}, {"source": 11, "target": 38}, {"source": 11, "target": 39}, {"source": 11, "target": 40}, {"source": 11, "target": 41}, {"source": 11, "target": 42}, {"source": 11, "target": 43}, {"source": 11, "target": 44}, {"source": 11, "target": 45}, {"source": 11, "target": 46}, {"source": 11, "target": 47}, {"source": 11, "target": 48}, {"source": 11, "target": 49}, {"source": 11, "target": 50}, {"source": 11, "target": 51}, {"source": 11, "target": 52}, {"source": 11, "target": 53}, {"source": 11, "target": 54}, {"source": 11, "target": 55}, {"source": 11, "target": 56}, {"source": 11, "target": 57}, {"source": 11, "target": 58}, {"source": 11, "target": 59}, {"source": 11, "target": 60}, {"source": 11, "target": 61}, {"source": 11, "target": 62}, {"source": 11, "target": 63}, {"source": 11, "target": 64}, {"source": 11, "target": 65}, {"source": 11, "target": 66}, {"source": 11, "target": 67}, {"source": 11, "target": 68}, {"source": 11, "target": 69}, {"source": 11, "target": 70}, {"source": 11, "target": 71}, {"source": 11, "target": 72}]}
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Makefile Visualization</title>
<style>
html {
height: 100%;
width: 100%;
overflow: hidden;
position: relative;
}
.drawing {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: -17px;
overflow-y: scroll;
}
.link {
stroke: #777;
stroke-opacity: 0.3;
stroke-width: 1.5px;
}
.node {
cursor: pointer;
}
.node circle {
/* fill: #ccc; */
stroke: #000;
stroke-width: 1.5px;
}
.node text {
display: none;
font: 10px sans-serif;
}
.node:hover circle {
fill: #000;
}
.node:hover text {
display: inline;
}
.cell {
fill: none;
pointer-events: all;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v5.js"></script>
<script src="makevis.js"></script>
</body>
</html>
Javascript:
/* Simple function to async load JSON data */
function load_dependency_report(error, success) {
var request = new XMLHttpRequest();
request.open('GET', 'dependency_report.json', true);
request.overrideMimeType('application/json');
request.onreadystatechange = function() {
if (request.readyState == XMLHttpRequest.DONE) {
if (request.status === 200) {
if (success)
success(JSON.parse(request.responseText));
} else {
if (error)
error(request);
}
}
}
request.send();
}
/* Setup the visualization once the JSON data async loads */
load_dependency_report( function(request) { console.error(request); }, function(json) {
data = json;
console.log("JSON data loaded");
// setup visualization variables on data (didn't come in the JSON, or needs to be assigned dynamically)
for (var i = 0; i < data.nodes.length; i++) {
var n = data.nodes[i];
n.collapsing = 0;
n.collapsed = false;
}
for (var i = 0; i < data.links.length; i++) {
var l = data.links[i];
l.source = data.nodes[l.source];
l.target = data.nodes[l.target];
l.id = i;
}
/* first call to update function, only call it again when we need to change the data */
update();
});
// first get the size from the window
// if that didn't work, get it from the body
var size = {
width: window.innerWidth || document.body.clientWidth,
height: window.innerHeight || document.body.clientHeight
};
var width = size.width;
var height = size.height;
var data;
var svg = d3.select('body').append('svg').attr('viewBox', [-width/2, -height/2, width, height]);
var drawing = svg.append('g').attr('class', 'drawing');
// key data groups
var node = drawing.selectAll('.node');
var link = drawing.selectAll('.link');
// TODO: Make responsive, instead of resize on reload
// TODO: start simulation with outermost leafs collapsed
// TODO: Notice that the hover fill isn't working for some reason either
/* zoom event handling */
var zoom_control = d3.zoom().on('zoom', zoom_actions);
zoom_control(svg);
// disable double-click zoom events
svg.on('dblclick.zoom', null);
function zoom_actions() { drawing.attr('transform', d3.event.transform); }
/* setup graph simulation */
// define each force and it's properties, no data binding yet
var link_force = d3.forceLink().id(function(d) { return d.id; });
var charge_force = d3.forceManyBody();
var x_force = d3.forceX();
var y_force = d3.forceY();
//var center_force = d3.forceCenter(width / 2, height / 2);
// setup force simulation, with no attached forces, then attach 4 different ones to it
var simulation = d3.forceSimulation()
.on('tick', tick)
.force('link', link_force)
.force('charge', charge_force)
.force('x', x_force)
.force('y', y_force);
//.force('center', center_force);
// tick function for force simulation
function tick() {
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
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; });
}
/* drag event handling */
function drag_started(d) {
// reheats the simulation if the drag event is inactive
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
// fix point location to mouse pointer location
d.fx = d.x;
d.fy = d.y;
}
function drag_move(d) {
// fix point location to mouse pointer location during drag
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_ended(d) {
// reheat simulation on drag end
if (!d3.event.active) simulation.alphaTarget(0);
// un-fix point location after the drag ends
d.fx = null;
d.fy = null;
}
/* color nodes based on data attributes */
function color(d) {
/*
RED #d94701 (217,71,1)
ORANGE #fd8d3c (253,141,60)
L BLUE #6bc3d6 (107,195,214)
D BLUE #219bb5 (33,155,181)
GREY #aaaaaa (170,170,170)
*/
switch (d.type) {
case 'root':
return '#fd8d3c';
case 'leaf':
return '#aaaaaa';
default:
if (d.collapsed)
return '#219bb5';
return '#6bc3d6';
}
}
/* mouse click event handling */
function toggle_collapse(d) {
if (d3.event.defaultPrevented) return; // ignore clicks that are part of drag
console.log("click");
data.links.forEach(function(l) {
if (l.source.id == d.id) {
if (d.collapsed) {
l.target.collapsing--;
} else {
l.target.collapsing++;
}
}
});
d.collapsed = !d.collapsed;
update();
}
/* graph updates (data related) */
function update() {
console.log("update");
// Keep only visible nodes & links
var f_nodes = data.nodes.filter(function(d) { return d.collapsing == 0; });
var f_links = data.links.filter(function(d) { return d.source.collapsing === 0 && d.target.collapsing === 0; });
// Restart simulation back up on filtered links and nodes
simulation.nodes(f_nodes);
simulation.force('link').links(f_links);
simulation.restart();
// Update the links, exit any old links, enter new links
link = link.data(f_links, function (d) { return d.id; })
// link.exit().remove();
.enter().insert('line').attr('class', '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; });
// Update the nodes, exit any old nodes, enter any new nodes
node = node.data(f_nodes, function (d) { return d.id; })
// node.exit().remove();
.enter().insert('g')
.attr('class', 'node')
.on('click.nodes', toggle_collapse)
.call(d3.drag().on('start', drag_started)
.on('drag', drag_move)
.on('end', drag_ended));
var circle = node.append('circle')
.attr('r', 5)
.style('fill', color);
var label = node.append('text')
.attr('dy', '-.5em')
.attr('dx', '.35em')
.text(function(d) { return d.name });
var cell = node.append('path')
.attr('class', 'cell');
}