Creating an SVG circle relative to a path in Javascript [duplicate] - javascript

This question already has answers here:
jquery's append not working with svg element?
(17 answers)
Closed 1 year ago.
How do you create a circle at the centerpoint of a filled path, in an SVG canvas, from generic Javascript or jQuery?
I've tried:
var path = $('#path123')[0];
var bb = path.getBBox();
var cx = bb.x + bb.width/2;
var cy = bb.y + bb.height/2;
$('svg').append('<circle cx="'+cx+'" cy="'+cy+'" r="40" stroke="black" stroke-width="3" fill="red" />');
but this doesn't seem to do anything, as I can't see any circle created.

You cannot manipulate an svg with code directly. You need to create a new node and insert it:
var pth = $('#path123')[0];
var bb = pth.getBBox();
var cx = bb.x + bb.width/2;
var cy = bb.y + bb.height/2;
let c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx', cx);
c.setAttribute('cy', cy);
c.setAttribute('r', 40);
c.setAttribute('fill', "red");
c.setAttribute('stroke', 'black');
c.setAttribute('stroke-width', 3);
$('svg')[0].insertBefore(c, pth);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg width="300" height="300">
<path id="path123" d="M 100 100 l 50 50 l -50 0 Z" />
</svg>

Jquery add the element on DOM but not on screen:
$(document).ready(function() {
var path = $('#path123')[0];
var bb = path.getBBox();
var cx = bb.x + bb.width / 2;
var cy = bb.y + bb.height / 2;
var obj = document.createElementNS("http://www.w3.org/2000/svg", "circle");
obj.setAttributeNS(null, "cx", cx);
obj.setAttributeNS(null, "cy", cy);
obj.setAttributeNS(null, "r", 40);
obj.setAttributeNS(null, "stroke", "black");
obj.setAttributeNS(null, "stroke-width", 3);
obj.setAttributeNS(null, "fill", "red");
$("svg")[0].append(obj,pth);
});
Another technich is to include the svg inside div for example and refresh it after appending svg:
<div id="divsvg"
<svg width="300" height="300">
<path id="path123" d="........." />
</svg>
</div>
$("svg").append('<circle ....... fill="red"/>');
$("#divsvg").html($("#divsvg").html());

Related

getBoundingClientRect() returns inaccurate values for complex SVG's in Chrome

I'm trying to calculate the bounding box of transformed SVG elements and for that I'm using getBoundingClientRect() and mapping the x and y values to SVG coordinates. However, this function seems to produce wrong outputs in Chrome and Edge when the shape has curves and a rotation. In the other hand, Firefox is able to produce the expected result.
Here's an example.
<svg height="600" width="600">
<g transform="rotate(-50, 240, 174)" fill="#A1B6FF">
<path transform="translate(100, 100)"
d="M0, 0 Q 140 128.76 280 0 v 148 Q 140 276.76 0 148 v -148z">
</path>
</g>
</svg>
Is there any way to achieve this with more precision like Firefox?
I deleted my previous answer as it was plain wrong, hope this is a better one:
<div>
<svg id="svg" width="600" height="600" version="1.1" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg">
<g id="svgElem" transform="rotate(-50, 240, 174)" fill="#A1B6FF">
<path transform="translate(100, 100)"
d="M0, 0 Q 140 128.76 280 0 v 148 Q 140 276.76 0 148 v -148z">
</path>
</g>
</svg>
</div>
<script type="text/javascript">
let svgElem = document.getElementById('svgElem');
let bBox = svgElem.getBBox();
console.dir(bBox);
</script>
The SVGRect returned by getBBox is identical Firefox/Chromium.
However as stated here on MDN
The returned value is a SVGRect object, which defines the bounding box. This value is irrespective of any transformation attribute applied to it or the parent elements
So you always get the bounding box of the svg before tranforms are applied this way. If you use getBoundingClientRect to get a DOMRect you will find out that Chrome seems to just apply the transforms on the original bounding rect and then calculate the bounding box of that.
You would achieve the same with something like this (more or less useless code just for illustration):
<script type="text/javascript">
const svg = document.getElementById('svg');
let svgElem = document.getElementById('svgElem');
const bBox = svgElem.getBBox(); // MDN: The returned value is a SVGRect object, which defines the bounding box. This value is irrespective of any transformation attribute applied to it or the parent elements
console.dir(bBox);
const boundingClientRect = svgElem.getBoundingClientRect();
console.dir(boundingClientRect);
// create a rect without transforms
const rect1 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect1.setAttribute('x', bBox.x);
rect1.setAttribute('y', bBox.y);
rect1.setAttribute('width', bBox.width);
rect1.setAttribute('height', bBox.height);
rect1.setAttribute('fill', '#00ff0040');
svg.appendChild(rect1);
const ctm = svgElem.getCTM();
const topLeftX = ctm.a * bBox.x + ctm.c * bBox.y + ctm.e;
const topLeftY = ctm.b * bBox.x + ctm.d * bBox.y + ctm.f;
const topRightX = ctm.a * (bBox.x + bBox.width) + ctm.c * bBox.y + ctm.e;
const topRightY = ctm.b * (bBox.x + bBox.width) + ctm.d * bBox.y + ctm.f;
const bottomLeftX = ctm.a * bBox.x + ctm.c * (bBox.y + bBox.height) + ctm.e;
const bottomLeftY = ctm.b * bBox.x + ctm.d * (bBox.y + bBox.height) + ctm.f;
const bottomRightX = ctm.a * (bBox.x + bBox.width) + ctm.c * (bBox.y + bBox.height) + ctm.e;
const bottomRightY = ctm.b * (bBox.x + bBox.width) + ctm.d * (bBox.y + bBox.height) + ctm.f;
const x = Math.min(topLeftX, topRightX, bottomLeftX, bottomRightX);
const y = Math.min(topLeftY, topRightY, bottomLeftY, bottomRightY);
const width = Math.max(topLeftX, topRightX, bottomLeftX, bottomRightX) - x;
const height = Math.max(topLeftY, topRightY, bottomLeftY, bottomRightY) - y;
const rect2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect2.setAttribute('x', x);
rect2.setAttribute('y', y);
rect2.setAttribute('width', width);
rect2.setAttribute('height', height);
rect2.setAttribute('fill', '#ff000040');
svg.appendChild(rect2);
</script>
Or you could just check the Developer tools of Firefox/Chromium to see the dfifferences (just to say putting a group around doesn't work either).
Maybe SVG version 2 will make a difference in the future:
Chrome Platfor Status SVG2
So now what? If getBBox is the only function that seems to be working but only for svgs without inner transforms, can these transforms be applied dynamically with javascript?
Turns out someone went the extra mile:
flatten.js
put the script in a file 'flatten.js' and remove the leftovers at the top if still there (html, title..)
<div>
<svg id="svg" width="600" height="600" version="1.1" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg">
<g id="svgElem" transform="rotate(-50, 240, 174)" fill="#A1B6FF">
<path transform="translate(100, 100)"
d="M0, 0 Q 140 128.76 280 0 v 148 Q 140 276.76 0 148 v -148z">
</path>
</g>
</svg>
</div>
<script src="flatten.js"></script>
<script type="text/javascript">
const svg = document.getElementById('svg');
let svgElemClone = document.getElementById('svgElem').cloneNode(true); // flatten will directly change the element so a clone is made
svgElemClone.id = 'svgElemClone';
svg.appendChild(svgElemClone);
flatten(svgElemClone, true);
const bBox = svgElemClone.getBBox();
console.dir(bBox);
</script>
So this might be a workaround way to get the "real" bounding box.
As for getBoundingClientRect:
MDN says: "The returned value is a DOMRect object which is the smallest rectangle which contains the entire element, including its padding and border-width."
IMHO there is a bug in Chromium's implementation.

Svg transform attribute is placing elements with a gap

I would place arrows above a path according to a progression parameter.
I'm able to place them.
The problem is that arrows are placed beside the path and not on the middle.
When I change path with circles , these are placed in center.
How can I place arrows without gap like circle ?
let svg= document.getElementsByTagName('svg')[0]
let newpath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let newpath2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
newpath.setAttribute('d', 'M0,0 V20 L10,10 Z');
newpath2.setAttribute('d', 'M0,10 L10,0 V20 Z');
let progress=50;
let progress2= 300
let progress3= 400
let position = document.getElementById('s3')
let pt1 = position.getPointAtLength(progress);
let pt2 = position.getPointAtLength(progress + 0.1);
let pt12 = position.getPointAtLength(progress2);
let pt22 = position.getPointAtLength(progress2 + 0.1);
let ptcircle= position.getPointAtLength(progress3);
let a = (Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x) * 180) / Math.PI;
let a2 = (Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x) * 180) / Math.PI;
newpath.setAttribute('transform', `translate(${pt1.x},${pt1.y})rotate(${a})`);
newpath2.setAttribute('transform', `translate(${pt12.x},${pt12.y})rotate(${a2})`);
circle.setAttribute('cx', ptcircle.x);
circle.setAttribute('cy', ptcircle.y);
circle.setAttribute('r', 6);
svg.appendChild(newpath);
svg.appendChild(newpath2);
svg.appendChild(circle);
<svg viewBox = "0 0 800 300" version = "1.1">
<path id = "s3" d = "M10.51,27.68c202.42,340.08,200.57-4.6,300,15.67" fill = "none" stroke = "green" stroke-width = "3"/>
</svg>
You just need shift every point in path d attribute.
goal is to draw arrows centered.
newpath.setAttribute('d', 'M0,-10 V10 L10,0 Z');
newpath2.setAttribute('d', 'M0,0 L10,-10 V10 Z');
and there are mistake in copy-paste of angle calculations...
let svg= document.getElementsByTagName('svg')[0]
let newpath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let newpath2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
newpath.setAttribute('d', 'M0,-10 V10 L10,0 Z');
newpath2.setAttribute('d', 'M0,0 L10,-10 V10 Z');
let progress=50;
let progress2= 300
let progress3= 400
let position = document.getElementById('s3')
let pt1 = position.getPointAtLength(progress);
let pt2 = position.getPointAtLength(progress + 0.1);
let pt12 = position.getPointAtLength(progress2);
let pt22 = position.getPointAtLength(progress2 + 0.1);
let ptcircle= position.getPointAtLength(progress3);
let a = (Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x) * 180) / Math.PI;
let a2 = (Math.atan2(pt22.y - pt12.y, pt22.x - pt12.x) * 180) / Math.PI;
newpath.setAttribute('transform', `translate(${pt1.x},${pt1.y})rotate(${a})`);
newpath2.setAttribute('transform', `translate(${pt12.x},${pt12.y})rotate(${a2})`);
circle.setAttribute('cx', ptcircle.x);
circle.setAttribute('cy', ptcircle.y);
circle.setAttribute('r', 6);
svg.appendChild(newpath);
svg.appendChild(newpath2);
svg.appendChild(circle);
<svg viewBox = "0 0 800 300" version = "1.1">
<path id = "s3" d = "M10.51,27.68c202.42,340.08,200.57-4.6,300,15.67" fill = "none" stroke = "green" stroke-width = "3"/>
</svg>

Find intersect line with circle is not working in Javascript

I followed the formula to find intersect line with circle. But it does not work. Here is my code.
<svg height="397" width="851">
<circle cx="334.4" cy="198.5" r="150.8" stroke="black" stroke-width="3" fill="red" />
<line x1="485.60978255954285" y1="231.75390342766823" x2="488.9584783979701" y2="231.75390342766823" style="stroke:rgb(255,0,0);stroke-width:2" />
Sorry, your browser does not support inline SVG.
</svg>
<script>
var line1point= {x: 485.60978255954285, y: 231.75390342766823};
var line2point= {x: 488.9584783979701, y: 231.75390342766823};
var center = {x: 334.4, y: 198.5};
var intersect = inCircle(line1point, line2point, center, 150.8);
alert(intersect);
function inCircle (line1point, line2point, center, r){
var l1 = line1point, l2 = line2point, c = center;
return (Math.abs((l2.x - l1.x) * c.x + c.y * (l1.y - l2.y) + (l1.x - l2.x) * l1.y + (l1.y - l2.y) * c.y)
/ Math.sqrt(((l2.x - l1.x) * (l2.x - l1.x)) + ((l1.y - l2.y) *(l1.y - l2.y))) <= r);
}
</script>
Fiddle Link
I tried the formula given in this forum second answer. Stackexchange link

Calculate multiple circle fragment rotations

So I have 4 SVG circles, which I'm using stroke-dash to mask. And the general idea is that they're supposed to make up one full circle based on their percentage.
I've gotten the length of each segment, and when I rotate them manually I see that it all adds up. But I can't figure out how to calculate the rotation of each segment. Under is a jsbin link to show how far I've gotten:
http://jsbin.com/lutodomujo/1/
Also, if there is better way to solve this, I'd be happy to hear it. The only thing that has to work is the hover effect as shown in the example.
By the way, the following line is a purely wild guess (as you may have noticed):
var rotate = (Math.sin((c-prevRotate)/100) * Math.PI)*100; // ?
And it is, as far as I know, the only one I need help to figure out.
var prevRotate = 0;
$('circle').each(function (i) {
var r = $(this).attr('r');
var val = $(this).data('perc');
var c = Math.PI * (r * 2);
var pct = ((100 - val) / 100) * c;
var rotate = (Math.sin((c-prevRotate)/100) * Math.PI)*100;
$(this).css({
strokeDasharray: c,
strokeDashoffset: pct,
transform: 'rotate(' + rotate + 'deg)'
});
prevRotate += pct;
});
svg { width: 300px; }
circle {
stroke-width: 3;
transform-origin: center;
}
circle:hover {stroke-width: 5}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 164 164">
<circle fill="none" stroke="#A5D2C6" cx="82" cy="82" r="80" data-perc="40"/>
<circle fill="none" stroke="#000000" cx="82" cy="82" r="80" data-perc="30"/>
<circle fill="none" stroke="#EBE6B7" cx="82" cy="82" r="80" data-perc="20"/>
<circle fill="none" stroke="#F1AAA6" cx="82" cy="82" r="80" data-perc="10"/>
</svg>
I made some change and it s work :
var prevRotate = 0;
$('circle').each(function (i) {
var r = $(this).attr('r');
var val = $(this).data('perc');
var c = Math.PI * (r * 2);
var pct = ((100 - val) / 100) * c;
var rotate = prevRotate;
$(this).css({
strokeDasharray: c,
strokeDashoffset: pct,
transform: 'rotate(' + rotate + 'deg)'
});
prevRotate += (360*val/100);
});

Make a rectangle knowing the position of the two vertical lines of the sides (path elements)

I want to make a rectangle knowing the position of the two vertical lines of the sides. The lines are implemented as follows:
<path fill="#000000" id="Measure1" d="M159.688,119.75L159.688,88.75L160.28799999999998,88.75L160.28799999999998,119.75ZM162.688,119.75L162.688,88.75L163.28799999999998,88.75L163.28799999999998,119.75Z"/>
<path fill="#000000" id="Measure2" d="M260.168,119.75L260.168,88.75L260.76800000000003,88.75L260.76800000000003,119.75Z"/>
With this code I have a rectangle:
<path d="M10 80 H 110 V 130 H 10 V 80 Z" fill="red" />
But can I use the coordinates of the two lines to use them to give my rectangle the position?
Thank you!
You can get the bounding box of the lines with SVG's native getBBox(), and then calculate the positions, here is a basic example:
var path1 = document.getElementById("Measure1");
var path2 = document.getElementById("Measure2");
var xmlns = "http://www.w3.org/2000/svg";
var svg = document.querySelector("svg");
var bbox1 = path1.getBBox();
var bbox2 = path2.getBBox();
var x = bbox1.x + bbox1.width;
var y = bbox1.y;
var width = bbox2.x - x;
var height = bbox1.height;
var rect = document.createElementNS(xmlns, "rect");
rect.setAttribute("x", x);
rect.setAttribute("y", y);
rect.setAttribute("width", width);
rect.setAttribute("height", height);
rect.setAttribute("fill", "red");
svg.appendChild(rect);
You can see the result here:
http://jsfiddle.net/3rSYV/

Categories

Resources