[英]Fade links and nodes that are not immediately connected to the node hovered on in a d3 graph
我現在正在進行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和2上時,X的直接父母和孩子應該保持不變。與X沒有直接關系的其他節點應該像其他鏈接一樣消失。 目前,所有節點都沒有消失。
我研究了我的代碼並意識到它說所有節點都相互連接,所以我的isConnected()
是罪魁禍首。 我仍然不知道這些鏈接。
請幫我。
要解決的兩個問題
對於您的節點,因為它們是圖像文件,您需要設置它們的“不透明度”,而不是筆畫/填充不透明度。
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; });
除了@TomShanley的回答
如果你是d3的新手,為什么還要使用d3v3? 目前我們處於d3v5並且API得到了很大改進。
該程序沒有開箱即用,因為在確定linkedByIndex
它抱怨links
不存在。 它應該是json.links
。
在linkColor
return
后無需break
。
您搜索類svg.selectAll(".nodes")
元素,但您使用.attr("class", "node")
創建元素。 如果要正確使用enter-exit-update,這將不起作用。 與鏈接相同:搜索類links
但添加類lol
元素。
您的標記不是唯一的,無需使用each
marker-end
添加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;
});
};
}
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.