Cập nhật chỉ số Z-Index của yếu tố SVG với D3


81

Cách hiệu quả để đưa phần tử SVG lên đầu thứ tự z, sử dụng thư viện D3 là gì?

Kịch bản cụ thể của tôi là một biểu đồ hình tròn sẽ làm nổi bật (bằng cách thêm a strokevào path) khi chuột đi qua một phần nhất định. Khối mã để tạo biểu đồ của tôi là bên dưới:

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);
    });

Tôi đã thử một vài lựa chọn, nhưng không có may mắn cho đến nay. Sử dụng style("z-index")và gọi điện classedđều không hoạt động.

Lớp "top" được định nghĩa như sau trong CSS của tôi:

.top {
    fill: red;
    z-index: 100;
}

Các filltuyên bố là có để chắc chắn rằng tôi biết nó đã được bật / tắt một cách chính xác. Nó là.

Tôi đã nghe nói sử dụng sortlà một tùy chọn, nhưng tôi không rõ về cách nó sẽ được triển khai để đưa phần tử "đã chọn" lên đầu.

CẬP NHẬT:

Tôi đã khắc phục tình huống cụ thể của mình bằng đoạn mã sau, mã này sẽ thêm một vòng cung mới vào SVG trên mouseoversự kiện để hiển thị điểm nổi bật.

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();
    });

Câu trả lời:


52

Một trong những giải pháp được nhà phát triển đưa ra là: "sử dụng toán tử sắp xếp của D3 để sắp xếp lại các phần tử." (xem https://github.com/mbostock/d3/issues/252 )

Theo cách này, người ta có thể sắp xếp các phần tử bằng cách so sánh dữ liệu hoặc vị trí của chúng nếu chúng là các phần tử không có dữ liệu:

.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
  });
})

Điều này không chỉ hoạt động theo thứ tự Z, nó cũng không làm phiền bất kỳ thao tác kéo nào vừa bắt đầu với phần tử được nâng lên.
Oliver Bock

3
nó cũng không hiệu quả với tôi. a, b có vẻ là chỉ có dữ liệu, không phải là yếu tố lựa chọn d3 hoặc phần tử DOM
nkint

Thật vậy, giống như đối với @nkint, nó không hoạt động khi so sánh a.idd.id. Giải pháp là so sánh trực tiếp ad.
Peace Makes Plenty

Điều này sẽ không hoạt động đầy đủ cho một số lượng lớn các phần tử con. Tốt hơn nên hiển thị lại phần tử được nâng lên trong phần tiếp theo <g>.
tribalvibes

1
Ngoài ra, svg:usephần tử có thể trỏ động đến phần tử được nâng lên. Xem stackoverflow.com/a/6289809/292085 để biết giải pháp.
tribalvibes

91

Như đã giải thích trong các câu trả lời khác, SVG không có khái niệm về chỉ số z. Thay vào đó, thứ tự của các phần tử trong tài liệu xác định thứ tự trong bản vẽ.

Ngoài việc sắp xếp lại các phần tử theo cách thủ công, có một cách khác cho một số trường hợp nhất định:

Làm việc với D3, bạn thường có một số loại phần tử nhất định luôn phải được vẽ lên trên các loại phần tử khác .

Ví dụ: khi đặt biểu đồ, các liên kết phải luôn được đặt bên dưới các nút. Nói chung hơn, một số yếu tố nền thường cần được đặt bên dưới mọi thứ khác, trong khi một số điểm nổi bật và lớp phủ nên được đặt ở trên.

Nếu bạn gặp phải tình huống này, tôi thấy rằng việc tạo các phần tử nhóm mẹ cho các nhóm phần tử đó là cách tốt nhất để thực hiện. Trong SVG, bạn có thể sử dụng gphần tử cho điều đó. Ví dụ: nếu bạn có các liên kết luôn được đặt bên dưới các nút, hãy làm như sau:

svg.append("g").attr("id", "links")
svg.append("g").attr("id", "nodes")

Bây giờ, khi bạn vẽ các liên kết và nút của mình, hãy chọn như sau (các bộ chọn bắt đầu bằng #tham chiếu id phần tử):

svg.select("#links").selectAll(".link")
// add data, attach elements and so on

svg.select("#nodes").selectAll(".node")
// add data, attach elements and so on

Bây giờ, tất cả các liên kết sẽ luôn được nối theo cấu trúc trước tất cả các phần tử nút. Do đó, SVG sẽ hiển thị tất cả các liên kết bên dưới tất cả các nút, bất kể bạn thêm hoặc bớt các phần tử thường xuyên như thế nào và theo thứ tự nào. Tất nhiên, tất cả các phần tử cùng loại (nghĩa là trong cùng một vùng chứa) sẽ vẫn phải tuân theo thứ tự mà chúng được thêm vào.


1
Tôi bắt đầu làm việc này với ý tưởng trong đầu rằng tôi có thể chuyển một mục lên trên, khi vấn đề thực sự của tôi là tổ chức các yếu tố của tôi thành các nhóm. Nếu vấn đề "thực sự" của bạn là di chuyển các phần tử riêng lẻ thì phương pháp sắp xếp được mô tả trong câu trả lời trước là một cách tốt để thực hiện.
John David Năm

Tuyệt vời! Cảm ơn notan3xit, bạn đã cứu một ngày của tôi! Chỉ để thêm ý tưởng cho những người khác - nếu bạn cần thêm các phần tử theo thứ tự khác với thứ tự hiển thị mong muốn, bạn chỉ có thể tạo các nhóm theo thứ tự hiển thị thích hợp (ngay cả trước khi thêm bất kỳ phần tử nào vào chúng) và sau đó nối các phần tử vào những nhóm theo thứ tự bất kỳ.
Olga Gnatenko

1
Đây là cách tiếp cận chính xác. Rõ ràng một khi bạn nghĩ về nó.
Dave

Đối với tôi, svg.select ('# links') ... phải được thay thế bằng d3.select ('# links') ... để hoạt động. Có thể điều này sẽ giúp ích cho người khác.
trao đổi

26

Vì SVG không có Z-index nhưng sử dụng thứ tự của các phần tử DOM, bạn có thể đưa nó lên trước bằng cách:

this.parentNode.appendChild(this);

Sau đó, bạn có thể sử dụng insertBefore để đặt nó trở lại mouseout. Tuy nhiên, điều này đòi hỏi bạn phải có thể nhắm mục tiêu đến nút anh chị em mà phần tử của bạn sẽ được chèn trước đó.

DEMO: Hãy xem JSFiddle này


Đây sẽ là một giải pháp tuyệt vời nếu Firefox không thể hiển thị bất kỳ :hoverkiểu nào . Tôi cũng không nghĩ rằng hàm ìnsert ở cuối sẽ được yêu cầu cho trường hợp sử dụng này.
John Slegers,

@swenedo, giải pháp của bạn phù hợp với tôi và mang yếu tố này lên hàng đầu. Về việc mang nó ra mặt sau, tôi hơi chồng chất và không biết viết như thế nào cho đúng. Bạn có thể làm ơn, đăng một ví dụ, làm thế nào bạn có thể đưa đối tượng về phía sau. Cảm ơn.
Mike B.

1
@MikeB. Chắc chắn, tôi vừa cập nhật câu trả lời của mình. Tôi nhận ra rằng insertchức năng của d3 có lẽ không phải là cách tốt nhất để sử dụng, vì nó tạo ra một phần tử mới . Chúng tôi chỉ muốn di chuyển một phần tử hiện có, do đó tôi sử dụng insertBeforetrong ví dụ này để thay thế.
swenedo

Tôi đã cố gắng sử dụng phương pháp này, nhưng tiếc là tôi không hoạt động trong IE11. Bạn có một công việc xung quanh cho trình duyệt này?
Antonio Giovanni Schiavone

13

SVG không thực hiện z-index. Thứ tự Z được quy định bởi thứ tự của các phần tử SVG DOM trong vùng chứa của chúng.

Theo như tôi có thể nói (và tôi đã thử điều này một vài lần trong quá khứ), D3 không cung cấp các phương pháp tách và gắn lại một phần tử duy nhất để đưa nó về phía trước hoặc không có gì khác.

Có một .order()phương pháp , điều chỉnh lại các nút để khớp với thứ tự chúng xuất hiện trong vùng chọn. Trong trường hợp của bạn, bạn cần đưa một yếu tố duy nhất lên phía trước. Vì vậy, về mặt kỹ thuật, bạn có thể sử dụng lựa chọn với phần tử mong muốn ở phía trước (hoặc ở cuối, không thể nhớ phần tử nào ở trên cùng), và sau đó gọi order()nó.

Hoặc, bạn có thể bỏ qua d3 cho tác vụ này và sử dụng JS thuần túy (hoặc jQuery) để chèn lại phần tử DOM duy nhất đó.


5
Xin lưu ý rằng có vấn đề với việc xóa / chèn các phần tử bên trong trình xử lý di chuột qua trong một số trình duyệt, nó có thể dẫn đến các sự kiện mouseout không được gửi đúng cách, vì vậy tôi khuyên bạn nên thử điều này trong tất cả các trình duyệt để đảm bảo nó hoạt động như bạn mong đợi.
Erik Dahlström

Việc thay đổi thứ tự sẽ không thay đổi bố cục của biểu đồ hình tròn của tôi phải không? Tôi cũng đang tìm hiểu về jQuery để tìm hiểu cách chèn lại các phần tử.
Evil Closet Monkey

Tôi đã cập nhật câu hỏi của mình với giải pháp mà tôi đã chọn, giải pháp này không thực sự sử dụng cốt lõi của câu hỏi ban đầu. Nếu bạn thấy bất cứ điều gì thực sự xấu về giải pháp này, tôi hoan nghênh các ý kiến ​​đóng góp! Cảm ơn bạn đã hiểu rõ về câu hỏi ban đầu.
Evil Closet Monkey

Có vẻ như đây là một cách tiếp cận hợp lý, chủ yếu là nhờ vào thực tế là hình dạng phủ không có phần tô. Nếu nó xảy ra, tôi hy vọng nó sẽ "đánh cắp" sự kiện chuột ngay khi nó xuất hiện. Và, tôi nghĩ rằng quan điểm của @ ErikDahlström vẫn còn: đây vẫn có thể là một vấn đề trong một số trình duyệt (chủ yếu là IE9). Để thêm "an toàn", bạn có thể thêm .style('pointer-events', 'none')vào đường dẫn có lớp phủ. Thật không may, khách sạn này không làm bất cứ điều gì trong IE, nhưng vẫn ....
meetamit

@EvilClosetMonkey Xin chào ... Câu hỏi này nhận được rất nhiều lượt xem và tôi tin rằng câu trả lời của futurend dưới đây hữu ích hơn của tôi. Vì vậy, có lẽ hãy xem xét việc thay đổi câu trả lời được chấp nhận thành futurend, để nó xuất hiện cao hơn.
meetamit


4

Tôi đã triển khai giải pháp của futurend trong mã của mình và nó hoạt động, nhưng với số lượng lớn các phần tử tôi đang sử dụng, nó rất chậm. Đây là phương pháp thay thế sử dụng jQuery hoạt động nhanh hơn cho hình ảnh trực quan cụ thể của tôi. Nó dựa trên các svgs bạn muốn trên cùng có một lớp chung (trong ví dụ của tôi, lớp được ghi chú trong tập dữ liệu của tôi là d.key). Trong mã của tôi có một <g>với "vị trí" lớp chứa tất cả các SVG mà tôi đang tổ chức lại.

.on("mouseover", function(d) {
    var pts = $("." + d.key).detach();
    $(".locations").append(pts);
 });

Vì vậy, khi bạn di chuột trên một điểm dữ liệu cụ thể, mã sẽ tìm thấy tất cả các điểm dữ liệu khác có phần tử SVG DOM với lớp cụ thể đó. Sau đó, nó tách ra và chèn lại các phần tử SVG DOM được liên kết với các điểm dữ liệu đó.


4

Tôi muốn mở rộng những gì @ notan3xit đã trả lời hơn là viết ra một câu trả lời hoàn toàn mới (nhưng tôi không có đủ danh tiếng).

Một cách khác để giải quyết vấn đề thứ tự phần tử là sử dụng 'insert' thay vì 'append' khi vẽ. Bằng cách đó, các đường dẫn sẽ luôn được đặt cùng nhau trước các phần tử svg khác (điều này giả sử mã của bạn đã thực hiện nhập () cho các liên kết trước enter () cho các phần tử svg khác).

d3 chèn api: https://github.com/mbostock/d3/wiki/Selilities#insert


1

Tôi đã mất nhiều thời gian để tìm cách điều chỉnh thứ tự Z trong một SVG hiện có. Tôi thực sự cần nó trong bối cảnh của d3.brush với hành vi chú giải công cụ. Để hai tính năng hoạt động tốt với nhau ( http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html ), bạn cần d3.brush là tính năng đầu tiên trong Z-order (Đầu tiên được vẽ trên canvas, sau đó được bao phủ bởi phần còn lại của các phần tử SVG) và nó sẽ ghi lại tất cả các sự kiện của chuột, bất kể điều gì ở trên nó (với chỉ số Z cao hơn).

Hầu hết các bình luận trên diễn đàn đều nói rằng bạn nên thêm d3.brush trước trong mã của mình, sau đó mới đến mã "vẽ" SVG của bạn. Nhưng đối với tôi thì không thể vì tôi đã tải tệp SVG bên ngoài. Bạn có thể dễ dàng thêm cọ vào bất kỳ lúc nào và thay đổi thứ tự Z sau này với:

d3.select("svg").insert("g", ":first-child");

Trong bối cảnh thiết lập d3.brush, nó sẽ giống như sau:

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);

API hàm d3.js insert (): https://github.com/mbostock/d3/wiki/Selices#insert

Hi vọng điêu nay co ich!


0

Phiên bản 1

Về lý thuyết, những điều sau đây sẽ hoạt động tốt.

Mã CSS:

path:hover {
    stroke: #fff;
    stroke-width : 2;
}

Mã CSS này sẽ thêm một nét vào đường dẫn đã chọn.

Mã JS:

svg.selectAll("path").on("mouseover", function(d) {
    this.parentNode.appendChild(this);
});

Mã JS này trước tiên xóa đường dẫn khỏi cây DOM và sau đó thêm nó làm con cuối cùng của cha mẹ của nó. Điều này đảm bảo rằng đường dẫn được vẽ trên tất cả các trẻ em khác của cùng một phụ huynh.

Trên thực tế, mã này hoạt động tốt trong Chrome nhưng bị hỏng trong một số trình duyệt khác. Tôi đã thử nó trong Firefox 20 trên máy Linux Mint của mình và không thể làm cho nó hoạt động. Bằng cách nào đó, Firefox không thể kích hoạt các :hoverkiểu và tôi chưa tìm ra cách khắc phục điều này.


Phiên bản 2

Vì vậy, tôi đã đưa ra một giải pháp thay thế. Nó có thể hơi 'bẩn', nhưng ít nhất nó hoạt động và nó không yêu cầu lặp lại tất cả các phần tử (như một số câu trả lời khác).

Mã CSS:

path.hover {
    stroke: #fff;
    stroke-width : 2;
}

Thay vì sử dụng trình :hoverdò giả, tôi sử dụng một .hoverlớp

Mã JS:

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);
   })

Khi di chuột qua, tôi thêm .hoverlớp vào đường dẫn của mình. Khi di chuột ra, tôi xóa nó. Như trong trường hợp đầu tiên, mã cũng xóa đường dẫn khỏi cây DOM và sau đó thêm nó làm con cuối cùng của cha mẹ của nó.


0

Bạn có thể làm như thế này Khi di chuột qua Bạn có thể kéo nó lên đầu.

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();
});

Nếu bạn muốn đẩy lùi

nodes.on("mouseout",function(){
   d3.select(this).pushElementAsBackLayer();
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.