私はノードの素敵な小さなネットワークを持っています。
1 つは中央ノードで固定されており、他のノードはその周りを飛んでおり、リンクで接続され、力場の影響を受けています。
ユーザーが他のノードの 1 つをクリックすると、これが新しいセントラル ノードになり、固定されます。
このクリックされたノードを、最後の中央ノードがあった svg の中心に移動して、ノードをクリックするたびにネットワークが脇に移動しないようにするという考えです。
現在、この 1 つのノードの位置をリセットできますが、目的の位置にゆっくりと移動することはできません。
誰か私にアドバイスはありますか?
2013 年 11 月 29 日編集:
group.select(this).transition().attr("cx", function(d) { return width/2; });
ノード ( ) のクリック ハンドラで、ノードを追加した直後に.transition() を試しました。
編集 2: 上記のコード行は、ノードに入った後にテストされました。
また、fixedNode を介してノードを取得しようとしましたが、これには遷移方法がありません。Afaik には circle オブジェクトのみが含まれており、その性質上、.transition() メソッドはありません。
編集 3: コードを数時間いじってみたところ、一般的なトランジションを行う方法が見つかりました。ノードに力を適用した後に、余分なコードのブロックを追加しました (...call(force.drag);)。
半径を操作するとうまくいきます。唯一の問題は、すべてのノードが影響を受けることです。
r-attributes の変更を で置き換えると、機能test.transition().duration(3000).attr("cx", width/2);
しますが、リンクが遷移なしで通常の位置のままであり、遷移が終了するとすぐにノードが跳ね返るため、非常に奇妙に見えます!
ですから、必要なノードを 1 つのノードとして取得し、ポジションが機能するように移行するのを手伝っていただければ幸いです。
2013 年 12 月 2 日編集:
ソースコードを更新しました。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>
2013 年 12 月 3 日編集:
ノードの 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}
]
}
2013 年 12 月 4 日編集:
今では移行は機能していますが、ネットワークはセントラルノードでプッシュされていないため、ネットワーク自体がこのノードの fixedNode / reqNodes-data からノードに関するデータを取得することがわかりました。fixedNode.px を変更すると、一緒にスナップします。
したがって、すべてのティックで px を更新する行を tick() に追加できれば、これは計画どおりに機能すると思います...
アイデアがある場合、または私が非常に間違った方向に向かっていることに気付いた場合は、下にコメントを残すか、必要に応じて回答を作成してください!