I have various svg rects on a web page on which a transform is applied in the form :
transform="translate(418 258) rotate(-45.2033 15 18) scale(2.5 2.5)"
I need to get the x,y coordinates of the 4 vertices of each rect after the transform is applied.
Here is an exemple of code :
<g transform="translate(418 258) rotate(-45.25 15 18) scale(2.5 2.5)">
<rect id="box" x="0" y="0" width="31" height="37" style="fill:none;stroke:rgb(102, 102, 102);stroke-width:1.5px;">
</rect>
</g>
I have tried the following formula in plain js :
x' = x * cos(angle) + y * sin(angle)
y' = -x * sin(angle) + y * cos(angle)
but the results are slightly different from the svg display in various browsers.
I guess this can be done using js/svg primitives, but I don't know how, and didn't find any code example. Perhaps changing the rects into paths after transform would do the trick...
Last but not least, I'm using jquery but not d3.
Thanks in advance.
You can read the transform attribute and convert it to a matrix.
Then for each of the rectangle's four corners, you can use that matrix to calculate the transformed corner locations.
See the following demo.
This demo assumes that there is an element with id of "box", and that the transform you care about is just the one on the parent <g> element. If your circumstances are more complex than that, then you will need to do some more work on this code.
// Get a reference to the "box" rect element
var box = document.getElementById("box");
// Get its x, y, width and height
var bx = box.x.baseVal.value;
var by = box.y.baseVal.value;
var bw = box.width.baseVal.value;
var bh = box.height.baseVal.value;
// Get the box's parent element (the <g>)
var parentGroup = box.parentNode;
// Read the transform attribute and convert it to a transform matrix object
var transformMatrix = parentGroup.transform.baseVal.consolidate().matrix;
// For each of the rectangle's four corners, use the transform matrix to calculate its new coordinates
console.log("point 1 = ", doPoint(bx, by));
console.log("point 2 = ", doPoint(bx + bw, by));
console.log("point 3 = ", doPoint(bx + bw, by + bh));
console.log("point 4 = ", doPoint(bx, by + bh));
function doPoint(x, y)
{
// We need a reference to the <svg> element for the next step
var svg = box.ownerSVGElement;
// Create an SVGPoint object with the correct x and y values
var pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
// Use the "matrixTransform()" method on SVGPoint to apply the transform matrix to the coordinate values
pt = pt.matrixTransform(transformMatrix);
// Return the updated x and y values
return {x: pt.x, y: pt.y};
}
<svg>
<g transform="translate(418 258) rotate(-45.25 15 18) scale(2.5 2.5)">
<rect id="box" x="0" y="0" width="31" height="37" style="fill:none;stroke:rgb(102, 102, 102);stroke-width:1.5px;">
</rect>
</g>
</svg>
Related
I have searched a lot in the SO and google but couldn't find a solution for my specific problem.
I am creating a triangle with the help of three lines in svg. I have no problem creating it but now I also want to show angle arc between the sides. Just few inches above the vertex where two lines are merging. This is the path that I am using to create angle kinda arc using path
<path d="M324,141 A50,50 0 0,1 336,164" stroke="#ef00ff" stroke-width="3" fill="none"></path>
I have x and y for the vertex.
Three lines I am using have name as line1,line2 and line3
The trianlge being made with mouse move and down events. So its dynamic thats why I have to get the coordinates just few inches away from the vertex in any of the two lines so that I can put them in my arc path.
If anyone likes to know html for triangle let me know. Its just three lines connecting each other.
I am not that much experienced but still learning.
Thank you.
This is how I would do it for one vertex [1]. You will need to loop the pts array and do the same for each point.
please read the comments in my code.
//the radius for the pink arc
let r = 15;
//the points to draw the triangle
let pts = [
[2.75,-45],[38.97,22.5],[-38.97,22.5]
]
//calculate the angle of the first line
let dx1 = pts[0][0] - pts[1][0];
let dy1 = pts[0][1] - pts[1][1];
let a1 = Math.atan2(dy1,dx1);
//calculate the move to point for the arc
let p1 = {
x:pts[1][0]+r*Math.cos(a1),
y:pts[1][1]+r*Math.sin(a1)
}
//calculate the angle of the second line
let dx2 = pts[2][0] - pts[1][0];
let dy2 = pts[2][1] - pts[1][1];
let a2 = Math.atan2(dy2,dx2)
//calculate the end point for the arc
let p2 = {
x:pts[1][0]+r*Math.cos(a2),
y:pts[1][1]+r*Math.sin(a2)
}
//build the d attribute for the arc
let d = `M${p1.x},${p1.y}A${r},${r} 0 0 0 ${p2.x},${p2.y}`
//set the d attribute for the arc
arc.setAttributeNS(null,"d",d)
svg{border:solid;}
<svg viewBox="-50 -50 100 90" width="200">
<polygon id="poly" points="2.75,-45 38.97,22.5 -38.97,22.5" stroke="black" fill="none"></polygon>
<path id="arc" stroke="#ef00ff" stroke-width="3" fill="none"></path>
</svg>
I just came across a weired case of bouncing box calculation and it seems I did not grasp the whole truth yet.
First of all, a bounding box is defined as the tightest box, an untransformed element can be enclosed with.
I always was under the impression, that for groups, that means, that it gets basically the union of the bounding box of all children.
However, today I came across this:
<g id="outer">
<g id="inner" transform="translate(100, 100)">
<rect x="0" y="0" width="100" height="100" />
</g>
</g>
The bounding boxes of the elements are as follows:
rect: x: 0, y: 0, w: 100, h: 100
#inner: x: 0, y: 0, w: 100, h: 100
#outer: x: 100, y: 100, w: 100, h: 100
My expectation would have been, that all boxes are the same but as you can see, the outer box is NOT the union of the inner elements (in that case it would equal the #inner's bbox). Instead it takes into account the transformation of the inner elements.
So, is it right to say, that the bbox of a group is the union of the TRANSFORMED bbox's of its children? Or more programatically said, the union of all getBoundingClientRect calls (assuming that scroll is 0 because getCoundingClientRect ignores scroll)?
I would really appreciate a link pointing me to the correct part of the specs.
The bounding box returned by getBBox is the box in the element's transformed coordinate system
Returns the tight bounding box in current user space (i.e., after application of the ‘transform’ attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping, masking and filter effects)...
The outer SVG element has a different co-ordinate system. I.e. where it places the origin is not the same as the inner <g> element because of the inner element's transform.
getBoundingClientRect operates in the global co-ordinate system however.
In this demo the red polygon represents the #outer BBox during an animation where the rect is rotating.
const SVG_NS = 'http://www.w3.org/2000/svg';
let o = outer.getBBox()
let i = inner.getBBox()
let BBpoly = drawBBox(o);
function drawBBox(bb){
let p = [{x:bb.x,y:bb.y},
{x:bb.x+bb.width,y:bb.y},
{x:bb.x+bb.width,y:bb.y+bb.height},
{x:bb.x,y:bb.y+bb.height}];
let BBpoly = drawPolygon(p, BBoxes);
return BBpoly;
}
function drawPolygon(p, parent) {
let poly = document.createElementNS(SVG_NS, 'polygon');
let ry = [];
for (var i = 0; i < p.length; i++) {
ry.push(String(p[i].x + ", " + p[i].y));
}
var points = ry.join(" ");
poly.setAttributeNS(null, 'points', points);
parent.appendChild(poly);
return poly;
}
function updatePolygon(p,poly){
let ry = [];
for (var i = 0; i < p.length; i++) {
ry.push(String(p[i].x + ", " + p[i].y));
}
var points = ry.join(" ");
poly.setAttributeNS(null, 'points', points);
}
let a = 0;
function Frame(){
requestAnimationFrame(Frame);
inner.setAttributeNS(null,"transform", `rotate(${a}, 120,120)`)
let bb = outer.getBBox()
let p = [{x:bb.x,y:bb.y},
{x:bb.x+bb.width,y:bb.y},
{x:bb.x+bb.width,y:bb.y+bb.height},
{x:bb.x,y:bb.y+bb.height}];
updatePolygon(p,BBpoly);
a++
}
Frame()
svg{border:1px solid; width:300px;}
polygon{fill:none; stroke:red; }
<svg viewBox="0 0 250 250">
<g id="BBoxes"></g>
<g id="outer">
<g id="inner">
<rect x="70" y="70" width="100" height="100" />
</g>
</g>
</svg>
I have a SVG element with x and y set, but I'm also translating it by a certain vector using transform="translate(a, b)", which changes the co-ordinates it's rendered to but obviously doesn't update its x and y attributes. Is there a way to get the actual co-ordinates, which in this case would be x + a and y + b, without having to directly parse the values out of the transform attribute?
Not that this is a D3-specific question, but my code looks like this:
svg.selectAll(selector)
.attr("x", x)
.attr("y", y)
.attr("width", width)
.attr("height", height)
.attr("transform", `translate(${a}, ${b})`);
OK, this is not really D3 related, but pure SVG/javascript:
I use this function here that I call "flatten", basically you want to reset matrix to non-transformed one (matrix(1 0 0 1 0 0)) and update path points with their sort of flattened values:
flattenShape(item, matrix) {
let points = item.pathPoints;
let l = points.length;
for (let i = 0; i<l; i++) {
let cache = this.mainSVG.createSVGPoint();
cache.x = points[i].x;
cache.y = points[i].y;
cache = cache.matrixTransform(matrix);
points[i].x = cache.x;
points[i].y = cache.y;
}
item.d = this.constructPath(points);
item.transform = "matrix(1 0 0 1 0 0)";
};
item - your SVG element, matrix - you need to get the actual SVGMatrix of the element in question. I get it using:
let matrix = YOUR_SVG_ELEMENT.transform.baseVal.consolidate().matrix;
So my approach is maybe too specific but generally speaking:
consolidate matrix of the SVG element that you are transforming.
use SVGPoint to perform: matrixTransform(matrix) for each coordinate in question.
reset transform attribute to initial state e.g. matrixTransform(matrix)
You can use the getBoundingClientRect() method to get the position of the node.
Here's a snippet showing two rects with one of them translated:
var svgXY = d3.select('svg').node().getBoundingClientRect();
var rect1 = d3.select('rect#test1').node().getBoundingClientRect();
console.log('Rect 1: { top: ' + (rect1.top-svgXY.top) + ', left: ' + (rect1.left-svgXY.left) + '}');
var rect2 = d3.select('rect#test2').node().getBoundingClientRect();
console.log('Rect 2: { top: ' + (rect2.top-svgXY.top) + ', left: ' + (rect2.left-svgXY.left) + '}');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<svg width="300" height="200">
<rect x="20" y="40" fill="red" width="100" height="40" id="test1"></rect>
<rect x="20" y="40" fill="green" width="100" height="40" transform="translate(40, 30)" id="test2"></rect>
</svg>
Or if you're using jQuery, you can get the position by using $('rect#test1').position().
Hope this helps.
Edit:
body had a margin of 8px by default and hence x was equal to 28. I've added the CSS and check out the snippet now.
I am working in SVG tags using javascript. I tried to get group tag <g> midpoint in svg. Is it possible to get mid point value of group tag using javascript?
Here's my demo group tag <g>
<g id="object_7" transform="translate(573,703) scale(0.5,0.51)" style="pointer-events:inherit">
<path d="m-40,-19l3,-3l74,0l3,3l0,37l-3,3l-74,0l-3,-3l0,-37z" id="uid127" stroke-linejoin="round" stroke-linecap="round" fill="#1e1d19" stroke="#000000"/>
<path d="m-9,21l4,2l10,0l4,-2" id="uid129" stroke-linejoin="round" stroke-linecap="round" fill-opacity="0" fill="none" stroke="#000"/>
<path d="m-40,-19l3,-3l74,0l3,3l-77,40l-3,-3l0,-37z" id="uid131" stroke-linejoin="round" stroke-linecap="round" fill-opacity="0.12" fill="#000000"/>
</g>
Here I need to get midpoint point of group tag. I used to get mouse co-ordinates for getting center of x and y position in group tag, but I did not achieve it. Can anyone please guide me?
You can get the bounding box of the <g> element by getting a reference to it and calling the function getBBox().
var bbox = document.getElementById("object_7").getBBox();
Note however that this is the union of all the bounding boxes of the group's children. If the group has a transform, it is not reflected in the bbox value. If you are adding elements to the group, this is probably the one you want.
If you want the bounds of the object in screen space, then you can get the group element's transform and apply it to the centre point you have calculated.
var ctm = document.getElementById("object_7").getCTM()
// Calculate the centre of the group
var cx = bbox.x + bbox.width/2;
var cy = bbox.y + bbox.height/2;
// Transform cx,cy by the group's transform
var pt = document.getElementById("mysvg").createSVGPoint();
pt.x = cx;
pt.y = cy;
pt = pt.matrixTransform(ctm);
// centre point in screen coordinates is in pt.x and pt.y
Demo here
If you want to get absolute middle point/position of g tag in screen:
let el = document.getElementById("object_7")
let midX = (el.getBoundingClientRect().left + el.getBoundingClientRect().right) / 2
let midY = (el.getBoundingClientRect().top + el.getBoundingClientRect().bottom) / 2
It also works for other svg elements.
I'm trying to manipulate with mouse SVG path which represents symbol of electronics resistor. This symbol requires manipulation with end of the "leads" (if you can picture real resistor); therefore I am trying to achieve draging 1st point arround (2nd is still there) and to all points of path to behave proportionally in when drag the 1st point on new coordinates.
Try to group, try with trigonometry functions...so code is like:
`<g id="r" > <!-- R - group for symbol of electronics resistor -->
<path d="M 200 20 v80 h30 v150 h-60 v-150 h30 m 0 150 v80 "
fill="none" stroke="blue" stroke-width="5" />
<circle cx="200" cy="20" r="10"
fill="blue" />
<circle cx="200" cy="330" r="10"
fill="blue"/>
</g>`
Please, help.
I think I've made roughly what you want: http://dl.dropbox.com/u/169269/resistor.svg
Click and drag on the resistor to scale and rotate it to that mouse position. In this version, the line thickness and circles also scale, but it shouldn't be too difficult to avoid that.
It does require trigonometry and transformations. The key part is the drag function, which I explain in more detail at: http://www.petercollingridge.co.uk/interactive-svg-components/draggable-svg-element
function drag(evt)
{
if(selected_element != 0)
{
var resistor_x = 200;
var resistor_y = 100;
var mouse_x = evt.pageX;
var mouse_y = evt.pageY;
dx = resistor_x - mouse_x;
dy = resistor_y - mouse_y;
d = Math.sqrt(dx*dx + dy*dy); // Find distance to mouse
theta = 90+Math.atan2(dy, dx)*180/Math.PI; //Find angle to mouse in degrees
transform = "translate(200, 100) rotate(" + theta + ") scale(" + d/310 + ")" ;
selected_element.setAttributeNS(null, "transform", transform);
}
}
Note that this code assumes the resistor is 310 pixels long and rotating about (200, 100). Scaling and rotation transformations work centred on (0,0), so you should draw the resistor with the static point at (0,0) and then translate it to where you want.