Обновление 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 это вариант, но мне неясно, как это будет реализовано для того, чтобы вывести "выбранный" элемент наверх.

ОБНОВИТЬ:

Я исправил мою конкретную ситуацию с помощью следующего кода, который добавляет новую дугу в 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();
    });

11 ответов

Решение

Одним из решений, представленных разработчиком, является "использование оператора сортировки 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-индекса. Вместо этого порядок элементов в документе определяет порядок на чертеже.

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

Работая с 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

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

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

this.parentNode.appendChild(this);

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

ДЕМО: взгляните на эту JSFiddle

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

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

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

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

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

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

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

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

Хотелось больше рассказать о том, что ответил @notan3xit, а не выписывать полностью новый ответ (но мне не хватает репутации).

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

D3 Вставьте API: https://github.com/mbostock/d3/wiki/Selections

Мне понадобились целые годы, чтобы найти способ настроить 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);

API функции вставки d3.js: https://github.com/mbostock/d3/wiki/Selections

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

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

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

Я решил это, используя функцию повышения.

      const raise = (d) => {
  d3.select(d).raise()
}

А в компоненте, который нужно поднимать при наведении (вместе со всеми его дочерними элементами, просто поместите this.

      .on("mouseover", (d) => raise(d.srcElement.parentNode))

В зависимости от вашей структуры, возможно, parentNode не нужен. В этом примере они использовали «это», но это не сработало в React. https://codepen.io/_cselig/pen/KKgOppo

Версия 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);
   })

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

Другие вопросы по тегам