我有一个很好的小节点网络。
一个是中心节点并且是固定的,其他的是在它周围飞行,与链接相连并受力场的影响。
当用户点击其中一个节点时,这个节点就是新的中心节点,因此是固定的。
这个想法是将这个点击的节点转移到最后一个中心节点所在的svg的中心,这样网络就不会随你点击的每个节点移动到一边。
现在我可以重置这个节点的位置,但不要慢慢移动到所需的位置。
有人对我有建议吗?
2013年11月29日编辑:
我在节点的点击处理程序(group.select(this).transition().attr("cx", function(d) { return width/2; });
)中尝试过.transition()
并且在追加节点后立即。
编辑2:输入节点后测试了上面给出的代码行。
我也尝试通过fixedNode获取节点,但是这个节点没有过渡方法。 Afaik它只包含圆形对象,根据它的性质,没有.transition() - 方法。
编辑3:我已经玩了几个小时的代码,现在我找到了一种方法来进行基因转换。在将力施加到节点后,我添加了一块额外的代码(... call(force.drag);)。
如果我操纵半径,它可以正常工作。唯一的问题是所有节点都受到影响。
如果我用test.transition().duration(3000).attr("cx", width/2);
替换r属性更改,它可以工作但看起来很奇怪,因为链接sstill具有没有转换的正常位置,并且一旦转换完成,节点就会跳回来!
所以,如果你可以帮助我获得我需要的节点作为单个节点并获得职位转换,我会很高兴!
编辑12/02/2013:
更新了源代码。感谢Lars Kotthoff,我现在可以移动圆圈了。但链接仍然转到节点的旧位置,当转换结束时,圆圈会回到它们开始的位置。
添加了要复制的工作代码。请注意,您需要在后台使用json文件('rawData.json'位于同一文件夹中)。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<script type="text/javascript" src="d3.v3.js"></script>
<script type="text/javascript" src="d3.v3.min.js"></script>
<link rel="stylesheet" type="text/css" href="dhtmlxSlider/codebase/dhtmlxslider.css">
<script src="dhtmlxSlider/codebase/dhtmlxcommon.js"></script>
<script src="dhtmlxSlider/codebase/dhtmlxslider.js"></script>
<script>
window.dhx_globalImgPath = "dhtmlxSlider/codebase/imgs/";
</script>
<style type="text/css">
.node text {
pointer-events: none;
font: 10px sans-serif;
color: #FF7777;
}
.text {
color: #FF7777;
}
</style>
<title>ConceptMaps</title>
</head>
<body>
<div id="chart" class="chart"></div>
<div align="center" id="slider" onmouseup="paintIt();"></div>
<div align="center" id="test"></div>
<script>
var reqNodes = [],
reqLinks = [],
fixedNode,
testContainer = null,
width = window.innerWidth*0.9,
height = window.innerHeight*0.9,
mittelpunkt = "Perry Rhodan",
checklist = [],
mitte = [],
colorArray = ["#D3D3D2", "darkblue", "#008000", "#F1AD45", "#F2D667", "#8BD3EB", "#B74965", "#67A175"],
force = d3.layout.force().gravity(-0.1).distance(250).charge(-500).linkDistance(80).linkStrength(4).friction(0.6).size([width, height]);
var group = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", 'networkBox');
var sld = new dhtmlxSlider("slider", 100, "arrow", false, 1, 5, 2);
sld.setImagePath("dhtmlxSlider/codebase/imgs/");
sld.setSteppingMode(true);
sld.linkTo("test");
sld.init();
function getIndexInXML(attribute, targetContent) {
if (xmlDoc == null) {alert("FAIL!");}
var searchArray = xmlDoc.documentElement.getElementsByTagName(attribute);
for (var i = 0; i < searchArray.length; i++) {
if (searchArray[i].textContent == targetContent) {
return i;
}
}
}//end getIndexInXML
function getNodesFinalIndex(nameOfTarget) {
for (var i = 0; i < reqNodes.length; i++) {
if(reqNodes[i] != null){
if (reqNodes[i].name == nameOfTarget) {
return i;
}
}
}//for
}//getNodesFinalIndex
function getNodesIndex(nameOfTarget) {
for(var i = 0; i < testContainer.nodes.length; i++){
if(testContainer.nodes[i].name == nameOfTarget){
return i;
}
}//for
}//getNodesIndex
var linkBin = [];
function getLinksIndex(nodeIndex){
linkBin = [];
for(var n = 0; n < reqLinks.length; n++){
if(reqLinks[n].source.name == reqNodes[nodeIndex].name || reqLinks[n].target.name == reqNodes[nodeIndex].name){ //can not read name of undefined
linkBin.push(n);
}
}
return linkBin;
}//end getLinksIndex
//getting the data from JSON-File
d3.json("rawData.json", function(error, graph) {
testContainer = graph;
paintIt();
});//end d3.json
function paintIt() {
reqLinks = null;
reqLinks = [];
getNetParts(mittelpunkt, sld.getValue());
update();
}//end paintIt
//------------ sort by used and unused nodes -------------------
function getNetParts(searchedFor, depth) {
var temp = null;
temp = [];
//create a checklist with one element for each node in testContainer
for(var pr = 0; pr < testContainer.nodes.length; pr++){
checklist[pr] = false;
}
var middle = getNodesIndex(searchedFor);
temp.push(testContainer.nodes[middle]); //asign middle node to temp
checklist[middle] = true; //note that middle has already been added to temp to prevent double assignments
sortReqNodes(middle,1);
checklist = null;
checklist = [];
var isThere = false;
var freeSpaces = null;
freeSpaces = [];
for(var pr = 0; pr < temp.length; pr++){ //new checklist, delete prev notes
checklist[pr] = false;
}
for(var c = 0; c < reqNodes.length; c++){ //circle over the elements in 'reqNodes'
for(var cg = 0; cg < temp.length; cg++){ //look up if the element should be held there for the next version, too
if(temp[cg] != null && reqNodes[c] != null){
if(temp[cg].name == reqNodes[c].name){
temp[cg] = null; //note the index of the node that doesn't need to be transfered
isThere = true; //and make a mark
break; //and break
}
else {reqNodes[c] = null;}
if(!isThere){
freeSpaces.push(c);
}
}
}
}
for(var x = 0; x < temp.length; x++){ //circle through temp
if(temp[x] != null){ //if the element hasn't been deleted yet
if(freeSpaces.length != 0) //if there is a free space, use it
reqNodes[freeSpaces.pop()] = temp[x]; //transfer to reqNodes
else
reqNodes.push(temp[x]);
}
}
//clean reqNodes up - deleting every 'null'
for(var sl = reqNodes.length-1; sl >= 0; sl--){ //reverse loop - start with last element and end with first -> deleting elements won't disturb the loop
if(reqNodes[sl] == null){ //if it is empty
reqNodes.splice(sl, 1); //...remove it!
}
}
for(var z = 1; z < reqNodes.length; z++){ //circle through stored Nodes
if(reqNodes[z] != null){
for(var c = 0; c < reqNodes[z].connections.length; c++){ //inspect all of their connections
for(var w = 0; w < reqNodes.length; w++){ //check all stored reqNodes for the one linked
if(reqNodes[w] != null){
if(testContainer.nodes[reqNodes[z].connections[c]].name == reqNodes[w].name){ //the node is available at reqNodes?
var source = null;
var newLink = {
"source":z,
"target":w,
"value":1,
};
reqLinks.push(newLink);
}
}
}
}
}
}
function sortReqNodes(mitte, count){
for(var y = 0; y < testContainer.nodes[mitte].connections.length; y++){ //stores every node connected with the main node in reqNode
if(!checklist[testContainer.nodes[mitte].connections[y]]){ //if this one hasn't already been added
temp.push(testContainer.nodes[testContainer.nodes[mitte].connections[y]]); //push a node connected with mitte to reqNodes
checklist[testContainer.nodes[mitte].connections[y]] = true; //check - node has been pushed!
}
if(count < depth){ //if we haven't reached the desired depth yet
sortReqNodes(testContainer.nodes[mitte].connections[y],count+1); //one more round, get the nodes connected with this one involved, too!
}
}
}//end function sortReqNodes
}//end function getNetParts
function update() {
group.selectAll(".link").remove();
group.selectAll(".node").remove();
//unfix any fixed nodes
for(var count = 0; count < reqNodes.length; count++){
if(reqNodes[count] != null){
reqNodes[count].fixed = false;
}
}
var fixedNode = reqNodes[getNodesFinalIndex(mittelpunkt)];
fixedNode.x = 900; //Only works the first time...
fixedNode.y = 300;
fixedNode.fixed = true;
// fixedNode.px = width/2; /*working, but makes the node jump -> ugly!*/
// fixedNode.py = height/2;
link = group.selectAll(".link")
.data(reqLinks);
link.enter().append("line")
.attr("class", "link")
.style("stroke", "#000")
.style("stroke-width", function(d) { return Math.sqrt(d.value)*1.2; });
node = group.selectAll("circle")
.data(reqNodes);
node.enter().append("circle")
.attr("class", "node")
.attr("r", 7)
.style("stroke", "black")
.style("fill", function(d) { return colorArray[d.group]; })
.call(force.drag);
node.append("name").text(function(d) { return d.name; });
node.append("title")
.text(function(d) { return d.name; });
node.attr("name", function(d) { return d.name; });
for(var oo = 0; oo < node[0].length; oo++){
if(node[0][oo] != null){
if(node[0][oo].getAttribute('name') == mittelpunkt){
node[0][oo].style.stroke = "red";
node[0][oo].style.strokeWidth = 3;
}
}
}
node.filter(function(d) { return d.fixed == true; }).transition().duration(5000)
.attr("cy", height/2)
.attr("cx", width/2);
node.filter(function(d) { return d.fixed == true; })
.attr("y", height/2)
.attr("x", width/2);
node.on("click", function(d) {
mittelpunkt = d.name;
paintIt();
});
node.append("text").attr("dx", 12).attr("dy", ".35em").attr("fill", "#aa0101").text(function(d) {
return d.name
});
//shows the fit article from the website www.perrypedia.proc.org, if you don't know the character on the node!
group.selectAll(".node").on("dblclick", function(d) {
window.open("http://www.perrypedia.proc.org/wiki/" + d.name);
});
force
.nodes(reqNodes)
.links(reqLinks)
.start();
group.selectAll('.not-fixed').call(force.drag);
var groupDrag = d3.behavior.drag().on("drag", function(d) {
// mouse pos offset by starting node pos
var x = window.event.clientX - 430, y = window.event.clientY - 280;
group.attr("transform", function(d) {
return "translate(" + x + "," + y + ")";
});
CurrentGTransformX = x;
CurrentGTransformY = y;
})
group.call(groupDrag);
node.append("title").text(function(d) {
return d.skill;
});
//HOVER for the nodes - makes it easier too determine which links belong to the node
node.on("mouseover", function(d) {
var selection = d3.select(this);
var links2Change = getLinksIndex(getNodesFinalIndex(selection[0][0].getElementsByTagName("title")[0].textContent));
var initialWidth = Number( selection.style("stroke-width") );
var linksContainer = group.selectAll(".link");
if(links2Change != null && links2Change != []){
for(var c = 0; c < links2Change.length; c++){
linksContainer[0][links2Change[c]].style.opacity = 0.2;
}
}
} )
node.on("mouseout", function(d) {
var selection = d3.select(this);
var links2Change = getLinksIndex(getNodesFinalIndex(selection[0][0].getElementsByTagName("title")[0].textContent));
var initialWidth = Number( selection.style("stroke-width") );
var linksContainer = group.selectAll(".link");
if(links2Change != null && links2Change != []){
for(var c = 0; c < links2Change.length; c++){
linksContainer[0][links2Change[c]].style.opacity = 1;
}
}
})
link.append("title").text(function(d) {
return d.group;
});
function getOffset(el) {
var _x = 0;
var _y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return {
top : _y,
left : _x
};
}
force.on("tick", function() {
node.attr("cx", function(d) { return d.x = Math.max(15, Math.min(width - 15, d.x)); }) //restricts the x-coordinates to the inside of the SVG
.attr("cy", function(d) { return d.y = Math.max(15, Math.min(height - 15, d.y)); }); //restricts the y-coordinates to the inside of the SVG
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;
});
});
}//end function update()
</script>
</body>
编辑12/03/2013:
添加了对节点x / y的操作。
不幸的是,这并不妨碍节点与网络分开以进行过渡。在px / py上执行相同的操作无助于修复节点从transition()的目标位置跳回到分配给force()的节点...
rawData.json:
{
"nodes":[
{"name":"Perry Rhodan","group":1,"skill":"Sofortumschalter, Erbe des Universums","connections":[1,2,3,4,5,7,13,17,19,18,22]},
{"name":"Reginald Bull","group":1,"skill":"Techniker, Draufgaenger","connections":[12,0]},
{"name":"Thora da Zoltral","group":2,"skill":"Hochmuetig","connections":[0,3,13,4]},
{"name":"Crest da Zoltral","group":2,"skill":"Derengar","connections":[0,2,4]},
{"name":"Atlan da Gonozal","group":2,"skill":"Der Einsame der Zeit","connections":[0,2,3,24]},
{"name":"Homer Gershwin Adams","group":1,"skill":"fotografisches Gedaechtnis","connections":[0,7]},
{"name":"Tatjana Michalowna","group":1,"skill":"Telepathin","connections":[7]},
{"name":"Mutantenkorps","group":3,"skill":"1972 gegruendet von Perry Rhodan","connections":[0,8,9,10,11,12,15,16]},
{"name":"Son Okura","group":1,"skill":"Telekinet","connections":[7]},
{"name":"Wuriu Sengu","group":1,"skill":"Spaeher","connections":[7,14]},
{"name":"Tako Kakuta","group":1,"skill":"Teleporter","connections":[7]},
{"name":"Ras Tschubai","group":1,"skill":"Teleporter","connections":[7,12]},
{"name":"Gucky","group":4,"skill":"Telepath, Telekinet, Teleporter","connections":[7,11,1]},
{"name":"Thomas Cardif","group":1,"skill":"Rhodan-Imitator, Rhodans Sohn","connections":[0,2]},
{"name":"Ismael ben Rabbat","group":1,"skill":"Raumschiffskommandant","connections":[9]},
{"name":"Tama Yokida","group":1,"skill":"Telekinet, Materiewandler","connections":[7]},
{"name":"Ernst Ellert","group":1,"skill":"Teletemporarier","connections":[7]},
{"name":"Lotho Keraete","group":0,"skill":"Bote von ES","connections":[0,18,22,26]},
{"name":"Homunk","group":0,"skill":"Bote von ES","connections":[0,17,26]},
{"name":"Alaska Saedelaere","group":1,"skill":"Maskentraeger","connections":[0,20,21,22]},
{"name":"Samburi Yura","group":0,"skill":"Kosmokratenbeauftragte","connections":[19,25]},
{"name":"Sholoubwa","group":0,"skill":"Konstrukteur","connections":[19]},
{"name":"Ennerhahl","group":0,"skill":"Beauftragter von ES, 'Mittel, Wege und Moeglichkeiten'","connections":[0,19,17,26]},
{"name":"Delorian Rhodan","group":0,"skill":"Sohn von Perry & Mondra, Ex-Chronist von ES, Schöpfer einer Enklave","connections":[0,22,20,26]},
{"name":"Theta da Ariga","group":2,"skill":"Geliebte Atlans","connections":[4]},
{"name":"LICHT VON AHN","group":5,"skill":"Superintelligenz/Kollektivwesen","connections":[20]},
{"name":"ES","group":5,"skill":"Superintelligenz","connections":[17,18,22,23]}
],
"links":[
{"source":1,"target":0,"value":2},
{"source":3,"target":0,"value":2},
{"source":2,"target":0,"value":3},
{"source":3,"target":2,"value":3},
{"source":4,"target":0,"value":2},
{"source":5,"target":0,"value":1},
{"source":4,"target":2,"value":1},
{"source":4,"target":3,"value":1},
{"source":6,"target":7,"value":4},
{"source":7,"target":0,"value":1},
{"source":5,"target":7,"value":4},
{"source":8,"target":7,"value":4},
{"source":9,"target":7,"value":4},
{"source":10,"target":7,"value":4},
{"source":11,"target":7,"value":4},
{"source":12,"target":7,"value":4},
{"source":12,"target":1,"value":2},
{"source":13,"target":0,"value":5},
{"source":13,"target":2,"value":5},
{"source":14,"target":9,"value":2},
{"source":11,"target":12,"value":2},
{"source":15,"target":7,"value":4},
{"source":16,"target":7,"value":4},
{"source":18,"target":22,"value":1},
{"source":20,"target":22,"value":1},
{"source":21,"target":19,"value":1},
{"source":22,"target":0,"value":1},
{"source":22,"target":19,"value":1},
{"source":22,"target":17,"value":1},
{"source":23,"target":22,"value":1},
{"source":23,"target":0,"value":1},
{"source":23,"target":20,"value":1}
]
}
编辑12/04/2013:
到现在为止我已经完成了转换工作,但网络没有被中心节点推送,我发现网络本身从fixedNode /此节点的reqNodes-data获取有关节点的数据。如果我更改fixedNode.px,它会一起拍摄。
因此,如果我可以在tick()中添加一行来更新每个tick中的px,我认为这可以按计划进行...
如果您有任何想法或看到我正朝着一个非常错误的方向前进,请在下方发表评论,或者,如果您愿意,请创建一个答案!
答案 0 :(得分:1)
作为参考,以下是在修正位置后将节点移动到新位置的正确方法:
node.filter(function(d) { return d.fixed; }).transition().duration(1000)
.tween("x", function() {
var i = d3.interpolate(fixedNode.x, 900);
return function(t) {
fixedNode.x = i(t);
fixedNode.px = i(t);
};
}).tween("y", function() {
var i = d3.interpolate(fixedNode.y, 300);
return function(t) {
fixedNode.y = i(t);
fixedNode.py = i(t);
};
});
这在已修复的元素上使用自定义补间函数,在力布局的每一步设置x
/ y
和px
/ py
属性接。