Обновление SVG Element Z-Index с D3

Что такое эффективный способ принести элемент SVG в начало z-порядка, используя библиотеку D3?

Мой конкретный сценарий - круговая диаграмма, которая выделяет (добавляет 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 оба не работали.

Класс "top" в моем CSS определяется следующим образом:

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

Оператор fill должен убедиться, что я знал, что он включен/выключен правильно. Это.

Я слышал, что использование опции sort является опцией, но я не понимаю, как она будет реализована для приведения элемента "selected" в начало.

UPDATE:

Я исправил свою конкретную ситуацию следующим кодом, который добавляет новую дугу в SVG в событие mouseover, чтобы показать выделение.

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

Ответ 2

Как объясняется в других ответах, SVG не имеет понятия z-индекса. Вместо этого порядок элементов в документе определяет порядок на чертеже.

Помимо переупорядочения элементов вручную, в некоторых ситуациях существует другой способ:

При работе с D3 у вас часто есть определенные типы элементов, которые всегда должны быть нарисованы поверх других типов элементов.

Например, при размещении графиков ссылки всегда должны помещаться под узлами. В более общем плане некоторые фоновые элементы обычно нужно размещать ниже всего остального, в то время как некоторые основные моменты и наложения должны быть размещены выше.

Если у вас такая ситуация, я обнаружил, что создание элементов родительской группы для этих групп элементов - лучший способ. В SVG вы можете использовать для этого элемент g. Например, если у вас есть ссылки, которые должны быть всегда размещены ниже узлов, выполните следующие действия:

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

Теперь, когда вы рисуете свои ссылки и узлы, выбирайте следующим образом (селектор, начинающийся с # ссылается на идентификатор элемента):

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

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

Теперь все ссылки всегда будут добавляться структурно до всех элементов node. Таким образом, SVG покажет все ссылки ниже всех узлов, независимо от того, как часто и в каком порядке вы добавляете или удаляете элементы. Конечно, все элементы одного типа (то есть внутри одного и того же контейнера) будут по-прежнему подчиняться тому порядку, в котором они были добавлены.

Ответ 3

Поскольку SVG не имеет Z-индекса, но использует порядок элементов DOM, вы можете перенести его на передний план:

this.parentNode.appendChild(this);

Вы можете затем, например, используйте insertBefore, чтобы вернуть его на mouseout. Это, однако, требует, чтобы вы могли ориентироваться на сиблинг-node, который должен быть вставлен перед вашим элементом.

DEMO: Взгляните на JSFiddle

Ответ 4

SVG не делает z-index. Z-порядок продиктован порядком элементов SVG DOM в их контейнере.

Насколько я мог судить (и я пробовал это несколько раз в прошлом), D3 не предоставляет методов для отсоединения и повторного подключения одного элемента, чтобы вывести его на передний план или еще что-то еще.

Существует метод .order(), который перетаскивает узлы в соответствии с порядком, который они отображают при выборе. В вашем случае вам нужно перенести один элемент на передний план. Итак, технически вы можете прибегнуть к выбору с нужным элементом спереди (или в конце, не помните, который является самым верхним), а затем называть order() на нем.

Или вы можете пропустить d3 для этой задачи и использовать простой JS (или jQuery), чтобы повторно вставить этот единственный элемент DOM.

Ответ 5

Я реализовал решение futurend в своем коде, и он сработал, но с большим количеством элементов, которые я использовал, это было очень медленно. Здесь альтернативный метод с использованием jQuery, который работал быстрее для моей конкретной визуализации. Он полагается на svgs, который вы хотите, с общим классом (в моем примере класс отмечен в моем наборе данных как d.key). В моем коде есть <g> с классом "местоположения", который содержит все SVG, которые я реорганизую.

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

Поэтому, когда вы наводите указатель на определенную точку данных, код находит все остальные точки данных с элементами SVG DOM с этим конкретным классом. Затем он отделяет и повторно вставляет элементы SVG DOM, связанные с этими точками данных.

Ответ 6

Требуется расширить то, на что @notan3xit ответил, а не написать весь новый ответ (но у меня недостаточно репутации).

Другим способом решения проблемы с порядком элементов является использование "insert", а не "append" при рисовании. Таким образом, пути всегда будут помещаться вместе перед другими элементами svg (это предполагает, что ваш код уже делает enter() для ссылок до ввода() для других элементов svg).

d3 insert api: https://github.com/mbostock/d3/wiki/Selections#insert

Ответ 7

Мне потребовалось много времени, чтобы найти, как настроить Z-порядок в существующем SVG. Мне это действительно нужно в контексте d3.brush с поведением всплывающей подсказки. Чтобы две функции работали вместе (http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html), вам нужно, чтобы d3.brush был первым в Z-порядке ( 1, который будет нарисован на холсте, затем покрыт остальными элементами 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

Надеюсь, это поможет!

Ответ 8

Простым ответом является использование методов упорядочения d3. В дополнение к d3.select('g'). Order() в версии 4 есть .lower() и .raise(). Это изменяет порядок отображения ваших элементов. Пожалуйста, обратитесь к документации за дополнительной информацией - https://github.com/d3/d3/blob/master/API.md#selections-d3-selection

Ответ 9

Версия 1

В теории следующее должно работать нормально.

Код CSS:

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

Этот CSS-код добавит штрих к выбранному пути.

Код JS:

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

Этот код JS сначала удаляет путь из дерева DOM, а затем добавляет его в качестве последнего дочернего элемента его родителя. Это гарантирует, что путь будет нарисован поверх всех других дочерних элементов одного и того же родителя.

На практике этот код отлично работает в Chrome, но ломается в некоторых других браузерах. Я попробовал его в Firefox 20 на моей машине Linux Mint и не смог заставить его работать. Так или иначе, Firefox не запускает стили :hover, и я не нашел способа исправить это.


Версия 2

Итак, я придумал альтернативу. Он может быть немного "грязным", но, по крайней мере, он работает, и он не требует петли по всем элементам (как некоторые другие ответы).

Код CSS:

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

Вместо использования псевдоселектора :hover я использую класс .hover

Код 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);
   })

В режиме mouseover я добавляю класс .hover к своему пути. На выводе я удаляю его. Как и в первом случае, код также удаляет путь из дерева DOM, а затем добавляет его в качестве последнего дочернего элемента его родителя.

Ответ 10

Вы можете сделать это над мышью. Вы можете потянуть его вверх.

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