問題:我希望根據鏈接型別來淡化/突出顯示整個依賴鏈。
為了做到這一點,我利用了滑鼠進入事件,該事件目前存盤了所有鏈接和節點。此外,我還將淡化所有節點和鏈接,只突出顯示那些被過濾為相關節點和鏈接的節點。如果這些節點和鏈接也有need型別的連接,就需要再次檢查所有相關節點和鏈接。只要發現依賴性連接,就必須這樣做。我想不出一個合適的演算法。
例子:
為了更好地理解,我創建了一個啤酒成分的依賴關系,它看起來就像一個星星。為了這些目的,我的版本很好。BUT第二條鏈,關于汽車->輪子->輪胎->橡膠和收音機,讓我感到頭痛。無線電是一個"使用"的依賴關系,意味著它對這個鏈來說不是強制性的,不應該被強調。
預期的結果:
如果游標在car上,所有具有 "need "依賴關系的連接節點都應該被高亮顯示,其余的應該淡化。
對于那些想幫助我的人,請不要猶豫,如果有什么不清楚的地方,請提問。
。<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">/span>
< meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js框架-->
<script src="https://d3js. org/d3.v6.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->>
<script src="https://kit.fontawesome.com/39094309d6. js" crossorigin="anonymous"></script>
</head>
<style>
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
.faded {
opacity: 0.1;
transition: 0.3s不透明度。
}
.highlight {
opacity: 1;
</style>>
<body>
<svg id="svg"/span>> </svg>
<script>
var graph = {
"nodes": [
{
"id": 0,
"name": "啤酒"。
},
{
"id": 1,
"name": "water",
},
{
"id": 2,
"name": "hop"。
},
{
"id": 3,
"name": "malt",
},
{
"id": 4,
"name": "yeast",
},
{
"id": 10,
"name": "car",
},
{
"id": 11,
"name": "wheel",
},
{
"id": 12,
"name": "tires",
},
{
"id": 13,
"name": "橡膠"。
},
{
"id": 14,
"name": "radio"。
}
],
"鏈接": [
{
"source": 0,
"目標": 1,
"type": "需要"。
},
{
"來源": 0,
"目標": 2,
"type": "需要"。
},
{
"來源": 0,
"目標": 3,
"type": "需要"。
},
{
"來源": 0,
"目標": 4,
"type": "需要"。
},
{
"來源": 10,
"目標": 11,
"type": "need"。
},
{
"來源": 11,
"目標": 12,
"type": "需要"。
},
{
"來源": 12,
"目標": 13,
"型別": "需要"。
},
{
"來源": 10,
"目標": 14,
"型別": "use", "type": "use"
}
]
}
var svg = d3.select("svg"/span>)
.attr("class"/span>, "canvas")
.attr("width"/span>, window.innerWidth)
.attr("height",window.innerHeight)
.call(d3.zoom()。 on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
//append markers to svg.
svg.append("defs").append("marker")
.attr("id", "箭頭")
.attr("viewBox", "-0 -5 10 10")
.attr("refX"/span>, 8)
.attr("refY", 0)
.attr("orient", "auto")
.attr("markerWidth"/span>, 50)
.attr("markerHeight"/span>, 50)
.attr("xoverflow", " visible")
.append("svg:path")
.attr("d", "M 0,-1 L 2 ,0 L 0,1" )
.attr("fill"/span>, "black")
.style("stroke", "none")
var linksContainer = svg.append("g"/span>).attr("class"/span>, linksContainer)
var nodesContainer = svg.append("g") 。 attr("class"/span>, nodesContainer)
var force = d3.forceSimulation()
.force("link", d3.forceLink() 。 id(function (d) {
returnd.id
}).distance(80)
.force("charge", d3.forceManyBody().strength(-100)
.force("center", d3.forceCenter(window. innerWidth / 2, window.innerHeight / 2)
.force("collision", d3.forceCollide().radius(90)
initialize()
function initialize() {
link = linksContainer.selectAll(".link")
.data(graph.links)
.join("line"/span>)
.attr("class"/span>, "link")
.attr('marker-end', 'url(#arrowhead)')
.style("display" , "block" )
.style("stroke", "black")
.style("stroke-width",1)
linkPaths = linksContainer.selectAll(".linkPath")
.data(graph.links)
.join("path"/span>)
.style("pointer-events", "none")
.attr("class"/span>, "linkPath")
.attr("fill-opacity"/span>, 1)
.attr("stroke-opacity", 1)
. attr("id", function(d, i) { return "linkPath"/span> i })
.style("display"/span>, "block"/span>)
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.join("text"/span>)
.style("pointer-events"/span>, "none")
.attr("class"/span>, "linkLabel")
. attr("id", function(d, i) { return "linkLabel"/span> i })
.attr("font-size",16)
.attr("fill", "black")
.text("")
鏈接標簽
.append("textPath"/span>)
. attr('xlink: href', function (d, i) { return '#linkPath' i })
.style("text-anchor"/span>, "middle")
.style("pointer-events"/span>, "none")
.attr("startOffset"/span>, "50%")
.text(function (d) { return d.type })
node = nodesContainer.selectAll(".node"/span>)
.data(graph.nodes, d => d.id)
.join("g"/span>)
.attr("class"/span>, "node")
.call(d3.drag()
.on("start"/span>, dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.selectAll("circle")
.data(d => [d])
.join("circle"/span>)
.attr("r"/span>, 30)
.style("fill", "whitesmoke")
.on("mouseenter"/span>, mouseEnter)
.on("mouseleave"/span>, mouseLeave)
node.selectAll("text")
.data(d => [d])
.join("text"/span>)
.style("class"/span>, "icon")
.attr("font-family"/span>, "FontAwesome")
.attr("dominant-baseline"/span>, "central")
.attr("text-anchor", "middle")
.attr("font-size", 20)
.attr("fill", "black")
.attr("pointer-events"/span>, "none")
.attr("dy"/span>, "-1em")
.text(function (d) {
return d.name
})
node.append("text")
.attr("dominant-baseline"/span>, "central")
.attr("text-anchor", "middle")
.attr("font-size", 13)
.attr("fill", "black")
.attr("pointer-events"/span>, "none")
.attr("dy"/span>, "0.5em"/span>)
.text(function (d) {
returnd.id
})
力量
.nodes(graph.nodes)
.on("tick", ticked)。
迫使
.force("link")
.links(graph.links)
}
function mouseEnter(event, d) {
const selNodes = node.selectAll("circle")
const selLink = link
const selLinkLabel = linkLabels
const selText = node.selectAll("text")
const related = []
const relatedLinks = [] 。
related.push(d)
force.force('link').links() 。 forEach((link) => {
if (link.source == d || link.target == d) {
relatedLinks.push(link)
if (related.indexOf(link.source) ==-1) { related.push(link.source) }
if (related.indexOf(link.target) ==-1) { related.push(link.target) }
}
})
selNodes.classed('faded', true)
selNodes.filter((dNodes) => 相關。 indexOf(dNodes) > -1)
.classed('highlight', true)
selLink.classed('faded', true)
selLink.filter((dLink) => dLink。 source ==d || dLink.target ==d)
.classed('highlight', true)
selLinkLabel.classed('faded', true)
selLinkLabel.filter((dLinkLabel) => dLinkLabel。 source ==d || dLinkLabel.target ==d)
.classed('highlight', true)
selText.classed('faded', true)
selText.filter((dText) => 相關。 indexOf(dText) > -1)
.classed('highlight', true)
force.alphaTarget(0.0001).restart()
}
function mouseLeave(event, d) {
const selNodes = node.selectAll("circle")
const selLink = link
const selLinkLabel = linkLabels
const selText = node.selectAll("text")
selNodes.classed('faded', false)
selNodes.classed('highlight', false)
selLink.classed('faded', false)
selLink.classed('highlight', false)
selLinkLabel.classed('faded', false)
selLinkLabel.classed('highlight', false)
selText.classed('faded', false)
selText.classed('highlight', false)
force.restart()
}
function ticked() {
//>更新鏈接位置。
鏈接
.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;
});
//更新節點位置 //更新節點位置。
節點
.attr("transform", function(d) {
return "translate(" d. x ", " d.y ")"/span>。
});
linkPaths.attr('d', function (d) {
return 'M ' d.source. x ' ' d.source. y ' L '/span> d.target. x ' '/span> d.target.y。
});
linkLabels.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(event, d) {
if (!event.active) force.alphaTarget(0.3).restart()。
d.fx = d.x;
d.fy = d.y;
PosX = d.x; x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y。
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0)。
d.fx = undefined;
d.fy = undefined;
</script>>
</body>
</html>/span>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" class="snippet-box-edit snippet-box-result" frameborder="0"></iframe>
uj5u.com熱心網友回復:
有幾個問題你可以看看,以獲得淡化/高亮運行:
首先,注意到force方法改變了graph中的links陣列。
例如,你的第一個鏈接是這樣開始的:
。{
"source"。0,
"目標": 1,
"type": "需要", "型別": "需要".
}
但變成這樣:
{
"index"。0.
"source": {
"id": 0
"index": 0
"name": "啤酒": "啤酒""vx": 0.036971029563580046
"vy": 0.04369386654517388
"x": 394.1514674087123
"y": 220.18458726626062
},
"target": {
"id": 1: "id".
"index": 1
"name": "water": "water".
"vx": -0.021212609689083086
"vy": 0.01105162589441528
"x": 568.911363724937
"y": 177.07991527420614
},
"type": "需要"。
所以你需要一個遞回函式,但是如果你參考link.source,你會得到空陣列--相反,你需要參考link.source.id,因為這就是force根據上述例子更新你的圖物件的方式。
下面是一個相當冗長的遞回函式,它可以回傳給定節點ID的所有節點和鏈接,這些節點和鏈接是由給定的型別的鏈接連接起來的:
function nodesByTypeAfterForce(nodeId, sieved, type) {
//獲得節點的鏈接,根據型別。
const newLinks = graph.links.
.filter(link => link。 type == type && link.source.id == nodeId)。)
//從鏈接中獲得與nodeId相連的節點。
const newNodes = newLinks
.map(link => graph.nodes。 find(span class="hljs-params">newNode => newNode。 id === link.target.id)。)
//連接新節點和鏈接。
(sieved.links = sieved.links || []).push(...newLinks) 。
(sieved.nodes = sieved.nodes || []).push(...newNodes) 。
//遞回訪問鏈接節點,直到用盡選項。
newNodes.forEach(node => nodesByTypeAfterForce(node. id, sieved, type))。)
//回傳相關節點和鏈接的索引。
return {
nodes: sieved.nodes. map(span class="hljs-params">node => node.index) 。
links: sieved.links. map(span class="hljs-params">link => link.index)
};
}
注意該函式回傳一個index的陣列,這是force分配給每個節點和鏈接的屬性。這使得以后對淡出/高亮的過濾更加明確。
現在,在
mouseEnter中,你可以先呼叫這個函式,只回傳由某個型別的鏈接連接起來的節點,并通過d來初始化搜索:
function mouseEnter(event, d) {
//被懸停的節點的子圖。
const sieved = nodesByTypeAfterForce(d.id, {nodes: [d]}, "need")。)
//...。
}
這取代了related陣列的構造,在你的OP中,它不是與type相關的。雖然這個問題很容易解決,但你目前的mouseEnter中的另一個問題是你有這些行。
selLink
.filter((dLink) => dLink.source ==d || dLink.target ==d)
并且
selLinkLabel
.filter((dLinkLabel) => dLinkLabel。 source ==d || dLinkLabel.target ==d)
.classed('highlight', true)
這導致鏈接(和鏈接標簽)的高亮顯示到任何鏈接的節點,而不僅僅是由型別(如need)鏈接的節點。
所以,我建議你用這個代碼塊來代替(我移動了所有的行,把所有的東西都淡化到他們自己的部分):
// Only highlight from sieved
node.selectAll("clar")
.filter(node => sieved.nodes。 indexOf(node.index) > -1)
.classed('highlight', true)
鏈接
.filter(span class="hljs-params">link => sieved.links。 indexOf(link.index) > -1)
.classed('highlight', true)
鏈接標簽
.filter(span class="hljs-params">link => sieved.links。 indexOf(link.index) > -1)
.classed('highlight', true)
node.selectAll("text")
.filter(node => sieved.nodes。 indexOf(node.index) > -1)
.classed('highlight', true)
現在只根據上面nodeByTypeAfterForce函式回傳的index來突出顯示節點和鏈接。
作業實體如下,其中
nodeByTypeAfterForce只是在graph的定義之后被丟進去,唯一的其他編輯是在mouseEnter:
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">/span>
< meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js框架-->
<script src="https://d3js. org/d3.v6.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->>
<script src="https://kit.fontawesome.com/39094309d6. js" crossorigin="anonymous"></script>
</head>
<style>
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
.faded {
opacity: 0.1;
transition: 0.3s不透明度。
}
.highlight {
opacity: 1;
</style>>
<body>
<svg id="svg"/span>> </svg>
<script>
var graph = {
"nodes": [
{
"id": 0,
"name": "啤酒"。
},
{
"id": 1,
"name": "water",
},
{
"id": 2,
"name": "hop"。
},
{
"id": 3,
"name": "malt",
},
{
"id": 4,
"name": "yeast",
},
{
"id": 10,
"name": "car",
},
{
"id": 11,
"name": "wheel",
},
{
"id": 12,
"name": "tires",
},
{
"id": 13,
"name": "橡膠"。
},
{
"id": 14,
"name": "radio"。
}
],
"鏈接": [
{
"source": 0,
"目標": 1,
"type": "需要"。
},
{
"來源": 0,
"目標": 2,
"type": "需要"。
},
{
"來源": 0,
"目標": 3,
"type": "需要"。
},
{
"來源": 0,
"目標": 4,
"type": "需要"。
},
{
"來源": 10,
"目標": 11,
"type": "need"。
},
{
"來源": 11,
"目標": 12,
"type": "需要"。
},
{
"來源": 12,
"目標": 13,
"型別": "需要"。
},
{
"來源": 10,
"目標": 14,
"型別": "use", "type": "use"
}
]
}
function nodesByTypeAfterForce(nodeId、sieved、type) {
//獲得節點的鏈接,根據型別。
const newLinks = graph.links.
.filter(link => link。 type == type && link.source.id == nodeId)。)
//從鏈接中獲得與nodeId相連的節點。
const newNodes = newLinks
.map(link => graph.nodes。 find(span class="hljs-params">newNode => newNode。 id === link.target.id)。)
//連接新節點和鏈接。
(sieved.links = sieved.links || []).push(...newLinks) 。
(sieved.nodes = sieved.nodes || []).push(...newNodes) 。
//遞回訪問鏈接節點,直到用盡選項。
newNodes.forEach(node => nodesByTypeAfterForce(node. id, sieved, type))。)
//回傳相關節點和鏈接的索引。
return {
nodes: sieved.nodes. map(span class="hljs-params">node => node.index) 。
links: sieved.links. map(span class="hljs-params">link => link.index)
};
}
var svg = d3.select("svg"/span>)
.attr("class"/span>, "canvas")
.attr("width"/span>, window.innerWidth)
.attr("height",window.innerHeight)
.call(d3.zoom()。 on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
//append markers to svg.
svg.append("defs").append("marker")
.attr("id", "箭頭")
.attr("viewBox", "-0 -5 10 10")
.attr("refX"/span>, 8)
.attr("refY", 0)
.attr("orient", "auto")
.attr("markerWidth"/span>, 50)
.attr("markerHeight"/span>, 50)
.attr("xoverflow", " visible")
.append("svg:path")
.attr("d", "M 0,-1 L 2 ,0 L 0,1" )
.attr("fill"/span>, "black")
.style("stroke", "none")
var linksContainer = svg.append("g"/span>).attr("class"/span>, linksContainer)
var nodesContainer = svg.append("g") 。 attr("class"/span>, nodesContainer)
var force = d3.forceSimulation()
.force("link", d3.forceLink() 。 id(function (d) {
returnd.id
}).distance(80)
.force("charge", d3.forceManyBody().strength(-100)
.force("center", d3.forceCenter(window. innerWidth / 2, window.innerHeight / 2)
.force("collision", d3.forceCollide().radius(90)
initialize()
function initialize() {
link = linksContainer.selectAll(".link")
.data(graph.links)
.join("line"/span>)
.attr("class"/span>, "link")
.attr('marker-end', 'url(#arrowhead)')
.style("display" , "block" )
.style("stroke", "black")
.style("stroke-width",1)
linkPaths = linksContainer.selectAll(".linkPath")
.data(graph.links)
.join("path"/span>)
.style("pointer-events", "none")
.attr("class"/span>, "linkPath")
.attr("fill-opacity"/span>, 1)
.attr("stroke-opacity", 1)
. attr("id", function(d, i) { return "linkPath"/span> i })
.style("display"/span>, "block"/span>)
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.join("text"/span>)
.style("pointer-events"/span>, "none")
.attr("class"/span>, "linkLabel")
. attr("id", function(d, i) { return "linkLabel"/span> i })
.attr("font-size",16)
.attr("fill", "black")
.text("")
鏈接標簽
.append("textPath"/span>)
. attr('xlink: href', function (d, i) { return '#linkPath' i })
.style("text-anchor"/span>, "middle")
.style("pointer-events"/span>, "none")
.attr("startOffset"/span>, "50%")
.text(function (d) { return d.type })
node = nodesContainer.selectAll(".node"/span>)
.data(graph.nodes, d => d.id)
.join("g"/span>)
.attr("class"/span>, "node")
.call(d3.drag()
.on("start"/span>, dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.selectAll("circle")
.data(d => [d])
.join("circle"/span>)
.attr("r"/span>, 30)
.style("fill", "whitesmoke")
.on("mouseenter"/span>, mouseEnter)
.on("mouseleave"/span>, mouseLeave)
node.selectAll("text")
.data(d => [d])
.join("text"/span>)
.style("class"/span>, "icon")
.attr("font-family"/span>, "FontAwesome")
.attr("dominant-baseline"/span>, "central")
.attr("text-anchor", "middle")
.attr("font-size", 20)
.attr("fill", "black")
.attr("pointer-events"/span>, "none")
.attr("dy"/span>, "-1em")
.text(function (d) {
return d.name
})
node.append("text")
.attr("dominant-baseline"/span>, "central")
.attr("text-anchor", "middle")
.attr("font-size", 13)
.attr("fill", "black")
.attr("pointer-events"/span>, "none")
.attr("dy"/span>, "0.5em"/span>)
.text(function (d) {
returnd.id
})
力量
.nodes(graph.nodes)
.on("tick", ticked)。
迫使
.force("link")
.links(graph.links)
}
function mouseEnter(event, d) {
//被懸停的節點的子圖。
const sieved = nodesByTypeAfterForce(d.id, {nodes: [d]}, "need")。)
//淡化一切。
node.selectAll("circle").classed('faded',true)
node.selectAll("circle").classed('highlight',false)
link.classed('faded', true)
link.classed('highlight', false)
linkLabels.classed('faded', true)
linkLabels.classed('highlight', false)
node.selectAll("text").classed('faded',true)
node.selectAll("text").classed('highlight', false)
//僅從篩分中突出顯示。
node.selectAll("circle")
.filter(span class="hljs-params">node => sieved.nodes。 indexOf(node.index) > -1)
.classed('highlight', true)
鏈接
.filter(span class="hljs-params">link => sieved.links。 indexOf(link.index) > -1)
.classed('highlight', true)
鏈接標簽
.filter(span class="hljs-params">link => sieved.links。 indexOf(link.index) > -1)
.classed('highlight', true)
node.selectAll("text")
.filter(node => sieved.nodes。 indexOf(node.index) > -1)
.classed('highlight', true)
force.alphaTarget(0.0001).restart()
}
function mouseLeave(event, d) {
const selNodes = node.selectAll("circle")
const selLink = link
const selLinkLabel = linkLabels
const selText = node.selectAll("text")
selNodes.classed('faded', false)
selNodes.classed('highlight', false)
selLink.classed('faded', false)
selLink.classed('highlight', false)
selLinkLabel.classed('faded', false)
selLinkLabel.classed('highlight', false)
selText.classed('faded', false)
selText.classed('highlight', false)
force.restart()
}
function ticked() {
//>更新鏈接位置。
鏈接
.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;
});
//更新節點位置 //更新節點位置。
節點
.attr("transform", function(d) {
return "translate(" d. x ", " d.y ")"/span>。
});
linkPaths.attr('d', function (d) {
return 'M ' d.source. x ' ' d.source. y ' L '/span> d.target. x ' '/span> d.target.y。
});
linkLabels.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(event, d) {
if (!event.active) force.alphaTarget(0.3).restart()。
d.fx = d.x;
d.fy = d.y;
PosX = d.x; x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y。
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0)。
d.fx = undefined;
d.fy = undefined;
</script>>
</body>
</html>/span>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" class="snippet-box-edit snippet-box-result" frameborder="0"></iframe>
如果你像這樣定義一個鏈接,例如用 "Yeast "來定義,你會遇到堆疊溢位:
"link": [ // Yeast need yeast ?
{
"來源": 4,
"目標": 4,
"type": "需要"。
},
...
]
所以在nodesByTypeAfterForce中需要一些額外的邏輯,以適應自我參考的鏈接。
uj5u.com熱心網友回復:
使用遞回(getNeedChain呼叫自己直到完成):
const getNeedChain = id => {
const links = graph.links.filter(l => l。 source === id && l.type === "need")。)
const nodes = links.map(l => graph.nodes. find(span class="hljs-params">n => n.id == l.target)) 。
const linked = nodes.reduce((c, n) => [. ...c, ...getNeedChain(n.id)], [])。)
return [...nodes, ... linked];
}
console. log('Beer needs: ', getNeedChain(0)); // Returns 4 nodes.
console。 log('Car needs: ', getNeedChain(10)); //回傳 3 個節點。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/320663.html
標籤:
