#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>
Вот проблемы:
-
Хотя использование
translate(${-current_x}, ${-current_y})
работает, когда я пытаюсь использоватьtranslate(${-current_x initial_x}, ${-current_y initial_y})
, в переводе используются очень большие отрицательные числа (например,translate(-52640, -4640)
). -
Во время использования
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>