Подтвердить что ты не робот

Получение экранных позиций узлов D3 после преобразования

Я пытаюсь получить положение экрана node после того, как макет был преобразован d3.behavior.zoom(), но мне не очень повезло. Как я могу получить фактическую позицию node в окне после перевода и масштабирования макета?

mouseOver = function(node) {
  screenX = magic(node.x); // Need a magic function to transform node
  screenY = magic(node.y); // positions into screen coordinates.
};

Любое руководство будет оценено.

EDIT: "node" выше - макет силы node, поэтому свойства x и y задаются симуляцией и остаются постоянными после того, как симуляция останавливается, независимо от того, какой тип преобразования применяется.

EDIT: стратегия, которую я использую для преобразования SVG, происходит из поведения d3-масштабирования, которое описано здесь: SVG Geometric Масштабирование.

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
  .append("g");

svg.append("rect")
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height);

svg.selectAll("circle")
    .data(data)
  .enter().append("circle")
    .attr("r", 2.5)
    .attr("transform", function(d) { return "translate(" + d + ")"; });

function zoom() {
  svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}

Это довольно просто. d3 позволяет переносить события панорамирования и масштабирования в обработчик, который применяет преобразования к элементу контейнера с помощью атрибута преобразования.

EDIT: Я работаю над проблемой, используя координаты мыши вместо координат node, так как меня интересует позиция node, когда node наводится на указатель мыши. Это не совсем то, что мне нужно, но оно работает по большей части и лучше, чем ничего.

EDIT: решение заключалось в том, чтобы получить текущую матрицу преобразования элемента svg с элементом element.getCTM(), а затем использовать ее для смещения координат x и y в относительное состояние экрана. См. Ниже.

4b9b3361

Ответ 1

Вы можете попробовать node.getBBox(), чтобы получить позиции пикселей в узком ограничивающем прямоугольнике вокруг фигур node после применения любого transform. Подробнее см. Здесь: ссылка.

EDIT:

getBBox работает не так, как я думал. Поскольку прямоугольник определен в терминах преобразованного координатного пространства, он всегда относится к родительскому <g> и поэтому всегда будет одинаковым для содержащихся фигур.

Существует еще одна функция под названием element.getBoundingClientRect, которая, как представляется, довольно широко поддерживается и возвращает ее прямоугольник в пиксельной позиции относительно левого верхнего угла порта просмотра браузера, Это может приблизиться к тому, что вы хотите, без необходимости напрямую связываться с матрицей преобразования.

Ответ 2

Похоже, что решение моего исходного вопроса выглядит примерно так:

(Обновлено для поддержки преобразований вращения.)

// The magic function.
function getScreenCoords(x, y, ctm) {
    var xn = ctm.e + x*ctm.a + y*ctm.c;
    var yn = ctm.f + x*ctm.b + y*ctm.d;
    return { x: xn, y: yn };
}

var circle = document.getElementById('svgCircle'),
cx = +circle.getAttribute('cx'),
cy = +circle.getAttribute('cy'),
ctm = circle.getCTM(),
coords = getScreenCoords(cx, cy, ctm);
console.log(coords.x, coords.y); // shows coords relative to my svg container

В качестве альтернативы это также можно сделать, используя свойства перевода и масштабирования из d3.event(если преобразования вращения не нужны):

// This function is called by d3 zoom event.
function zoom() {

// The magic function - converts node positions into positions on screen.    
function getScreenCoords(x, y, translate, scale) {
    var xn = translate[0] + x*scale;
    var yn = translate[1] + y*scale;
    return { x: xn, y: yn };
}

// Get element coordinates and transform them to screen coordinates.
var circle = document.getElementById('svgCircle');
cx = +circle.getAttribute('cx'),
cy = +circle.getAttribute('cy'),
coords = getScreenCoords(cx, cy, d3.event.translate, d3.event.scale);
console.log(coords.x, coords.y); // shows coords relative to my svg container

  // ...
}

EDIT: я нашел, что приведенная ниже форма функции является наиболее полезной и общей, и, похоже, она встает там, где getBoundingClientRect падает. Более конкретно, когда я пытался получить точные позиции SVG node в проекте компоновки сил D3, getBoundingClientRect произвел неточные результаты, в то время как ниже метод вернул тесные координаты элемента circle в нескольких браузерах.

(Обновлено для поддержки преобразований вращения.)

// Pass in the element and its pre-transform coords
function getElementCoords(element, coords) {
    var ctm = element.getCTM(),
    x = ctm.e + coords.x*ctm.a + coords.y*ctm.c,
    y = ctm.f + coords.x*ctm.b + coords.y*ctm.d;
    return {x: x, y: y};
};

// Get post-transform coords from the element.
var circle = document.getElementById('svgCircle'),
x = +circle.getAttribute('cx'),
y = +circle.getAttribute('cy'),
coords = getElementCoords(circle, {x:x, y:y});

// Get post-transform coords using a 'node' object.
// Any object with x,y properties will do.
var node = ..., // some D3 node or object with x,y properties.
circle = document.getElementById('svgCircle'),
coords = getElementCoords(circle, node);

Функция работает, получая матрицу преобразования элемента DOM, а затем используя поворот матрицы, масштабирование и перевод информации, чтобы вернуть координаты посттрансформации данного объекта node.