我现在通常是d3和Web开发。
我正在使用d3库创建图形。我试图确保每当用户将鼠标悬停在某个节点上时,其直接父级和子级的不透明度应保持不变,而其余节点的不透明度应降低。
我的目标是通过使下面写的文字消失而不是我所停留的文字的一部分来实现我的目标。
这是我的JavaScript代码:
// setting up the canvas size :)
var width = 960,
height = 500;
// initialization
var svg = d3.select("div").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "blueLine"); // the graph invisible thing :)
var force = d3.layout.force()
.gravity(0) // atom's cohesiveness / elasticity of imgs :)
.distance(150) // how far the lines ---> arrows :)
.charge(-50) // meta state transition excitement
.linkDistance(140)
//.friction(0.55) // similar to charge for quick reset :)
.size([width, height]); // degree of freedom to the canvas
// exception handling
d3.json("graph.json", function(error, json) {
if (error) throw error;
// Restart the force layout
force
.nodes(json.nodes)
.links(json.links)
.start();
// Build the link
var link = svg.selectAll(".links")
.data(json.links)
.enter().append("line")
.attr("class", "lol")
.style("stroke-width", "2")
.attr("stroke", function(d){
return linkColor(d.colorCode);})
.each(function(d) {
var color = linkColor(d.colorCode);
d3.select(this).attr("marker-end", marker(color));
});
function marker(color) {
svg.append("svg:marker")
.attr("id", color.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
return "url(" + color + ")";
};
// this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3
// create a node
var node = svg.selectAll(".nodes")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag)
.on("mouseover", fade(.2))
.on("mouseout", fade(1));;
// Define the div for the tooltip
var div = d3.select("body").append("pre")
.attr("class", "tooltip")
.style("opacity", 0);
// Append custom images
node.append("svg:image")
.attr("xlink:href", function(d) { return d.img;}) // update the node with the image
.attr("x", function(d) { return -5;}) // how far is the image from the link??
.attr("y", function(d) { return -25;}) // --- same ---
.attr("height", 55) // size
.attr("width", 55);
node.append("text")
.attr("class", "labelText")
.attr("x", function(d) { return -5;})
.attr("y", function(d) { return 48;})
.text(function(d) { return d.name });
force.on("tick", function() {
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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
force.stop();
});
function linkColor(linkCode) {
switch (linkCode)
{
case 'ctoc':
return '#0000FF';//blue
break;
case 'ctof':
return '#00afaa';//green
break;
case 'ftoc':
return '#fab800';//yellow
break;
case 'ftof':
return '#7F007F';//purple
break;
default:
return '#0950D0';//generic blue
break;
}
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.id + "," + d.target.id] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
// fade nodes on hover
function fade(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
// also style link accordingly
link.style("stroke-opacity", function(o) {
return o[0] === d || o[2] === d ? 1 : opacity;
});
};
}
});
css:
.node text {
font-size: 1rem;
text-decoration: underline;
fill: #aeb4bf;
font-weight: 700;
text-anchor: end;
alignment-baseline: central;
pointer-events: none;
}
.node:not(:hover) .nodetext {
display: none;
}
pre.tooltip {
position: absolute;
text-align: left;
width: auto;
height: auto;
padding: 5px;
font: 14px "Helvetica","Arial",sans-serif bold;
background: #273142;
border: 0;
border-radius: 8px;
cursor: pointer!important;
pointer-events: none;
color: #aeb4bf;
}
和我的json文件:
{
"nodes": [
{"x": 100, "y": 100, "name": "A", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 0},
{"x": 250, "y": 100, "name": "B", "img":"https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 1},
{"x": 400, "y": 100, "name": "C", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id": 2},
{"x": 550, "y": 200, "name": "D", "img":"https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 3},
{"x": 700, "y": 200, "name": "E", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 4},
{"x": 100, "y": 300, "name": "F", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 5},
{"x": 250, "y": 300, "name": "G", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 6},
{"x": 400, "y": 300, "name": "H", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id": 7}
],
"links": [
{"source": 0, "target": 1, "colorCode" : "ctof"},
{"source": 1, "target": 2, "colorCode" : "ftoc"},
{"source": 2, "target": 3, "colorCode" : "ctof"},
{"source": 3, "target": 4, "colorCode" : "ftoc"},
{"source": 5, "target": 6, "colorCode" : "ctof"},
{"source": 6, "target": 7, "colorCode" : "ftoc"},
{"source": 7, "target": 3, "colorCode" : "ctof"}
]
}
我不知道我要去哪里。我需要实现两件事:1.如果我将鼠标悬停在X和X上,则X的直接父代和子代应保持不褪色。2.与X没有直接关系的其他节点应该像其他链接一样淡出。目前,没有节点消失。
我对代码进行了研究,发现它说所有节点都相互连接,所以我的isConnected()
是元凶。我仍然对链接一无所知。
请帮助我。
答案 0 :(得分:2)
两个要解决的问题
对于您的节点,由于它们是图像文件,因此需要设置其“不透明度”,而不是笔触/填充不透明度。
node.style("opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
对于链接,假设名称属性是唯一的,则应将链接的源和目标与所选节点的名称匹配。
link.style("stroke-opacity", function(o) {
return o.source.name === d.name || o.target.name === d.name ? 1 : opacity;
});
答案 1 :(得分:1)
@TomShanley答案的补充内容
如果您不熟悉d3,为什么要使用d3v3?目前我们在d3v5上,API有了很大的改进。
该程序并没有立即可用,因为在确定linkedByIndex
时它会抱怨links
不存在。应该是json.links
。
无需在break
中将return
放在linkColor
之后。
您搜索类svg.selectAll(".nodes")
的元素,但是使用.attr("class", "node")
创建元素。如果您要正确使用enter-exit-update,则此方法将无效。链接相同:搜索类links
,但添加类为lol
的元素。
您的标记不是唯一的,不需要使用each
添加marker-end
。
也许最好根据颜色创建一组标记,然后引用它们。
在原始代码中,您有多个带有相同id
的标签。 HTML中的id
应该是唯一的。
// Build the link
var link = svg.selectAll(".lol")
.data(json.links)
.enter().append("line")
.attr("class", "lol")
.style("stroke-width", "2")
.attr("stroke", function(d){
return linkColor(d.colorCode);})
.attr("marker-end", function(d, i){
return marker(i, linkColor(d.colorCode));} );
function marker(i, color) {
var markId = "#marker"+i;
svg.append("svg:marker")
.attr("id", markId.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
return "url(" + markId + ")";
};
编辑:唯一标记,链接是一条边到另一边的路径
我将代码修改为:
defs
标记中的每种颜色的这是完整的代码
var width = 960,
height = 500;
// initialization
var svg = d3.select("div").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "blueLine"); // the graph invisible thing :)
var svgDefs = svg.append("defs");
var force = d3.layout.force()
.gravity(0) // atom's cohesiveness / elasticity of imgs :)
.distance(150) // how far the lines ---> arrows :)
.charge(-50) // meta state transition excitement
.linkDistance(140)
//.friction(0.55) // similar to charge for quick reset :)
.size([width, height]); // degree of freedom to the canvas
// exception handling
d3.json("/fade-links.json", function(error, json) {
if (error) throw error;
var imageSize = { width:55, height:55 };
// Restart the force layout
force
.nodes(json.nodes)
.links(json.links)
.start();
var markersDone = {};
// Build the link
var link = svg.selectAll(".lol")
.data(json.links)
.enter().append("path")
.attr("class", "lol")
.style("stroke-width", "2")
.attr("stroke", function(d){
return linkColor(d.colorCode);})
.attr("marker-end", function(d){
return marker(linkColor(d.colorCode));} );
function marker(color) {
var markerId = markersDone[color];
if (!markerId) {
markerId = color;
markersDone[color] = markerId;
svgDefs.append("svg:marker")
.attr("id", color.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
}
return "url(" + markerId + ")";
};
// this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3
// create a node
var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag)
.on("mouseover", fade(.2))
.on("mouseout", fade(1));
// Define the div for the tooltip
var div = d3.select("body").append("pre")
.attr("class", "tooltip")
.style("opacity", 0);
// Append custom images
node.append("svg:image")
.attr("xlink:href", function(d) { return d.img;}) // update the node with the image
.attr("x", function(d) { return -imageSize.width*0.5;}) // how far is the image from the link??
.attr("y", function(d) { return -imageSize.height*0.5;}) // --- same ---
.attr("height", imageSize.width)
.attr("width", imageSize.height);
node.append("text")
.attr("class", "labelText")
.attr("x", function(d) { return 0;})
.attr("y", function(d) { return imageSize.height*0.75;})
.text(function(d) { return d.name });
force.on("tick", function() {
// use trick described by Gerardo to only draw link from image border to border: https://stackoverflow.com/q/51399062/9938317
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
var angle = Math.atan2(dy, dx);
var radius = imageSize.width*0.5;
var offsetX = radius * Math.cos(angle);
var offsetY = radius * Math.sin(angle);
return ( `M${d.source.x + offsetX},${d.source.y + offsetY}L${d.target.x - offsetX},${d.target.y - offsetY}`);
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
force.stop();
});
function linkColor(linkCode) {
switch (linkCode)
{
case 'ctoc': return '#0000FF';//blue
case 'ctof': return '#00afaa';//green
case 'ftoc': return '#fab800';//yellow
case 'ftof': return '#7F007F';//purple
}
return '#0950D0';//generic blue
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
json.links.forEach(function(d) {
linkedByIndex[d.source.id + "," + d.target.id] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
// fade nodes on hover
function fade(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("opacity", function(o) {
return isConnected(d, o) ? 1 : opacity;
});
// also style link accordingly
link.style("opacity", function(o) {
return o.source.name === d.name || o.target.name === d.name ? 1 : opacity;
});
};
}
});