javascript - 动态过滤 D3 有向图的节点
问题描述
我正在使用D3
对我来说新的库,D3 根据图形详细信息可视化有向图。由于我拥有大量数据,因此很难在一页上描绘所有内容。
我正在尝试通过添加下拉选项来过滤具有其向内和向外依赖关系的节点,但我被卡住了,不知道如何继续。
以下是我正在努力实现的目标。例如,Beta 节点依赖于 gamma,但同样 Alpha 和 Delta 依赖于 Beta。下图更多地关注 Beta 节点及其内部和外部依赖关系。
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.node {}
.link {
stroke: #999;
stroke-opacity: .6;
stroke-width: 1px;
}
svg {
border: 1px solid black
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<select name="cars" id="cars">
<option value="volvo">All</option>
<option value="volvo">Alpha</option>
<option value="saab">Beta</option>
<option value="volvo">Charlie</option>
<option value="saab">Delta</option>
<option value="audi">Echo</option>
<option value="opel">Foxtrot</option>
<option value="audi">Gamma</option>
<option value="opel">Hotel</option>
</select>
<br><br>
<svg width="400" height="200"></svg>
<script>
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
svg.append('defs').append('marker')
.attrs({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 13,
'refY': 0,
'orient': 'auto',
'markerWidth': 13,
'markerHeight': 13,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100).strength(1))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var graph = {
"nodes": [{
"name": "Alpha",
"label": "Person",
"id": 1
},
{
"name": "Beta",
"label": "Person",
"id": 2
},
{
"name": "Gamma",
"label": "Database",
"id": 3
},
{
"name": "Delta",
"label": "Database",
"id": 4
},
{
"name": "Echo",
"label": "Person",
"id": 5
},
{
"name": "Foxtrot",
"label": "Person",
"id": 6
},
{
"name": "Golf",
"label": "Database",
"id": 7
},
{
"name": "Hotel",
"label": "Database",
"id": 8
}
],
"links": [{
"source": 1,
"target": 2,
"type": "KNOWS",
"since": 2010
},
{
"source": 1,
"target": 3,
"type": "FOUNDED"
},
{
"source": 2,
"target": 3,
"type": "WORKS_ON"
},
{
"source": 3,
"target": 4,
"type": "IS_A"
},
{
"source": 4,
"target": 2,
"type": "KNOWS",
"since": 2010
},
{
"source": 5,
"target": 6,
"type": "FOUNDED"
},
{
"source": 3,
"target": 4,
"type": "WORKS_ON"
},
{
"source": 1,
"target": 4,
"type": "IS_A"
}
]
};
update(graph.links, graph.nodes);
function update(links, nodes) {
link = svg.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)')
link.append("title")
.text(function(d) {
return d.type;
});
edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attrs({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'id': function(d, i) {
return 'edgepath' + i
}
})
.style("pointer-events", "none");
edgelabels = svg.selectAll(".edgelabel")
.data(links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'edgelabel',
'id': function(d, i) {
return 'edgelabel' + i
},
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function(d, i) {
return '#edgepath' + i
})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function(d) {
return d.type
});
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
//.on("end", dragended)
);
node.append("circle")
.attr("r", 5)
.style("fill", function(d, i) {
return colors(i);
})
node.append("title")
.text(function(d) {
return d.id;
});
node.append("text")
.attr("dy", -3)
.text(function(d) {
return d.name;
});
simulation.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
}
function ticked() {
link
.attr("x1", function(d) {
checkBounds(d.source);
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
checkBounds(d.target);
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("transform", function(d) {
checkBounds(d);
return "translate(" + d.x + ", " + d.y + ")";
});
edgepaths.attr('d', function(d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
edgelabels.attr('transform', function(d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
} else {
return 'rotate(0)';
}
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function checkBounds(d) {
if (d.x < 0) d.x = 0;
if (d.x > (width - 20)) d.x = width - 20;
if (d.y < 0) d.y = 0;
if (d.y > (height - 20)) d.y = height - 20;
}
</script>
</body>
</html>
解决方案
我对您的代码进行了细微更改,因为选择项的值似乎不正确;我让它适用于 Beta,你需要修复其余的值才能让一切正常工作。
var graph = {
"nodes": [{
"name": "Alpha",
"label": "Person",
"id": 1
},
{
"name": "Beta",
"label": "Person",
"id": 2
},
{
"name": "Gamma",
"label": "Database",
"id": 3
},
{
"name": "Delta",
"label": "Database",
"id": 4
},
{
"name": "Echo",
"label": "Person",
"id": 5
},
{
"name": "Foxtrot",
"label": "Person",
"id": 6
},
{
"name": "Golf",
"label": "Database",
"id": 7
},
{
"name": "Hotel",
"label": "Database",
"id": 8
}
],
"links": [{
"source": 1,
"target": 2,
"type": "KNOWS",
"since": 2010
},
{
"source": 1,
"target": 3,
"type": "FOUNDED"
},
{
"source": 2,
"target": 3,
"type": "WORKS_ON"
},
{
"source": 3,
"target": 4,
"type": "IS_A"
},
{
"source": 4,
"target": 2,
"type": "KNOWS",
"since": 2010
},
{
"source": 5,
"target": 6,
"type": "FOUNDED"
},
{
"source": 3,
"target": 4,
"type": "WORKS_ON"
},
{
"source": 1,
"target": 4,
"type": "IS_A"
}
]
};
// here is what you need to filter on
document.getElementById("cars").addEventListener("change", event => {
var val = parseInt(event.target.value, 10);
var links = graph.links.filter(link => link.source.id === val || link.target.id === val);
document.getElementsByTagName("svg")[0].innerHTML = "";
if (links.length === 0) {
update([], graph.nodes.filter(n => n.id === val));
} else {
var selectedNodes = Object.keys(links.reduce((acc, curr) => {
acc[curr.source.id] = true;
acc[curr.target.id] = true;
return acc;
}, {}));
var nodes = graph.nodes.filter(n => selectedNodes.indexOf("" + n.id) !== -1);
update(links, nodes);
}
});
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
var simulation = null;
update(graph.links, graph.nodes);
function update(links, nodes) {
svg.append('defs').append('marker')
.attrs({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 13,
'refY': 0,
'orient': 'auto',
'markerWidth': 13,
'markerHeight': 13,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100).strength(1))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
link = svg.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)')
link.append("title")
.text(function(d) {
return d.type;
});
edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attrs({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'id': function(d, i) {
return 'edgepath' + i
}
})
.style("pointer-events", "none");
edgelabels = svg.selectAll(".edgelabel")
.data(links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'edgelabel',
'id': function(d, i) {
return 'edgelabel' + i
},
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function(d, i) {
return '#edgepath' + i
})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function(d) {
return d.type
});
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
//.on("end", dragended)
);
node.append("circle")
.attr("r", 5)
.style("fill", function(d, i) {
return colors(i);
})
node.append("title")
.text(function(d) {
return d.id;
});
node.append("text")
.attr("dy", -3)
.text(function(d) {
return d.name;
});
simulation.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
}
function ticked() {
link
.attr("x1", function(d) {
checkBounds(d.source);
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
checkBounds(d.target);
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("transform", function(d) {
checkBounds(d);
return "translate(" + d.x + ", " + d.y + ")";
});
edgepaths.attr('d', function(d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
edgelabels.attr('transform', function(d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
} else {
return 'rotate(0)';
}
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function checkBounds(d) {
if (d.x < 0) d.x = 0;
if (d.x > (width - 20)) d.x = width - 20;
if (d.y < 0) d.y = 0;
if (d.y > (height - 20)) d.y = height - 20;
}
.node {}
.link {
stroke: #999;
stroke-opacity: .6;
stroke-width: 1px;
}
svg {
border: 1px solid black
}
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
</head>
<body>
<select name="cars" id="cars">
<option value="volvo">All</option>
<option value="volvo">Alpha</option>
<option value="2">Beta</option>
<option value="volvo">Charlie</option>
<option value="saab">Delta</option>
<option value="audi">Echo</option>
<option value="opel">Foxtrot</option>
<option value="audi">Gamma</option>
<option value="opel">Hotel</option>
</select>
<br><br>
<svg width="400" height="200"></svg>
</body>
</html>
推荐阅读
- aspnetboilerplate - 升级到 3.6.2 后 JSON 序列化程序不工作
- php - 限制对 PHP 上的管理按钮的访问
- c# - 如何执行取决于策略的多项操作?
- spring-boot - 收到消息 MessageDeliveryException: Dispatcher 没有订阅者时
- laravel-5.5 - Laravel 类型错误:函数参数太少
- node.js - nodejs fs.writeFile不在Windows上调用回调
- javascript - 为什么在控制台中交换 == 的参数会改变输出?
- python - 将 ffprobe 与 Django 一起使用时出错 - Python2
- ios - MFMailComposeViewController 返回和发送按钮不起作用
- node.js - 如何在微服务/事件驱动架构中处理 HTTP 请求?