(多数の)ノードとそれらの間の多くのリンクを含む力指向グラフがあります。ノードのサブセット(およびノード間のリンク)のみが残るように、インタラクティブにフィルターを適用したいと思います。ただし、グラフが大きいため、ノードを非表示にするのではなく、フィルターで除外されたノードをシミュレーションから削除します(結果のグラフのパフォーマンスが向上するようにします)。したがって、新しい配列としてノードのフィルタリングされたリストを作成し、これらのノードのみでシミュレーションを再初期化することを検討しています。同じことが、エッジに適用されます-私はこれをまだ行っていないが、私はおそらく前にフィルタリングの同じ種類を維持して行うためにエッジを決定プログラム的に必要となる新しいノードを持つグラフを再初期化し、エッジ。元のノード/エッジ配列を保持して、「リセット」を開始状態に戻せるようにしたい。
この時点でハードコードされたフィルタリングを実行している簡単な例をまとめましたが、フィルタリングされた配列を使用してシミュレーションを再初期化するのに苦労しています。フィルタリングされたノードをシミュレーションから削除しているようです(円「3」はドラッグできなくなります)が、レンダリングされたグラフには引き続き表示されます。
「リセット」ロジックでの私の試みは、これまでのところうまくいくようです。
私は何が間違っているのですか?これを達成するためのより良い方法はありますか?(d3.js v3)
私のサンプルコードは次のとおりです。
var links = [{
source: 0,
target: 1,
type: "c"
},
{
source: 1,
target: 2,
type: "d"
},
{
source: 2,
target: 0,
type: "d"
}
];
var nodes = [{
name: "one",
type: "a"
},
{
name: "two",
type: "a"
},
{
name: "three",
type: "b"
}
];
var width = 300;
var height = 300;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(200)
.charge(-400)
.on("tick", tick)
.start();
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height);
function colours(n) {
var colours = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395",
"#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"
];
return colours[n % colours.length];
}
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("line")
.attr('class', 'link')
.attr('stroke', function(d, i) {
return colours(i);
})
var circles = svg.append("g");
var circle = circles.selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 8)
.attr('class', 'circle')
.attr('fill', function(d, i) {
return colours(i + 3);
})
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 14)
.attr("y", ".31em")
.text(function(d) {
return d.name;
});
function tick() {
path.attr({
x1: function(d) {
return d.source.x;
},
y1: function(d) {
return d.source.y;
},
x2: function(d) {
return d.target.x;
},
y2: function(d) {
return d.target.y;
}
});
circle.attr("transform", transform);
text.attr("transform", transform);
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
var nodeText = "";
function nodeTypeA(node) {
return (node.type == "a");
}
function linkTypeC(link) {
return (link.type == "c");
}
function applyFilter() {
force.nodes(nodes.filter(nodeTypeA));
force.links(links.filter(linkTypeC));
circle.data(force.nodes());
text.data(force.nodes());
path.data(force.links());
d3.selectAll("circle").each(
function(d) {
console.log(d.name);
}
);
console.log("");
}
function resetFilter() {
force.nodes(nodes);
force.links(links);
circle.data(force.nodes());
text.data(force.nodes());
path.data(force.links());
d3.selectAll("circle").each(
function(d) {
console.log(d.name);
}
)
console.log("");
}
#buttons {
position: absolute;
top: 10px;
left: 20px;
height: 100px;
width: 400px;
z-index: 99;
}
#graph {
position: absolute;
top: 50px;
left: 20px;
height: 300px;
width: 300px;
z-index: 98;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<div id="root">
<div id="buttons">
<button id="filter" onclick="applyFilter()">Apply</button>
<button id="reset" onclick="resetFilter()">Reset</button>
</div>
<div id="graph">
</div>
</div>
</body>
<div id="node_details">
</div>
</body>
フィルタおよびリセット機能では、選択範囲のデータを更新しますが、新しい要素を追加/削除するために選択範囲の入力または終了を使用しないでください。
最初に要素を追加するときは、Enter選択を使用します。例:
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("line")
...
ただし、更新するときは、次を使用するだけです。
path.data(force.links());
最初にノードを追加したときと同じように、追加する内容と追加方法を指定する必要があります。
ノードを終了するのは非常に簡単.exit().remove()
です。フィルターを適用するときに上記の行に追加するだけです。.exit()
データ配列に対応する項目がなくなった選択範囲の要素を選択します。.remove()
それらをDOMから削除するだけです。
var links = [
{source: 0, target: 1, type: "c"},
{source: 1, target: 2, type: "d"},
{source: 2, target: 0, type: "d"}
];
var nodes = [
{name: "one", type: "a"},
{name: "two", type: "a"},
{name: "three", type: "b"}
];
var width = 300;
var height = 300;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(200)
.charge(-400)
.on("tick", tick)
.start();
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height);
function colours(n) {
var colours = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395",
"#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"];
return colours[n % colours.length];
}
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("line")
.attr('class', 'link')
.attr('stroke', function(d, i) {
return colours(i);
})
var circles = svg.append("g");
var circle = circles.selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 8)
.attr('class', 'circle')
.attr('fill', function(d, i) {
return colours(i + 3);
})
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 14)
.attr("y", ".31em")
.text(function(d) {
return d.name;
});
function tick() {
path.attr({
x1: function(d) {
return d.source.x;
},
y1: function(d) {
return d.source.y;
},
x2: function(d) {
return d.target.x;
},
y2: function(d) {
return d.target.y;
}
});
circle.attr("transform", transform);
text.attr("transform", transform);
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
var nodeText = "";
function nodeTypeA(node) {
return (node.type == "a");
}
function linkTypeC(link) {
return (link.type == "c");
}
function applyFilter() {
force.nodes(nodes.filter(nodeTypeA));
force.links(links.filter(linkTypeC));
circle.data(force.nodes()).exit().remove();
text.data(force.nodes()).exit().remove();
path.data(force.links()).exit().remove();
d3.selectAll("circle").each(
function(d) {
console.log(d.name);
}
);
console.log("");
}
function resetFilter() {
force.nodes(nodes);
force.links(links);
circle.data(force.nodes());
text.data(force.nodes());
path.data(force.links());
d3.selectAll("circle").each(
function(d) {
console.log(d.name);
}
)
console.log("");
}
#buttons {
position: absolute;
top: 10px;
left: 20px;
height: 100px;
width: 400px;
z-index: 99;
}
#graph {
position: absolute;
top: 50px;
left: 20px;
height: 300px;
width: 300px;
z-index: 98;
}
<div id="root">
<div id="buttons">
<button id="filter" onclick="applyFilter()">Apply</button>
<button id="reset" onclick="resetFilter()">Reset</button>
</div>
<div id="graph">
</div>
</div>
</body>
<script src="https://d3js.org/d3.v3.min.js"></script>
リセット関数の最初の入力に使用するコードを複製して要素を入力することもできますが(いくつかの小さな変更を加えて)、これは少し反復的です。同じことを行うコードの2つのセクションがあります。
代わりに、入力と終了を更新関数に入れましょう。更新機能は、フォースレイアウトからノードとリンクを取得し、必要に応じて出入りします。
var links = [
{source: 0, target: 1, type: "c"},
{source: 1, target: 2, type: "d"},
{source: 2, target: 0, type: "d"}
];
var nodes = [
{name: "one", type: "a"},
{name: "two", type: "a"},
{name: "three", type: "b"}
];
var width = 300;
var height = 300;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(200)
.charge(-400)
.on("tick", tick)
.start();
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height);
function colours(n) {
var colours = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395",
"#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"];
return colours[n % colours.length];
}
var paths = svg.append("g");
var circles = svg.append("g");
var texts = svg.append("g");
update();
function tick() {
paths.selectAll("line").attr({
x1: function(d) {
return d.source.x;
},
y1: function(d) {
return d.source.y;
},
x2: function(d) {
return d.target.x;
},
y2: function(d) {
return d.target.y;
}
});
circles.selectAll("circle").attr("transform", transform);
texts.selectAll("text").attr("transform", transform);
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
var nodeText = "";
function nodeTypeA(node) {
return (node.type == "a");
}
function linkTypeC(link) {
return (link.type == "c");
}
function applyFilter() {
force.nodes(nodes.filter(nodeTypeA));
force.links(links.filter(linkTypeC));
update();
}
function resetFilter() {
force.nodes(nodes);
force.links(links);
update();
force.start(); // start the force layout again.
}
function update() {
// update the data for the lines:
var path = paths.selectAll("line")
.data(force.links());
// enter new lines:
path.enter().append("line")
.attr('class', 'link')
.attr('stroke', function(d, i) {
return colours(i);
})
// exit unneeded lines:
path.exit().transition().style("opacity",0).remove();
// update the data for the circles:
var circle = circles.selectAll("circle")
.data(force.nodes());
// enter new circles:
circle.enter().append("circle")
.attr("r", 8)
.attr('class', 'circle')
.attr('fill', function(d, i) {
return colours(i + 3);
})
.call(force.drag);
// remove unneeded circles:
circle.exit().transition().style("opacity",0).remove();
// update the text data:
var text = texts.selectAll("text")
.data(force.nodes());
// enter new text
text.enter().append("text")
.attr("x", 14)
.attr("y", ".31em")
.text(function(d) {
return d.name;
});
// exit old text:
text.exit().transition().style("opacity",0).remove();
}
#buttons {
position: absolute;
top: 10px;
left: 20px;
height: 100px;
width: 400px;
z-index: 99;
}
#graph {
position: absolute;
top: 50px;
left: 20px;
height: 300px;
width: 300px;
z-index: 98;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="root">
<div id="buttons">
<button id="filter" onclick="applyFilter()">Apply</button>
<button id="reset" onclick="resetFilter()">Reset</button>
</div>
<div id="graph">
</div>
</div>
</body>
<div id="node_details">
</div>
オリジナルからの変更点:フィルター関数とリセット関数は、フォースのノードとリンクを設定した後に更新関数を呼び出します(ノードは最初に更新関数で描画されます)。ノードを追加すると、力がリセットされます(シミュレーションが冷却されたかのように再起動するために、ティックは呼び出されず、ノードは適切に配置されません)。
最後に、テキスト、円、および線は、それぞれ、、およびg
という名前の親選択に含まれtexts
circles
ていlines
ます。ティック関数はg
、ティックごとに各親の子を再選択するように変更されましたが、これは別の方法で最適化できます。
最後の注意として、データの識別子の指定またはデータ内のノード/リンクプロパティの指定を検討する価値があるかもしれません-リンク/ノードの色付けの削除/追加および/またはインデックスによるプロパティの設定が問題になる可能性があります。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加