Обновление Z-индекса элемента SVG с помощью D3

81

Как эффективно вывести 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обоих не помогли.

В моем CSS класс "top" определен следующим образом:

.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();
    });
Злая Обезьяна Шкаф
источник

Ответы:

52

Одно из решений, представленных разработчиком: «используйте оператор сортировки 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
  });
})
будущее
источник
Это не только работает с Z-порядком, но и не нарушает перетаскивание, которое только что началось с поднятым элементом.
Оливер Бок,
3
у меня тоже не работает. a, b, похоже, только данные, а не элемент выбора d3 или элемент DOM
nkint
Действительно, как и для @nkint, не получилось при сравнении a.idи d.id. Решение состоит в том, чтобы напрямую сравнить aи d.
Peace Makes Plenty
Это не будет работать должным образом для большого количества дочерних элементов. Лучше повторно визуализировать поднятый элемент в последующем <g>.
tribalvibes
1
В качестве альтернативы svg:useэлемент может динамически указывать на поднятый элемент. См. Решение stackoverflow.com/a/6289809/292085 .
tribalvibes
91

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

notan3xit
источник
1
Я столкнулся с этим с идеей в голове, что я могу переместить один элемент вверх, когда моей реальной проблемой была организация моих элементов в группы. Если ваша «настоящая» проблема заключается в перемещении отдельных элементов, тогда метод сортировки, описанный в предыдущем ответе, является хорошим решением.
Джон Дэвид Пять
Потрясающе! Спасибо, notan3xit, ты спас мне день! Просто чтобы добавить идею для других - если вам нужно добавить элементы в порядке, отличном от желаемого порядка видимости, вы можете просто создать группы в правильном порядке видимости (даже до добавления к ним каких-либо элементов), а затем добавить элементы к этим группы в любом порядке.
Ольга Гнатенко
1
Это правильный подход. Очевидно, если подумать.
Дэйв
Для меня svg.select ('# links') ... пришлось заменить на d3.select ('# links') ... для работы. Может это поможет другим.
обмен
26

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

this.parentNode.appendChild(this);

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

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

Свенедо
источник
Это было бы отличным решением, если бы Firefox не отображал какие-либо :hoverстили. Я также не думаю, что в этом случае потребуется функция вставки в конце.
Джон Слегерс
@swenedo, ваше решение отлично работает для меня и выводит элемент на первый план. Что касается переноса на задний план, я немного запутался и не знаю, как правильно это написать. Не могли бы вы выложить пример, как можно вернуть предмет на задний план. Благодарю.
Майк Б.
1
@MikeB. Конечно, я только что обновил свой ответ. Я понял, что insertфункция d3, вероятно, не лучший вариант, потому что она создает новый элемент. Мы просто хотим переместить существующий элемент, поэтому insertBeforeвместо этого я использую в этом примере.
swenedo 05
Я пробовал использовать этот метод, но, к сожалению, не работает в IE11. Есть ли у вас способ обхода этого браузера?
Антонио Джованни Скьявоне
13

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

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

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

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

Meetamit
источник
5
Обратите внимание, что в некоторых браузерах есть проблемы с удалением / вставкой элементов внутри обработчика наведения указателя мыши, это может привести к неправильной отправке событий mouseout, поэтому я бы рекомендовал попробовать это во всех браузерах, чтобы убедиться, что он работает так, как вы ожидаете.
Эрик Дальстрем
Разве изменение порядка не изменит и макет моей круговой диаграммы? Я также копаюсь в jQuery, чтобы узнать, как повторно вставлять элементы.
Evil Closet Monkey
Я обновил свой вопрос своим решением, которое я выбрал, которое на самом деле не использует суть исходного вопроса. Если вы видите что-то действительно плохое в этом решении, я приветствую комментарии! Спасибо за понимание исходного вопроса.
Evil Closet Monkey
Кажется разумным подходом, в основном благодаря тому, что наложенная форма не имеет заливки. Если бы это было так, я бы ожидал, что он «украдет» событие мыши в момент его появления. И я думаю, что точка зрения @ErikDahlström все еще остается в силе: это все еще может быть проблемой в некоторых браузерах (в первую очередь IE9). Для дополнительной «безопасности» можно добавить .style('pointer-events', 'none')наложенный путь. К сожалению, это свойство ничего не делает в IE, но все же ....
meetamit
@EvilClosetMonkey Привет ... Этот вопрос набирает много просмотров, и я считаю, что ответ будущего, приведенный ниже, более полезен, чем мой. Итак, возможно, подумайте об изменении принятого ответа на будущий, чтобы он отображался выше.
meetamit
10

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

Evanjmg
источник
1
Это лучший ответ!
Ахмад Карим
4

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

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

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

lk145
источник
4

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

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

d3 вставить api: https://github.com/mbostock/d3/wiki/Selections#insert

Али
источник
1

Мне потребовалось время, чтобы найти, как настроить 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 insert (): https://github.com/mbostock/d3/wiki/Selections#insert

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

mskr182
источник
0

Версия 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класс на свой путь. При наведении курсора удаляю. Как и в первом случае, код также удаляет путь из дерева DOM и затем добавляет его в качестве последнего дочернего элемента своего родителя.

Джон Слегерс
источник
0

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

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();
});
РамиРедди П
источник