D3ライブラリを使用して、zオーダーの最上位にSVG要素をもたらす効果的な方法は何ですか?
私の特定のシナリオは、マウスが特定のピースの上にあるときに(stroke
をpath
に追加することによって)強調表示する円グラフです。チャートを生成するためのコードブロックは次のとおりです。
svg.selectAll("path")
.data(d)
.enter().append("path")
.attr("d", arc)
.attr("class", "arc")
.attr("fill", function(d) { return color(d.name); })
.attr("stroke", "#fff")
.attr("stroke-width", 0)
.on("mouseover", function(d) {
d3.select(this)
.attr("stroke-width", 2)
.classed("top", true);
//.style("z-index", 1);
})
.on("mouseout", function(d) {
d3.select(this)
.attr("stroke-width", 0)
.classed("top", false);
//.style("z-index", -1);
});
私はいくつかのオプションを試しましたが、これまでのところ運はありません。 style("z-index")
の使用とclassed
の呼び出しは両方とも機能しませんでした。
「トップ」クラスは、私のCSSで次のように定義されています。
.top {
fill: red;
z-index: 100;
}
fill
ステートメントは、正しくオン/オフになることを確認するためにあります。そうです。
sort
を使用することはオプションであると聞いたことがありますが、「選択した」要素を一番上に表示するためにどのように実装するかはわかりません。
更新:
mouseover
イベントのSVGに新しいアークを追加してハイライトを表示する次のコードで特定の状況を修正しました。
svg.selectAll("path")
.data(d)
.enter().append("path")
.attr("d", arc)
.attr("class", "arc")
.style("fill", function(d) { return color(d.name); })
.style("stroke", "#fff")
.style("stroke-width", 0)
.on("mouseover", function(d) {
svg.append("path")
.attr("d", d3.select(this).attr("d"))
.attr("id", "arcSelection")
.style("fill", "none")
.style("stroke", "#fff")
.style("stroke-width", 2);
})
.on("mouseout", function(d) {
d3.select("#arcSelection").remove();
});
開発者が提示するソリューションの1つは、「D3のソート演算子を使用して要素を並べ替える」ことです。 ( https://github.com/mbostock/d3/issues/252 を参照)
この観点から、データを比較して要素を並べ替えるか、データがない要素の場合は位置を並べ替えることができます。
.on("mouseover", function(d) {
svg.selectAll("path").sort(function (a, b) { // select the parent and sort the path's
if (a.id != d.id) return -1; // a is not the hovered element, send "a" to the back
else return 1; // a is the hovered element, bring "a" to the front
});
})
他の回答で説明したように、SVGにはz-indexの概念がありません。代わりに、ドキュメント内の要素の順序によって、図面内の順序が決まります。
要素を手動で並べ替える以外に、特定の状況には別の方法があります。
D3を使用すると、多くの場合、他の種類の要素の上に常に描画される特定の種類の要素があります。
たとえば、グラフをレイアウトする場合、リンクは常にノードの下に配置する必要があります。より一般的には、一部の背景要素は通常、他のすべての下に配置する必要がありますが、一部のハイライトとオーバーレイは上に配置する必要があります。
この種の状況がある場合、これらの要素グループに対して親グループ要素を作成するのが最善の方法であることがわかりました。 SVGでは、そのためにg
要素を使用できます。たとえば、常にノードの下に配置する必要があるリンクがある場合は、次の手順を実行します。
svg.append("g").attr("id", "links")
svg.append("g").attr("id", "nodes")
ここで、リンクとノードをペイントするときに、次のように選択します(#
で始まるセレクターは要素IDを参照します)。
svg.select("#links").selectAll(".link")
// add data, attach elements and so on
svg.select("#nodes").selectAll(".node")
// add data, attach elements and so on
これで、すべてのリンクが常にすべてのノード要素の前に構造的に追加されます。したがって、SVGは、要素を追加または削除する頻度と順序に関係なく、すべてのノードの下にすべてのリンクを表示します。もちろん、同じタイプ(つまり、同じコンテナ内)のすべての要素は、追加された順序の影響を受けます。
SVGにはZ-indexがありませんが、DOM要素の順序を使用するため、次の方法で前面に表示できます。
this.parentNode.appendChild(this);
その後、例えば insertBefore を使用して、mouseout
に戻します。ただし、これには、エレメントを前に挿入する必要がある兄弟ノードをターゲットにできる必要があります。
DEMO:これを見てください JSFiddle
SVGはz-indexを実行しません。 Zオーダーは、コンテナー内のSVG DOM要素の順序によって決まります。
私が知る限り(そして、これを過去に何度か試したことがあります)、D3には、単一の要素を前面またはその他の場所に移動するために、その要素を切り離して再接続する方法は用意されていません。
.order()
method があります。これは、選択に表示される順序に一致するようにノードを再シャッフルします。あなたのケースでは、単一の要素を前面に持ってくる必要があります。そのため、技術的には、目的の要素を前にして(または最後に、どちらが一番上かを思い出せずに)選択を再ソートし、その上でorder()
を呼び出すことができます。
または、このタスクでd3をスキップし、プレーンJS(またはjQuery)を使用してその単一のDOM要素を再挿入することもできます。
簡単な答えは、d3順序付け方法を使用することです。 d3.select( 'g')。order()に加えて、バージョン4には.lower()および.raise()があります。これにより、要素の表示方法が変わります。詳細については、ドキュメントをご覧ください- https://github.com/d3/d3/blob/master/API.md#selections-d3-selection
@ notan3xitが答えた内容を拡張したかったのですが、まったく新しい答えを書き出すのではありませんでした(しかし、私は十分な評判を得ていません)。
要素の順序の問題を解決する別の方法は、描画時に「追加」ではなく「挿入」を使用することです。そうすれば、パスは常に他のsvg要素の前に一緒に配置されます(これは、他のsvg要素のenter()の前に、コードがすでにリンクのenter()を実行していることを前提としています)。
d3 insert api: https://github.com/mbostock/d3/wiki/Selections#insert
Futurendのソリューションをコードに実装して動作しましたが、使用していた要素が多数あったため、非常に遅くなりました。特定の視覚化でより高速に動作するjQueryを使用した代替方法を次に示します。共通のクラスを持つ上に必要なsvgに依存します(私の例では、クラスはd.keyとしてデータセットに記載されています)。私のコードには、[locations]クラスを持つ<g>
があり、再編成中のすべてのSVGが含まれています。
.on("mouseover", function(d) {
var pts = $("." + d.key).detach();
$(".locations").append(pts);
});
したがって、特定のデータポイントにカーソルを合わせると、コードはその特定のクラスのSVG DOM要素を持つ他のすべてのデータポイントを見つけます。次に、それらのデータポイントに関連付けられたSVG DOM要素を切り離して再挿入します。
既存のSVGでZオーダーを微調整する方法を見つけるのに長い時間がかかりました。ツールチップ動作を備えたd3.brushのコンテキストで本当に必要でした。 2つの機能をうまく連携させるには( http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html )、d3.brushが必要ですZオーダーの最初(キャンバスに最初に描画され、次に残りのSVG要素で覆われる)であり、その上にあるもの(Zインデックスが高いもの)に関係なく、すべてのマウスイベントをキャプチャします。
ほとんどのフォーラムのコメントは、最初にコードにd3.brushを追加し、次にSVGの「描画」コードを追加する必要があると言っています。しかし、私にとっては、外部SVGファイルをロードしたため不可能でした。ブラシはいつでも簡単に追加でき、後でZオーダーを変更できます。
d3.select("svg").insert("g", ":first-child");
D3.brushセットアップのコンテキストでは、次のようになります。
brush = d3.svg.brush()
.x(d3.scale.identity().domain([1, width-1]))
.y(d3.scale.identity().domain([1, height-1]))
.clamp([true,true])
.on("brush", function() {
var extent = d3.event.target.extent();
...
});
d3.select("svg").insert("g", ":first-child");
.attr("class", "brush")
.call(brush);
d3.js insert()関数API: https://github.com/mbostock/d3/wiki/Selections#insert
お役に立てれば!
理論的には、次の方法でうまくいくはずです。
path:hover {
stroke: #fff;
stroke-width : 2;
}
このCSSコードは、選択されたパスにストロークを追加します。
svg.selectAll("path").on("mouseover", function(d) {
this.parentNode.appendChild(this);
});
このJSコードは、最初にDOMツリーからパスを削除してから、親の最後の子として追加します。これにより、同じ親の他のすべての子の上にパスが描画されます。
実際には、このコードはChromeでは正常に機能しますが、他の一部のブラウザーでは機能しません。 Linux MintマシンのFirefox 20で試してみましたが、動作しませんでした。どういうわけか、Firefoxは:hover
スタイルのトリガーに失敗し、これを修正する方法を見つけていません。
そこで、私は代替案を思いつきました。それは少し「汚い」かもしれませんが、少なくともそれは動作し、すべての要素をループする必要はありません(他の答えのいくつかとして)。
path.hover {
stroke: #fff;
stroke-width : 2;
}
:hover
擬似セレクターを使用する代わりに、.hover
クラスを使用します
svg.selectAll(".path")
.on("mouseover", function(d) {
d3.select(this).classed('hover', true);
this.parentNode.appendChild(this);
})
.on("mouseout", function(d) {
d3.select(this).classed('hover', false);
})
マウスオーバーで、.hover
クラスをパスに追加します。マウスアウトで、私はそれを削除します。最初の場合と同様に、コードはDOMツリーからパスを削除し、その親の最後の子として追加します。
マウスオーバーでこのようにすることができます。
d3.selection.prototype.bringElementAsTopLayer = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
d3.selection.prototype.pushElementAsBackLayer = function() {
return this.each(function() {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
nodes.on("mouseover",function(){
d3.select(this).bringElementAsTopLayer();
});
押し戻したい場合
nodes.on("mouseout",function(){
d3.select(this).pushElementAsBackLayer();
});