Сброс перетаскивания с помощью нескольких элементов

#javascript #d3.js #svg #transform

#javascript #d3.js #svg #преобразование

Вопрос:

У меня есть несколько групп svg (каждая из которых содержит круг и текст), которые я перетаскиваю с помощью d3-drag из начальной позиции. У меня есть прямоугольная зона попадания, в которой я хочу одновременно использовать только одну из этих перетаскиваемых групп. Поэтому всякий раз, когда две группы находятся в зоне попадания, я бы хотел, чтобы первая группа, которая была в зоне попадания, исчезала и вновь появлялась в своем исходном положении.

Я пытался сделать это с помощью функции, которая переводит группу обратно в исходное положение, находя текущее положение формы круга и переводя как:

 translate(${-current_x}, ${-current_y})
  

Это переводит группу обратно в положение (0,0), поэтому я должен сместить ее на исходное положение. Я делаю это, устанавливая начальные значения x и y формы круга в качестве атрибутов в элементе circle и включая их в перевод:

 translate(${-current_x   initial_x}, ${-current_y   initial_y})
  

Вот блок моей попытки:

https://bl.ocks.org/interwebjill/fb9b0d648df769ed72aeb2755d3ff7d5

И вот это в виде фрагмента:

     const circleRadius = 40;
    const variables = ['one', 'two', 'three', 'four'];
    const inZone = [];
    
    // DOM elements
    const svg = d3.select("body").append("svg")
      .attr("width", 960)
      .attr("height", 500)
    
    const dragDockGroup = svg.append('g')
      .attr('id', 'draggables-dock');
    
    const dock = dragDockGroup.selectAll('g')
      .data(variables)
      .enter().append("g")
      .attr("id", (d, i) => `dock-${variables[i]}`);

    dock.append("circle")
      .attr("cx", (d, i) => circleRadius * (2 * i   1))
      .attr("cy", circleRadius)
      .attr("r", circleRadius)
      .style("stroke", "none")
      .style("fill", "palegoldenrod");

    dock.append("text")
      .attr("x", (d, i) => circleRadius * (2 * i   1))
      .attr("y", circleRadius)
      .attr("text-anchor", "middle")
      .style("fill", "white")
      .text((d, i) => variables[i]);
    
    const draggablesGroup = svg.append('g')
    	.attr('id', 'draggables');
    
    const draggables = draggablesGroup.selectAll('g')
      .data(variables)
      .enter().append("g")
      .attr("id", (d, i) => variables[i])
      .call(d3.drag()
          .on("start", dragStarted)
          .on("drag", dragged)
          .on("end", dragEnded));

    draggables.append('circle')
      .attr("cx", (d, i) => circleRadius * (2 * i   1))
      .attr("cy", circleRadius)
      .attr("initial_x", (d, i) => circleRadius * (2 * i   1))
      .attr("initial_y", circleRadius)
      .attr("r", circleRadius)
      .style("stroke", "orange")
      .style("fill", "yellowgreen");

    draggables.append("text")
       .attr("x", (d, i) => circleRadius * (2 * i   1))
       .attr("y", circleRadius)
       .attr("text-anchor", "middle")
       .style("fill", "white")
       .text((d, i) => variables[i]);
    
    svg.append('rect')
      .attr("x", 960/2)
      .attr("y", 0)
      .attr("width", 100)
      .attr("height", 500/2)
      .attr("fill-opacity", 0)
      .style("stroke", "#848276")
      .attr("id", "hitZone");
    
    // functions
    function dragStarted() {
      d3.select(this).raise().classed("active", true);
  	}

  	function dragged() {
      d3.select(this).select("text").attr("x", d3.event.x).attr("y", d3.event.y);
      d3.select(this).select("circle").attr("cx", d3.event.x).attr("cy", d3.event.y);
  	}

  function dragEnded() {
    d3.select(this).classed("active", false);
    d3.select(this).lower();
    let hit = d3.select(document.elementFromPoint(d3.event.sourceEvent.clientX, d3.event.sourceEvent.clientY)).attr("id");

    if (hit == "hitZone") {
      inZone.push(this.id);
      if (inZone.length > 1) {
        let resetVar = inZone.shift();
        resetCircle(resetVar);
      }
    }
    
    d3.select(this).raise();
  }

  function resetCircle(resetVar) {
    let current_x = d3.select(`#${resetVar}`)
      .select('circle')
      .attr('cx');

    let current_y = d3.select(`#${resetVar}`)
      .select('circle')
      .attr('cy');

    let initial_x = d3.select(`#${resetVar}`)
      .select('circle')
      .attr('initial_x');

    let initial_y = d3.select(`#${resetVar}`)
      .select('circle')
      .attr('initial_y');

    d3.select(`#${resetVar}`)
      .transition()
        .duration(2000)
        .style('opacity', 0)
      .transition()
        .duration(2000)
        .attr('transform', `translate(${-current_x}, ${-current_y})`)
      .transition()
        .duration(2000)
        .style('opacity', 1);
  }  
  body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }  
  <script src="https://d3js.org/d3.v5.min.js"></script>  

Вот проблемы:

  1. Хотя использование translate(${-current_x}, ${-current_y}) работает, когда я пытаюсь использовать translate(${-current_x initial_x}, ${-current_y initial_y}) , в переводе используются очень большие отрицательные числа (например, translate(-52640, -4640) ).

  2. Во время использования translate(${-current_x}, ${-current_y}) работает, когда я пытаюсь перетащить эту переведенную группу снова, группа немедленно повторяет предыдущую translate(${-current_x}, ${-current_y})

Ответ №1:

Ваш код сталкивается с трудностями, потому что вы размещаете как g элементы, так и дочерние text и circle элементы.

Круги и текст изначально расположены по атрибутам x / y:

 draggables.append('circle')
  .attr("cx", (d, i) => circleRadius * (2 * i   1))
  .attr("cy", circleRadius)

draggables.append("text")
   .attr("x", (d, i) => circleRadius * (2 * i   1))
   .attr("y", circleRadius)
  

События перетаскивания перемещают круги и текст сюда:

 d3.select(this).select("text").attr("x", d3.event.x).attr("y", d3.event.y);
d3.select(this).select("circle").attr("cx", d3.event.x).attr("cy", d3.event.y);
  

И затем мы сбрасываем круги и текст, пытаясь сместить родительский элемент g с помощью преобразования:

 d3.select(`#${resetVar}`).attr('transform', `translate(${-current_x}, ${-current_y})`)
  

Где current_x и current_y — текущие значения x, y для кругов и текста. Мы также сохранили начальные значения x, y для текста, но в целом это становится более сложным, чем необходимо, поскольку у нас есть два конкурирующих набора координат позиционирования.

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

Теперь у нас нет модификации атрибутов x, y / cx, cy и преобразований для позиционирования элементов относительно друг друга. Никаких смещений и преобразование родительского элемента g всегда будет представлять положение круга и текста.

Ниже я отслеживаю исходное преобразование с помощью datum (не атрибута элемента) — обычно я бы использовал свойство datum, но у вас есть необъектные данные, поэтому я просто заменяю datum исходным преобразованием:

 const circleRadius = 40;
const variables = ['one', 'two', 'three', 'four'];
const inZone = [];
    
// DOM elements
const svg = d3.select("body").append("svg")
  .attr("width", 960)
  .attr("height", 500)
    
const dragDockGroup = svg.append('g')
  .attr('id', 'draggables-dock');

// Immovable placemarkers:
const dock = dragDockGroup.selectAll('g')
  .data(variables)
  .enter().append("g")
  .attr("id", (d, i) => `dock-${variables[i]}`);

dock.append("circle")
  .attr("cx", (d, i) => circleRadius * (2 * i   1))
  .attr("cy", circleRadius)
  .attr("r", circleRadius)
  .style("stroke", "none")
  .style("fill", "palegoldenrod");

dock.append("text")
  .attr("x", (d, i) => circleRadius * (2 * i   1))
  .attr("y", circleRadius)
  .attr("text-anchor", "middle")
  .style("fill", "white")
  .text((d, i) => variables[i]);

// Dragables
const draggablesGroup = svg.append('g')
  .attr('id', 'draggables');
    
const draggables = draggablesGroup.selectAll('g')
  .data(variables)
  .enter()
  .append("g")
  .datum(function(d,i) {
     return "translate(" [circleRadius * (2 * i   1),circleRadius] ")";
  })
  .attr("transform", (d,i) => d)
  .attr("id", (d, i) => variables[i])
  .call(d3.drag()
     .on("start", dragStarted)
     .on("drag", dragged)
     .on("end", dragEnded));

draggables.append('circle')
  .attr("r", circleRadius)
  .style("stroke", "orange")
  .style("fill", "yellowgreen");

draggables.append("text")
  .attr("text-anchor", "middle")
  .style("fill", "white")
  .text((d, i) => variables[i]);
    
svg.append('rect')
   .attr("x", 960/2)
   .attr("y", 0)
   .attr("width", 100)
   .attr("height", 500/2)
   .attr("fill-opacity", 0)
   .style("stroke", "#848276")
   .attr("id", "hitZone");
    
 // functions
function dragStarted() {
    d3.select(this).raise();
 }

function dragged() {
   d3.select(this).attr("transform","translate(" [d3.event.x,d3.event.y] ")")
}

function dragEnded() {
  d3.select(this).lower();
  let hit = d3.select(document.elementFromPoint(d3.event.sourceEvent.clientX, d3.event.sourceEvent.clientY)).attr("id");

  if (hit == "hitZone") {
    inZone.push(this.id);
    if (inZone.length > 1) {
    let resetVar = inZone.shift();
    resetCircle(resetVar);
   }
  }
    
  d3.select(this).raise();
}

function resetCircle(resetVar) {
  d3.select(`#${resetVar}`)
   .transition()
     .duration(500)
     .style('opacity', 0)
   .transition()
     .duration(500)
     .attr("transform", (d,i) => d)
   .transition()
     .duration(500)
     .style('opacity', 1);
}
   
 body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }  
 <script src="https://d3js.org/d3.v5.min.js"></script>