- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
问题:我想根据链接类型淡化/突出显示整个依赖链。
为此,我使用了 mouseEnter 事件,该事件当前存储所有链接和节点。此外,我淡化所有节点和链接,只突出显示那些被过滤为相关节点和链接的节点。它需要再次检查所有相关的节点和链接,如果它们也有来自 need
类型的连接。只要找到依赖关系就必须这样做。我想不出合适的算法。
示例:
为了更好地理解,我创建了一个啤酒成分依赖项,它看起来像一个星星。出于这些目的,我的版本很好。 但是 第二条链,关于汽车 -> 车轮 -> 轮胎 -> 橡胶和 radio 让我头疼。 radio 是一个“use
”依赖项,这意味着它对链不是强制性的,不应该被突出显示。
预期结果:
如果光标在 car
上,所有具有“需要”依赖关系的连接节点都应该突出显示,其余的应该淡出。
对于那些想帮助我的人,如果有任何不清楚的地方,请不要犹豫。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js framework -->
<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 opacity;
}
.highlight {
opacity: 1;
}
</style>
<body>
<svg id="svg"></svg>
<script>
var graph = {
"nodes": [
{
"id": 0,
"name": "beer",
},
{
"id": 1,
"name": "water",
},
{
"id": 2,
"name": "hop",
},
{
"id": 3,
"name": "malt",
},
{
"id": 4,
"name": "yeast",
},
{
"id": 10,
"name": "car",
},
{
"id": 11,
"name": "wheels",
},
{
"id": 12,
"name": "tires",
},
{
"id": 13,
"name": "rubber",
},
{
"id": 14,
"name": "radio",
}
],
"links": [
{
"source": 0,
"target": 1,
"type": "need"
},
{
"source": 0,
"target": 2,
"type": "need"
},
{
"source": 0,
"target": 3,
"type": "need"
},
{
"source": 0,
"target": 4,
"type": "need"
},
{
"source": 10,
"target": 11,
"type": "need"
},
{
"source": 11,
"target": 12,
"type": "need"
},
{
"source": 12,
"target": 13,
"type": "need"
},
{
"source": 10,
"target": 14,
"type": "use"
}
]
}
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", 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", "arrowhead")
.attr("viewBox", "-0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("orient", "auto")
.attr("markerWidth", 50)
.attr("markerHeight", 50)
.attr("xoverflow", "visible")
.append("svg:path")
.attr("d", "M 0,-1 L 2 ,0 L 0,1")
.attr("fill", "black")
.style("stroke", "none")
var linksContainer = svg.append("g").attr("class", linksContainer)
var nodesContainer = svg.append("g").attr("class", nodesContainer)
var force = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.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")
.attr("class", "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")
.style("pointer-events", "none")
.attr("class", "linkPath")
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1)
.attr("id", function (d, i) { return "linkPath" + i })
.style("display", "block")
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.join("text")
.style("pointer-events", "none")
.attr("class", "linkLabel")
.attr("id", function (d, i) { return "linkLabel" + i })
.attr("font-size", 16)
.attr("fill", "black")
.text("")
linkLabels
.append("textPath")
.attr('xlink:href', function (d, i) { return '#linkPath' + i })
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) { return d.type })
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
node.selectAll("text")
.data(d => [d])
.join("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 20)
.attr("fill", "black")
.attr("pointer-events", "none")
.attr("dy", "-1em")
.text(function (d) {
return d.name
})
node.append("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 13)
.attr("fill", "black")
.attr("pointer-events", "none")
.attr("dy", "0.5em")
.text(function (d) {
return d.id
})
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.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) => related.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) => related.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() {
// update link positions
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 node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
linkPaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + 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
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>
最佳答案
要让淡入淡出/高光运行,您可以考虑几个问题:
首先,注意 force
方法改变了 links
graph
中的数组.
例如,您的第一个链接是这样开始的:
{
"source": 0,
"target": 1,
"type": "need"
}
但变成这样:
{
"index": 0
"source": {
"id": 0
"index": 0
"name": "beer"
"vx": 0.036971029563580046
"vy": 0.04369386654517388
"x": 394.1514674087123
"y": 220.18458726626062
},
"target": {
"id": 1
"index": 1
"name": "water"
"vx": -0.021212609689083086
"vy": 0.01105162589441528
"x": 568.911363724937
"y": 177.07991527420614
},
"type": "need"
}
因此您需要一个递归函数,但如果您引用 link.source
,您将得到空数组- 相反,您需要引用 link.source.id
因为这就是force
已根据上述示例更新了您的图形对象。
这是一个相当冗长的递归函数,它返回给定节点 ID 的所有节点和链接,这些节点和链接由给定 type
的链接链接。 :
function nodesByTypeAfterForce(nodeId, sieved, type) {
// get the links for the node per the type
const newLinks = graph.links
.filter(link => link.type === type && link.source.id === nodeId);
// get the linked nodes to nodeId from the links
const newNodes = newLinks
.map(link => graph.nodes.find(newNode => newNode.id === link.target.id));
// concatenate new nodes and links
(sieved.links = sieved.links || []).push(...newLinks);
(sieved.nodes = sieved.nodes || []).push(...newNodes);
// recursively visit linked nodes until exhausted options
newNodes.forEach(node => nodesByTypeAfterForce(node.id, sieved, type));
// return indices relevant nodes and links
return {
nodes: sieved.nodes.map(node => node.index),
links: sieved.links.map(link => link.index)
};
}
注意该函数返回一个 index
的数组这是一个属性 force
分配给每个节点和链接。这使得稍后对淡入淡出/突出显示的过滤更加明确。
现在,在 mouseEnter
你可以通过调用这个函数来开始,只返回由某个 type
的链接链接在一起的节点。并通过 d
初始化搜索:
function mouseEnter(event, d) {
// sub graph for the hovered node
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)
这导致链接(和链接标签)突出显示到任何链接节点,而不仅仅是由 type
链接的节点。 (例如 need
)。
所以,我建议你用这个代码块替换(我移动了所有行以将所有内容淡化到它们自己的部分):
// only highlight from sieved
node.selectAll("circle")
.filter(node => sieved.nodes.indexOf(node.index) > -1)
.classed('highlight', true)
link
.filter(link => sieved.links.indexOf(link.index) > -1)
.classed('highlight', true)
linkLabels
.filter(link => sieved.links.indexOf(link.index) > -1)
.classed('highlight', true)
node.selectAll("text")
.filter(node => sieved.nodes.indexOf(node.index) > -1)
.classed('highlight', true)
现在根据 index
仅突出显示节点和链接从 nodeByTypeAfterForce
返回上面的功能。
工作示例在下面,其中 nodeByTypeAfterForce
在 graph
的定义之后被丢弃唯一的其他编辑在 mouseEnter
中:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js framework -->
<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 opacity;
}
.highlight {
opacity: 1;
}
</style>
<body>
<svg id="svg"></svg>
<script>
var graph = {
"nodes": [
{
"id": 0,
"name": "beer",
},
{
"id": 1,
"name": "water",
},
{
"id": 2,
"name": "hop",
},
{
"id": 3,
"name": "malt",
},
{
"id": 4,
"name": "yeast",
},
{
"id": 10,
"name": "car",
},
{
"id": 11,
"name": "wheels",
},
{
"id": 12,
"name": "tires",
},
{
"id": 13,
"name": "rubber",
},
{
"id": 14,
"name": "radio",
}
],
"links": [
{
"source": 0,
"target": 1,
"type": "need"
},
{
"source": 0,
"target": 2,
"type": "need"
},
{
"source": 0,
"target": 3,
"type": "need"
},
{
"source": 0,
"target": 4,
"type": "need"
},
{
"source": 10,
"target": 11,
"type": "need"
},
{
"source": 11,
"target": 12,
"type": "need"
},
{
"source": 12,
"target": 13,
"type": "need"
},
{
"source": 10,
"target": 14,
"type": "use"
}
]
}
function nodesByTypeAfterForce(nodeId, sieved, type) {
// get the links for the node per the type
const newLinks = graph.links
.filter(link => link.type === type && link.source.id === nodeId);
// get the linked nodes to nodeId from the links
const newNodes = newLinks
.map(link => graph.nodes.find(newNode => newNode.id === link.target.id));
// concatenate new nodes and links
(sieved.links = sieved.links || []).push(...newLinks);
(sieved.nodes = sieved.nodes || []).push(...newNodes);
// recursively visit linked nodes until exhausted options
newNodes.forEach(node => nodesByTypeAfterForce(node.id, sieved, type));
// return indices relevant nodes and links
return {
nodes: sieved.nodes.map(node => node.index),
links: sieved.links.map(link => link.index)
};
}
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", 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", "arrowhead")
.attr("viewBox", "-0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("orient", "auto")
.attr("markerWidth", 50)
.attr("markerHeight", 50)
.attr("xoverflow", "visible")
.append("svg:path")
.attr("d", "M 0,-1 L 2 ,0 L 0,1")
.attr("fill", "black")
.style("stroke", "none")
var linksContainer = svg.append("g").attr("class", linksContainer)
var nodesContainer = svg.append("g").attr("class", nodesContainer)
var force = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.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")
.attr("class", "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")
.style("pointer-events", "none")
.attr("class", "linkPath")
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1)
.attr("id", function (d, i) { return "linkPath" + i })
.style("display", "block")
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.join("text")
.style("pointer-events", "none")
.attr("class", "linkLabel")
.attr("id", function (d, i) { return "linkLabel" + i })
.attr("font-size", 16)
.attr("fill", "black")
.text("")
linkLabels
.append("textPath")
.attr('xlink:href', function (d, i) { return '#linkPath' + i })
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) { return d.type })
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
node.selectAll("text")
.data(d => [d])
.join("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 20)
.attr("fill", "black")
.attr("pointer-events", "none")
.attr("dy", "-1em")
.text(function (d) {
return d.name
})
node.append("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 13)
.attr("fill", "black")
.attr("pointer-events", "none")
.attr("dy", "0.5em")
.text(function (d) {
return d.id
})
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.force("link")
.links(graph.links)
}
function mouseEnter(event, d) {
// sub graph for the hovered node
const sieved = nodesByTypeAfterForce(d.id, {nodes: [d]}, "need");
// fade everything
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)
// only highlight from sieved
node.selectAll("circle")
.filter(node => sieved.nodes.indexOf(node.index) > -1)
.classed('highlight', true)
link
.filter(link => sieved.links.indexOf(link.index) > -1)
.classed('highlight', true)
linkLabels
.filter(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() {
// update link positions
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 node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
linkPaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + 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
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>
如果您定义这样的链接,您将遇到堆栈溢出,例如用“酵母”:
"links": [ // Yeast need yeast ??
{
"source": 4,
"target": 4,
"type": "need"
},
...
]
因此 nodesByTypeAfterForce
中需要一些额外的逻辑以适应自引用链接。
关于javascript - D3.js 高亮相关节点/链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68939086/
在这个 fiddle 上,函数完美地完成了工作,但我需要稍微调整连接。 该点击甚至需要是父 div 的 addClass(深色/浅色)(true 或 false)。 如果是深色,则添加 Bright
我正在使用 Windows.UI.ViewManagement.UISettings 来获取系统强调色,但该类似乎没有任何亮/暗模式的方法或属性。我找不到此功能的文档,我该如何检测? PS:我正在制作
在使用 iOS 13 在明暗模式之间切换时,我遇到了显示键盘的异常行为。 该 View 有一个 inputAccessoryView 供用户输入消息。当然后在键盘显示的情况下切换亮/暗模式时,亮/暗模
我正在开发一个支持多个主题的 iOS 应用程序。有些主题使用深色背景,有些使用浅色。一些默认图标在深色/浅色背景中不可见。我在 xcassets 中看到一个选项,可以为不同的颜色模式添加图像。我的目标
一些上下文:Sciter (纯 win32 应用程序)已经能够呈现类似 UWP 的 UI: 在深色模式下: 在灯光模式下: Windows 10.1803 在设置小程序中引入深色/浅色开关 as se
我是一名优秀的程序员,十分优秀!