Оновлення ZG-індексу SVG за допомогою D3


81

Який ефективний спосіб перенести елемент SVG у верхню частину z-порядку, використовуючи бібліотеку D3?

Мій конкретний сценарій - це кругова діаграма, яка виділяє (додаючи a 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 {
    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.
Мир робить достатком

Це не буде працювати належним чином для великої кількості дочірніх елементів. Краще відтворити піднятий елемент у наступному <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 покаже всі посилання під усіма вузлами, незалежно від того, як часто і в якому порядку ви додаєте або видаляєте елементи. Звичайно, всі елементи одного типу (тобто всередині одного контейнера) все одно будуть підпорядковані порядку, в якому вони були додані.


1
Я зіткнувся з цим, маючи в голові ідею, що я можу перемістити один предмет вгору, коли моєю справжньою проблемою була організація моїх елементів у групи. Якщо вашою "справжньою" проблемою є переміщення окремих елементів, то хорошим способом буде метод сортування, описаний у попередній відповіді.
Джон Девід П'ять

Чудово! Дякую, notan3xit, ти врятував мій день! Просто щоб додати ідею для інших - якщо вам потрібно додати елементи в порядку, відмінному від бажаного порядку видимості, ви можете просто створити групи у відповідному порядку видимості (навіть до того, як додати будь-які елементи до них), а потім додати елементи до них групи в будь-якому порядку.
Ольга Гнатенко

1
Це правильний підхід. Очевидно, як тільки ти задумаєшся.
Дейв

Для мене svg.select ('# посилання') ... довелося замінити на d3.select ('# посилання') ... для того, щоб працювати. Можливо, це допоможе іншим.
обмін

26

Оскільки SVG не має Z-індексу, але використовує порядок елементів DOM, ви можете вивести його наперед, виконавши:

this.parentNode.appendChild(this);

Тоді ви можете, наприклад, використовувати insertBefore, щоб повернути його знову mouseout. Однак це вимагає, щоб ви мали змогу націлити вузол брата або сестри, до якого ваш елемент слід вставити раніше.

DEMO: Погляньте на цей JSFiddle


Це було б чудовим рішенням, якби не Firefox не зумів відтворити будь-які :hoverстилі. Я також не думаю, що для цього випадку буде потрібна функція "вставки" в кінці.
Джон Слегерс

@swenedo, твоє рішення чудово працює для мене і виводить елемент на перший план. Щодо того, щоб віднести його ззаду, я трохи складений і не знаю, як правильно це написати. Чи можете ви, будь ласка, опублікувати приклад, як ви можете віднести предмет назад? Дякую.
Mike B.

1
@MikeB. Звичайно, я щойно оновив свою відповідь. Я зрозумів, що insertфункція d3, мабуть, не найкращий шлях, оскільки вона створює новий елемент. Ми просто хочемо перемістити існуючий елемент, тому я використовую insertBeforeзамість цього приклад.
swenedo

Я намагався використовувати цей метод, але, на жаль, я не працюю в IE11. Чи є у вас обхід для цього браузера?
Антоніо Джованні Скіавоне

13

SVG не робить z-індекс. Z-порядок диктується порядком елементів SVG DOM в їх контейнері.

Наскільки я міг зрозуміти (а я вже кілька разів пробував це раніше), D3 не надає методів для від'єднання та повторного приєднання одного елемента, щоб вивести його на передню частину або щось інше.

Існує .order()метод , який здійснює перестановку вузлів відповідно до порядку, в якому вони відображаються у виділенні. У вашому випадку вам потрібно винести один елемент спереду. Отже, технічно ви можете вдатися до виділення з потрібним елементом попереду (або в кінці, не пам’ятаєте, який найвищий), а потім зателефонувати order()йому.

Або ви можете пропустити d3 для цього завдання і використовувати звичайний JS (або jQuery), щоб повторно вставити цей єдиний елемент DOM.


5
Зверніть увагу, що в деяких браузерах існують проблеми з видаленням / вставкою елементів всередині обробника наведення курсора, це може призвести до того, що події виведення миші не надсилаються належним чином, тому я рекомендую спробувати це у всіх браузерах, щоб переконатися, що він працює так, як ви очікуєте.
Ерік Далстрем

Чи не зміна порядку також змінить макет моєї кругової діаграми? Я також копаюся навколо 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


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, пов’язані з цими точками даних.


4

Хотіли розширити те, що відповів @ notan3xit, а не виписати цілком нову відповідь (але у мене недостатньо репутації).

Інший спосіб вирішити проблему порядку елементів - це використовувати "вставляти", а не "додавати" під час малювання. Таким чином, шляхи завжди будуть розміщені разом перед іншими елементами svg (це передбачає, що ваш код вже робить enter () для посилань перед enter () для інших елементів 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

Сподіваюся, це допомагає!


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();
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.