d3.js 如何获取组内对象的绝对坐标< g>?

wr98u20j  于 2023-10-19  发布在  其他
关注(0)|答案(6)|浏览(127)

这可能是一个常见问题,所以请随时向我指出另一个答案。这个题目很难搜索。
如果我想使用d3.js来获取在SVG对象中显式声明的属性,或者我使用D3显式放置的属性,我可以使用d3.select轻松获取属性的值。例如,这将打印300:

...
<circle id="mycircle" r="10" cx="100" cy="200">
...
d3.select("#mycircle").attr("cx", 300);
console.log(d3.select("#mycircle").attr("cx"));

如果我没有显式地设置属性的值,而是从<g>组隐式地“设置”属性的值,该怎么办?或者:如何使用代码找出<g>组的中心位置?我想知道<g>里面的东西在<svg>物体的绝对坐标系中的位置。如果我知道<g>在哪里,它在空间中的方向,等等,我就可以计算出它内部的点在哪里。我怎么能这么做呢?
BigBadaboom在对这个问题的回答的评论中指出,继承的不是一对坐标,而是一个transform属性。所以我可以选择一个<g>并获取transform属性的值:

console.log(d3.select("#mygroup").attr("transform"));

它打印,例如:
“旋转(-125.93)平移(0,-25)”
我需要解析它来找出<g>在绝对坐标系中的位置吗?

j1dl9f46

j1dl9f461#

其他人在这里已经提到了SVGLocatable.getBBox(),它对于根据元素自己的局部坐标系抓取元素的边界框很有用。不幸的是,正如您所注意到的,这并没有考虑对元素或其父元素所做的任何转换。
还有一些其他的函数可以帮助你处理这些转换。
SVGLocatable.getScreenCTM()提供了一个SVGMatrix,表示从视口坐标转换到元素的局部坐标所需的变换。这很棒,因为它将考虑应用于它所调用的元素的转换,以及应用于父元素的任何转换。不幸的是,它还考虑了元素在屏幕上的确切位置,这意味着如果你在svg文档之前有内容,甚至只是它周围的一些空白,返回的矩阵将包括作为翻译的空间。
Element.getBoundingClientRect()将允许您说明该空间。如果在SVG文档本身上调用此函数,则可以通过SVG在屏幕上的偏移量来确定。
然后,当你想在坐标系之间转换时,你所要做的就是将两者合并结合起来。这里有一些关于SVGMatrix如何工作的好信息。现在要知道的重要一点是,SVGMatrix是一个具有六个属性abcdef的对象,它们表示如下的转换:

假设你有一个变量svgDoc,它是对svg文档的引用(* 不是d3选择,而是元素本身 *)。然后,您可以创建一个函数,该函数将转换为svg元素elem的坐标系,如下所示。

function convertCoords(x,y) {

  var offset = svgDoc.getBoundingClientRect();

  var matrix = elem.getScreenCTM();

  return {
    x: (matrix.a * x) + (matrix.c * y) + matrix.e - offset.left,
    y: (matrix.b * x) + (matrix.d * y) + matrix.f - offset.top
  };
}

然后,假设你想在elem的中间放一个点,你可以这样做:

var bbox = elem.getBBox(),
    middleX = bbox.x + (bbox.width / 2),
    middleY = bbox.y + (bbox.height / 2);

var absoluteCoords = convertCoords(middleX, middleY);

var dot = svg.append('circle')
  .attr('cx', absoluteCoords.x)
  .attr('cy', absoluteCoords.y)
  .attr('r', 5);

当然,您可能希望泛化convertCoords函数,以便可以传入目标元素,但希望这能让您朝着正确的方向前进。祝你好运!
一个更好的实现是一个工厂,它为任何给定的元素和svg文档上下文生成一个转换函数:

function makeAbsoluteContext(element, svgDocument) {
  return function(x,y) {
    var offset = svgDocument.getBoundingClientRect();
    var matrix = element.getScreenCTM();
    return {
      x: (matrix.a * x) + (matrix.c * y) + matrix.e - offset.left,
      y: (matrix.b * x) + (matrix.d * y) + matrix.f - offset.top
    };
  };
}

如果elemsvgDoc与简单示例相同,则可以如下使用:

var bbox = elem.getBBox(),
    middleX = bbox.x + (bbox.width / 2),
    middleY = bbox.y + (bbox.height / 2);

// generate a conversion function
var convert = makeAbsoluteContext(elem, svgDoc);

// use it to calculate the absolute center of the element
var absoluteCenter = convert(middleX, middleY);

var dot = svg.append('circle')
  .attr('cx', absoluteCenter.x)
  .attr('cy', absoluteCenter.y)
  .attr('r', 5);
cigdeys3

cigdeys32#

@Jshanley的优秀答案实际上非常容易在原始JavaScript(或任何框架)中使用SVGPoint的矩阵转换实现。

/**
* Get a new XY point in SVG-Space, where X and Y are relative to an existing element.  Useful for drawing lines between elements, for example

* X : the new X with relation to element, 5 would be '5' to the right of element's left boundary.  element.width would be the right edge.
* Y : the new Y coordinate, same principle applies
* svg: the parent SVG DOM element
* element: the SVG element which we are using as a base point.
*/
function getRelativeXY(x, y, svg, element){
  var p = svg.createSVGPoint();
  var ctm = element.getCTM();
  p.x = x;
  p.y = y;
  return p.matrixTransform(ctm);
}

标签:Rectangle coordinates after transform
为了找到圆的边,例如:

var leftTangent = getRelativeXY(circle.cx-circle.r, circle.y, svg, circle);
var rightTangent = getRelativeXY(circle.cx+circle.r, circle.y, svg, circle);
var topTangent= getRelativeXY(circle.cx, circle.y-circle.r, svg, circle); 
var bottomTangent= getRelativeXY(circle.cx, circle.y+ circle.r, svg, circle);
var deadCenter= getRelativeXY(circle.cx, circle.y, svg, circle);

诚然,对于一个普通的圆来说,这并不有趣,但是一旦圆被移动或拉伸,它就是一个获取坐标的好工具。
W3规格
Microsoft's more easily understood tutorial

mzsu5hc0

mzsu5hc03#

D3有一个内置的函数来解析svg转换:d3.transform
你可以用它来获取transform的translate数组([x,y]),即:

var transformText = d3.select("#mygroup").attr("transform");
var translate = d3.transform(transformText).translate;  //returns [0,-25]
hkmswyz6

hkmswyz64#

要获取SVG元素的边界,您有两个选项:

  1. getBBox()适用于所有(图形)SVG元素。它在局部坐标空间中获取元素的边界框。如果元素具有transform属性,它将影响bbox,但如果父元素具有transform属性,它将不会反映在返回的bbox中。
    http://www.w3.org/TR/SVG/types.html#InterfaceSVGLocatable
  2. getBoundingClientRect()是一个HTML元素函数,但也适用于SVG元素。它返回元素在屏幕空间中的边界(在应用了所有变换之后)。
    https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect
iszxjhcz

iszxjhcz5#

Nice demo:https://codepen.io/netsi1964/pen/pWjwgP

point = point.matrixTransform(svg.getScreenCTM().inverse())
ohfgkhjo

ohfgkhjo6#

对我来说,“绝对”可能意味着两件事:从SVG元素左上角开始的屏幕像素,或用户空间(即viewBox坐标)。我对后者很感兴趣。对于那些想把matrixTransform的结果“转换”为绝对用户空间坐标的人来说,这对我来说很有效:

function object_coords_to_userspace (x, y, object, svg) {
    // creates an SVGPoint with x,y coordinates inside an SVG element "object"
    // residing in parent svg, returns the location of the point in absolute
    // user space.

    // New point.
    point = svg.createSVGPoint();
    point.x = x;
    point.y = y;

    // New point location on screen where 0,0 is svg top left corner.
    coords_on_screen = point.matrixTransform(object.getCTM());

    // Convert screen coords to absolute userspace (i.e. respecting viewBox and svg element height and width).
    vphpx = svg.height.animVal.value;
    vpwpx = svg.height.animVal.value;


    // I thought I would need to implement this dumpster fire
    // (https://www.w3.org/TR/SVG/coords.html#ComputingAViewportsTransform) to
    // get the viewPort dimensions (as opposed to the viewBox).  If I use
    // preserveAspectRatio=none, the results are correct because the viewport
    // shows the viewBox exactly.  Otherwise in general the viewport shows the
    // viewBox /plus/ some in order to make up the space and preserve the
    // aspect ratio (because I'm using the default "xMidyMid meet" value for
    // preserveAspectRatio.  Was hoping the <svg> element would have a simple
    // way to get the transform from screen to userspace!?, however I have
    // managed to find that the svg element's getCTM().inverse() produces a way
    // to transform screen pixels x,y (relative to the viewports top-left
    // corner) into userspace coordinates.  Phew!
    point2 = svg.createSVGPoint();
    point2.x = 0;
    point2.y = 0;
    res = point2.matrixTransform(svg.getCTM().inverse())
    vpl = res.x;
    vpt = res.y;
    
    point3 = svg.createSVGPoint();
    point3.x = svg.clientWidth;
    point3.y = svg.clientHeight;
    res = point3.matrixTransform(svg.getCTM().inverse())
    
    vpw = res.x - vpl;
    vph = res.y - vpt;
    
    return {x: coords_on_screen.x/vpwpx*vpw + vpl, y: coords_on_screen.y/vphpx*vph + vpt}

相关问题