Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert svg element coordinates to screen coordinates?

Tags:

javascript

svg

Is there a way to get the screen/window coordinates from a svg element ? I have seen solutions for the other way around like:

function transformPoint(screenX, screenY) {
   var p = this.node.createSVGPoint()
    p.x = screenX
    p.y = screenY
    return p.matrixTransform(this.node.getScreenCTM().inverse())
}

But what i need in my case are the screen coordinates.

Sory if it's an obvious question, but i'm new to svg. Thanks.

like image 514
inna Avatar asked Jan 19 '18 14:01

inna


4 Answers

The code you included in your question converts screen coordinates to SVG coordinates. To go the other way, you have to do the opposite of what that function does.

getScreenCTM() returns the matrix that you need to convert the coordinates. Notice that the code calls inverse()? That is inverting the matrix so it does the conversion in the other direction.

So all you need to do is remove the inverse() call from that code.

var svg = document.getElementById("mysvg");

function screenToSVG(screenX, screenY) {
   var p = svg.createSVGPoint()
    p.x = screenX
    p.y = screenY
    return p.matrixTransform(svg.getScreenCTM().inverse());
}

function SVGToScreen(svgX, svgY) {
   var p = svg.createSVGPoint()
    p.x = svgX
    p.y = svgY
    return p.matrixTransform(svg.getScreenCTM());
}


var  pt = screenToSVG(20, 30);
console.log("screenToSVG: ", pt);

var  pt = SVGToScreen(pt.x, pt.y);
console.log("SVGToScreen: ", pt);
<svg id="mysvg" viewBox="42 100 36 40" width="100%">
</svg>
like image 99
Paul LeBeau Avatar answered Nov 15 '22 13:11

Paul LeBeau


I was playing around with this snippet below when I wanted to do the same (learn which screen coordinates correspond to the SVG coordinates). I think in short this is what you need:

  1. Learn current transformation matrix of the SVG element (which coordinates you are interested in), roughly: matrix = element.getCTM();

  2. Then get screen position by doing, roughly: position = point.matrixTransform(matrix), where "point" is a SVGPoint.

See the snippet below. I was playing with this by changing browser window size and was altering svg coordinates to match those of the div element

// main SVG:
var rootSVG = document.getElementById("rootSVG");
// SVG element (group with rectangle inside):
var rect = document.getElementById("rect");
// SVGPoint that we create to use transformation methods:
var point = rootSVG.createSVGPoint();
// declare vars we will use below:
var matrix, position;
// this method is called by rootSVG after load:
function init() {
  // first we learn current transform matrix (CTM) of the element' whose screen (not SVG) coordinates we want to learn:
  matrix = rect.getCTM();
  // then we "load" SVG coordinates in question into SVGPoint here:
  point.x = 100;  // replace this with the x co-ordinate of the path segment
  point.y = 300;  // replace this with the y co-ordinate of the path segment
  // now position var will contain screen coordinates:
  position = point.matrixTransform(matrix);
  console.log(position)
  // to validate that the coordinates are correct - take these x,y screen coordinates and apply to CSS #htmlRect to change left, top pixel position. You will see that the HTML div element will get placed into the top left corner of the current svg element position.
}
html, body {
	margin: 0;
	padding: 0;
	border: 0;
	overflow:hidden;
	background-color: #fff;	
}
svg {
      position: fixed; 
	  top:0%; 
	  left:0%; 
	  width:100%; 
	  height:100%; 
	  background:#fff;	  
}
#htmlRect {
  width: 10px;
  height: 10px;
  background: green;
  position: fixed;
  left: 44px;
  top: 132px;
}
<body>
  <svg id="rootSVG" width="100%" height="100%" viewbox="0 0 480 800" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="init()">

    <g id="rect">
      <rect id="rectangle" x="100" y="300" width="400" height="150"/>
    </g>

  </svg>
  <div id="htmlRect"></div>
</body>
like image 7
Sergey Rudenko Avatar answered Nov 15 '22 14:11

Sergey Rudenko


Not sure why it hasn't been suggested before, but `Element.getBoundingClientRect() should be enough:

const {
  top,  // x position on viewport (window)
  left, // y position on viewport (window)
} = document.querySelector('rect').getBoundingClientRect()

I think other answers might be derived from a method promoted by Craig Buckler on SitePoint, where he explains using the SVGElement API (instead of getBoudingClientRect, from the - DOM - Element API) to convert DOM to SVG coordinates and vice-versa.

But 1. only DOM coordinates are required here 2. he claims that using getBoundingClientRect when transformations (via CSS or SVG) are applied will return incorrect values to translate to SVG coordinates, but the current specification for getBoundingClientRect takes those transformations into account.

The getClientRects() method, when invoked, must return the result of the following algorithm: [...]

If the element has an associated SVG layout box return a DOMRectList object containing a single DOMRect object that describes the bounding box of the element as defined by the SVG specification, applying the transforms that apply to the element and its ancestors.

Specification: https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface

Support: https://caniuse.com/#feat=getboundingclientrect

like image 3
cdoublev Avatar answered Nov 15 '22 13:11

cdoublev


2020

⚠️ Safari currently has several bugs that make this pretty difficult if you're working with SVGs (or SVG containers) that are transitioning, rotated, or scaled.

getScreenCTM() does not include ancestor scale and rotation transforms in the returned matrix. (If your svgs are neither rotated or scaled, then this is the way to go though.)

However, if you know the ancestor elements that are being scaled and/or rotated, and their transformation values, you can manually fix the matrix provided by getScreenCTM(). The workaround will look something like this:

let ctm = ele.getScreenCTM();

// -- adjust 
let ancestorScale = // track yourself or derive from window.getComputedStyle()
let ancestorRotation = // track yourself or derive from window.getComputedStyle()

ctm = ctm.scale(ancestorScale)
ctm = ctm.rotate(ancestorRotation)

// !! Note: avoid ctm.scaleSelf etc. On some systems the matrix is a true blue SVGMatrix (opposed to a DOMMatrix) and may not support these transform-in-place methods
// --

// repeat 'adjust' for each ancestor, in order from closest to furthest from ele. Mind the order of the scale/rotation transformations on each ancestor.

If you don't know the ancestors... the best I've come up with is a trek up the tree looking for transformations via getComputedStyle, which could be incredibly slow depending on the depth of the tree...

getBoundingClientRect() may return incorrect values when transitioning. If you're not animating things but you are transforming things, then this may be the way to go, though I'm pretty sure it's notably less performant than getScreenCTM. Ideally, insert a very small element into the SVG such that its bounding rect will effectively be a point.

window.getComputedStyles().transform has the same issue as above.

like image 2
Ian Avatar answered Nov 15 '22 14:11

Ian