it-roy-ru.com

Обновление 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();
    });
67
Evil Closet Monkey

Одним из решений, представленных разработчиком, является «использование оператора сортировки 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
  });
})
46
futurend

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

81
notan3xit

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

this.parentNode.appendChild(this);

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

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

20
swenedo

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

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

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

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

13
meetamit

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

6
evanjmg

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

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

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

4
Ali

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

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

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

4
lk145

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

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

1
mskr182

Версия 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
John Slegers

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

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