SVG put circular text over a line - javascript

I has this clock and I need to put the numbers above as you can see:
I know the position of each line but if I create a text with x, y and angle like:
<text *ngFor="let line of lines; let index = i" [attr.rotate]="line.angle"
[attr.x]="line.x1" [attr.y]="line.y1" [id]="'text'+index">
<tspan class="number">20</tspan>
</text>
I get this:
How could I put the text exactly in middle of line and has circle text instead of this?

For each <text> set x = 0, y = -radius and transform='rotate(index * angle)':
const SECTORS = 16;
const RADIUS = 70;
const g = d3.select('g');
for (let i = 0; i < SECTORS; i++) {
g.append('text')
.text('20')
.attr('x', 0)
.attr('y', -RADIUS)
.attr('transform', `rotate(${i * 360 / SECTORS})`)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="200">
<g transform="translate(100,100)"/>
</svg>

In this example each <text> is the child of a <g> and all the <g>s are children of a container <g>. The container is translated to the center and each <text> has a negative value of -44 that moves the text out to the circle. All the <g>s are then rotated. The container is also rotated (-20) to rotate the entire thing in place.
const container = document.getElementById('container');
Object.keys([...Array(13)]).forEach(i => {
let t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
t.setAttribute('y', '-44');
t.textContent = i*5;
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
g.setAttribute('transform', `rotate(${i*20})`);
g.append(t);
container.append(g);
});
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="250">
<circle cx="50" cy="50" r="40" fill="none" stroke="black" stroke-width="1" />
<g id="container" transform="translate(50 50) rotate(-20)"
font-size="8" text-anchor="middle" dominant-baseline="text-bottom">
</g>
</svg>
The <text>s can also be horizontal by rotating them "back" after rotating the parent <g> (but you can see that they are more difficult to place with an equal distance from the circle):
const container = document.getElementById('container');
Object.keys([...Array(13)]).forEach(i => {
let t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
t.setAttribute('transform', `translate(0 -46) rotate(${-i*20+20})`);
t.textContent = i*5;
let g2 = document.createElementNS('http://www.w3.org/2000/svg', 'g');
g2.setAttribute('transform', `rotate(${i*20-20})`);
g2.append(t);
container.append(g2);
});
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="250">
<circle cx="50" cy="50" r="40" fill="none" stroke="black" stroke-width="1" />
<g id="container" transform="translate(50 50)"
font-size="8" text-anchor="middle" dominant-baseline="middle">
</g>
</svg>

Related

SVG getBoundingClientRect() + "transform: rotate()" is bugged in Chrome in a very specific way

getBoundingClientRect() works fine on all SVG elements except in the circumstance when an element is rotated in Chrome.
Below I have drawn 2 lines, the one on the left is diagonal and the right line starts vertical, as represented by the black lines. The green box is a visual representation of the getBoundingClientRect(). I then apply the same transform rotate to both lines at the same time and then update the bounding box visual.
As you can see from the following, the left bounding box does not represent the actual bounds, however, the right one does.
The problem is I need the true client bounds of the line in whatever orientation it is. This works fine in Firefox. Does anyone know of another way to get or calculate the bounds when a line, specifically, has a transform rotate?
I have posted a copy of this here: getBoundingClientRect() + "transform: rotate()" is bugged in Chrome?. To help people with a similar problem, but then decided I'd actually ask the community.
Chrome: v100.0.4896.75
This bug has now been reported to the cromium bugs board here: https://bugs.chromium.org/p/chromium/issues/detail?id=1314959
UPDATE: I've added the same 2 static lines with a direct transform attribute with a rotation of 40deg, and 2 div's representing the 'getBoundingClientRect'. The left box is clearly not showing the smallest rectangle which contains the entire element.
UPDATE 2 Included a BBox Visual in blue. getBBox() simply calculates the bounds of a child relative to the SVG element itself, prior to any rotation transforms. getBoundingClientRect does calculate the transform correctly, but is using the BBox, which has it's own issues here: https://bugs.chromium.org/p/chromium/issues/detail?id=377665
let l = [1,2,3,4,5,6].map(d => document.getElementById(`line${d}`)),
vis = [1,2,3,4,5,6].map(d => document.createElement(`div${d}`)),
bboxvis = [1,2,3,4,5,6].map(d => document.createElement(`div${d}`)),
r = 0;
vis.map((element) => {
document.body.appendChild(element)
element.style.position = "absolute"
element.style.border = "1px solid green"
element.style.background = `#0FF0002e`
})
bboxvis.map((element) => {
document.body.appendChild(element)
element.style.position = "absolute"
element.style.border = "1px solid blue"
element.style.background = `#0000FF2e`
})
let updateBounds = (element, displayElement, bboxvisEl) => {
let rect = element.getBoundingClientRect(),
svgBounds = element.parentElement.getBoundingClientRect(),
bbox = element.getBBox(),
d = document.documentElement;
displayElement.style.top = rect.y+d.scrollTop+"px"
displayElement.style.left = rect.x+d.scrollLeft+"px"
displayElement.style.width = rect.width+"px"
displayElement.style.height = rect.height+"px"
bboxvisEl.style.top = bbox.y+svgBounds.y+d.scrollTop + "px"
bboxvisEl.style.left = bbox.x+svgBounds.x+d.scrollLeft +"px"
bboxvisEl.style.width = bbox.width+"px"
bboxvisEl.style.height = bbox.height+"px"
}
[3,4,5].forEach(i => {
updateBounds(l[i], vis[i], bboxvis[i]);
bboxvis[i].style.transform = `rotate(40deg)`;
})
setInterval(() => {
[0,1,2].forEach(i => {
l[i].setAttribute("transform", `rotate(${r} 80 80)`);
bboxvis[i].style.transform = `rotate(${r}deg)`;
updateBounds(l[i], vis[i], bboxvis[i]);
})
r++;
if(r===360) r=0;
}, 20)
.container {
width:140px;
height:140px;
display:inline-block;
}
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="40" y1="40" x2="120" y2="120" stroke="black" stroke-width="2"></line>
<line x1="40" y1="40" x2="120" y2="120" id="line1" stroke="red" stroke-width="2"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="80" y1="30" x2="80" y2="130" stroke="black" stroke-width="2"></line>
<line x1="80" y1="30" x2="80" y2="130" id="line2" stroke="red" stroke-width="2"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="60" y1="30" x2="100" y2="130" stroke="black" stroke-width="2"></line>
<line x1="60" y1="30" x2="100" y2="130" id="line3" stroke="red" stroke-width="2"></line>
</svg>
<br/>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="40" y1="40" x2="120" y2="120" id="line4" stroke="red" stroke-width="2" transform="rotate(40 80 80)"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="80" y1="30" x2="80" y2="130" id="line5" stroke="red" stroke-width="2" transform="rotate(40 80 80)"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="60" y1="30" x2="100" y2="130" id="line6" stroke="red" stroke-width="2" transform="rotate(40 80 80)"></line>
</svg>

How to draw SVG element in center of click?

User makes a click then gets coordinates (x, y). I try to draw the balloon svg in place of click:
The balloon has code:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 88 88" style="enable-background:new 0 0 88 88;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#007dbb;}
</style>
<g id="marker2">
<g>
<g>
<path class="st0" d="M74.8,84.5c-20.1,0-39.9,0-59.7,0c0-27.1,0-54.2,0-81.4c19.9,0,39.8,0,59.7,0C74.8,30.2,74.8,57.3,74.8,84.5
z"/>
</g>
<g>
<g>
<path class="st1" d="M68,31.3c0,11.2-7.6,20.7-17.8,23.5c-2,0.5-4.9,2.2-6.2,8.3c-1.6-6-4.3-7.8-6.3-8.3
C27.5,51.9,20,42.5,20,31.3C20,17.9,30.7,7,44,7C57.3,7,68,17.9,68,31.3z"/>
<path class="st1" d="M44,67c3.3,0,6,2.7,6,6s-2.7,6-6,6c-3.3,0-6-2.7-6-6S40.7,67,44,67z"/>
</g>
</g>
</g>
</g>
<g id="Layer_1_1_">
</g>
</svg>
It is SVG image.
First how to set width, height for this balloon and how to the point of ballooin in center of click (highlighted by red)?
I tried to set width and height like this:
let group = document.getElementById('marker2');
group.setAttribute("width", '30px');
group.setAttribute("height", '30px');
But it does now work, when I have tried to find a center of circle:
<path class="st1" d="M44,67c3.3,0,6,2.7,6,6s-2.7,6-6,6c-3.3,0-6-2.7-6-6S40.7,67,44,67z"/>
Could you explain me how to place ballooin in the center of click?
My idea is to get rectangle outbox of id="marker2" then find the center:
let rectSmallCircle = getRect('st1');
let box = getRect('marker2');
let height = box.height;
let centerWidth = box.width / 2;
let startPositionX = centerWidth;
let startPositionY = height - rectSmallCircle.height / 2;
Play with the positions
const svg = `<svg viewBox="0 0 88 88"><style>.st0{fill:none;}.st1{fill:#007dbb;}</style><g id="marker2"><path class="st0" d="M74.8,84.5c-20.1,0-39.9,0-59.7,0c0-27.1,0-54.2,0-81.4c19.9,0,39.8,0,59.7,0C74.8,30.2,74.8,57.3,74.8,84.5 z"/><path class="st1" d="M68,31.3c0,11.2-7.6,20.7-17.8,23.5c-2,0.5-4.9,2.2-6.2,8.3c-1.6-6-4.3-7.8-6.3-8.3 C27.5,51.9,20,42.5,20,31.3C20,17.9,30.7,7,44,7C57.3,7,68,17.9,68,31.3z"/><path class="st1" d="M44,67c3.3,0,6,2.7,6,6s-2.7,6-6,6c-3.3,0-6-2.7-6-6S40.7,67,44,67z"/></g></svg>`
const container = document.getElementById("svgContainer");
document.getElementById("lnk").addEventListener("click", function(e) {
e.preventDefault();
container.innerHTML = svg;
const dot = document.querySelectorAll(".st1")[1]
dot.setAttribute('style', 'fill: green');
const bbox = dot.getBBox();
const center = [bbox.x / 2 + bbox.height / 2, bbox.y / 2 + bbox.width / 2]
const clickX = e.clientX,
clickY = e.clientY;
document.getElementById("output").innerHTML = `${clickX},${clickY} - ${center}`
container.style.top = (clickX-100-center[0]+88)+"px"
container.style.left = (clickY-100-88)+"px"
})
#svgContainer {
height: 200px;
position:absolute;
}
svg {
height: 200px
}
<h1>Test svg</h1>
<p id="output"></p>
<p><br/></p>
<p><br/></p>
<div id="svgContainer"></div>
<p>Click the <a id="lnk" href="#">Link</a></p>

Creating 9 circles in each rectangle (4 in each corner, 4 on the sides and 1 in the middle) at the same time/after immediately creating a rectangle

I have a challenge, where i am trying to create 9 circles in each rectangle (4 in each corner, 4 on the sides and 1
in the middle) at the same time after immediately creating a rectangle. In my approach, 4 circles are created with a rectangle, which is not desired!
Each rectangle should contain 9 circles (4 in each corner, 4 on the sides and 1 in the middle) at same time when it
will be created.
To be more precise, 9 circles are desired at the same time when each rectangle is created.
I used snap.svg library to create circles and rectangles.
You can use following code snippet.
const svgId = 'campus_map';
const width = document.getElementById(svgId).viewBox.baseVal.width;
const height = document.getElementById(svgId).viewBox.baseVal.height;
let draw = Snap("#tiles");
let c = 0;
let size = Math.round(0.05 * width);
let circleSize = 25;
let circleColor = ["#ff0000", "#000000", "#00ffe1", "#0051ff"];
let svg = document.getElementById(svgId);
for (let i = 0; i <= width; i = i + size) {
for (let j = 0; j <= height; j = j + size) {
c += 1;
let rect = draw.rect(i, j, size, size);
let circle1 = draw.circle(i, j, circleSize);
let circle2 = draw.circle(i + (size / 2), j, circleSize);
let circle3 = draw.circle(i, j + (size / 2), circleSize);
let circle4 = draw.circle(i + (size / 2), j + (size / 2), circleSize);
rect.attr({
fill: "#d00bf3",
"fill-opacity": 0.2,
stroke: "#000",
"stroke-width": "1px",
id: "rect_" + c,
name: "rect" + c
});
circle1.attr({
fill: circleColor[0],
"fill-opacity": 1,
stroke: "#000",
"stroke-width": "1px",
id: "circle1_" + c,
name: "circle1_" + c
});
circle2.attr({
fill: circleColor[1],
"fill-opacity": 1,
stroke: "#000",
"stroke-width": "1px",
id: "circle2_" + c,
name: "circle2_" + c
});
circle3.attr({
fill: circleColor[2],
"fill-opacity": 1,
stroke: "#000",
"stroke-width": "1px",
id: "circle3_" + c,
name: "circle3_" + c
});
circle4.attr({
fill: circleColor[3],
"fill-opacity": 1,
stroke: "#000",
"stroke-width": "1px",
id: "circle4_" + c,
name: "circle4_" + c
});
}
}
<script src="https://unpkg.com/snapsvg#0.5"></script>
<svg viewBox="0 0 8204.08 6413.17" version="1.1" id="campus_map"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<g id="tiles"></g>
</svg>
In your loop, you only create four circles per tile. If you added five more your code would work. What is stopping you from doing that?
It sounds like you want the circles to be inside the square. To achieve that, you need to move the circles in by one circle radius.
Here's some modified code where I calculate that offset and use a loop to position the nine circles.
const svgId = 'campus_map';
const width = document.getElementById(svgId).viewBox.baseVal.width;
const height = document.getElementById(svgId).viewBox.baseVal.height;
let draw = Snap("#tiles");
let c = 0;
let size = Math.round(0.05 * width);
let circleSize = 25;
let circleColor = ["#ff0000", "#000000", "#00ffe1", "#0051ff"];
let svg = document.getElementById(svgId);
for (let i = 0; i <= width; i = i + size) {
for (let j = 0; j <= height; j = j + size) {
c += 1;
let rect = draw.rect(i, j, size, size);
rect.attr({
fill: "#d00bf3",
"fill-opacity": 0.2,
stroke: "#000",
"stroke-width": "1px",
id: "rect_" + c,
name: "rect" + c
});
// Now have another loop to create the nine circles.
// It sounds like you want to have the circle INSIDE the rectangle
// so you need to move the away from the rectangle corners by
// the radius (circleSize).
// The distance between the circles is the square size - two radiuses
// then divided in half
let circleSpacing = (size - circleSize * 2) / 2;
// Top left circle is one radius inside the top left square corner
let circleStartX = i + circleSize;
let circleStartY = j + circleSize;
for (let i2 = 0; i2 < 3; i2++) {
for (let j2 = 0; j2 < 3; j2++) {
c += 1;
let circle = draw.circle(circleStartX + i2 * circleSpacing, circleStartY + j2 * circleSpacing, circleSize);
circle.attr({
fill: circleColor[0],
"fill-opacity": 1,
stroke: "#000",
"stroke-width": "1px",
id: "circle1_" + c,
name: "circle1_" + c
});
}
}
}
}
<script src="https://unpkg.com/snapsvg#0.5"></script>
<svg viewBox="0 0 8204.08 6413.17" version="1.1" id="campus_map"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<g id="tiles"></g>
</svg>
Consider the use of pattern SVG
This is the easiest solution for your application.
You can easily change its content at any time.
You can also fill containers of any shape with a pattern and make them responsive.
Please look at the comments to understand how to position the pattern elements.
<svg width="100%" height="100%" version="1.1" viewBox="0 0 800 800"
xmlns="http://www.w3.org/2000/svg" >
<defs>
<pattern id="myPattern"
x="0" y="0" width="40" height="40"
patternUnits="userSpaceOnUse" >
<rect x="0" y="0" width="40" height="40" fill="#F6CEFD" stroke-width="0.5" stroke="black" />
<g stroke="none" >
<!-- Central circle -->
<circle cx="20" cy="20" r="3" fill="#0051FF"/>
<!-- Top mid circle -->
<circle cx="20" cy="0.5" r="3" fill="#000000"/>
<!-- Bottom mid circle -->
<circle cx="20" cy="40" r="3" fill="000"/>
<!-- left middle circle -->
<circle cx="0" cy="20" r="3" fill="#00FFE1"/>
<!-- right middle circle -->
<circle cx="40" cy="20" r="3" fill="#00FFE1"/>
<!-- upper left corner -->
<circle cx="0" cy="0" r="3" fill="#FF0000"/>
<!-- upper bottom corner -->
<circle cx="0" cy="40" r="3" fill="#FF0000"/>
<!-- top right corner -->
<circle cx="40" cy="0" r="3" fill="#FF0000"/>
<!-- bottom right corner -->
<circle cx="40" cy="40" r="3" fill="#FF0000"/>
</g>
</pattern>
</defs>
<rect x="0" y="0" width="100%" height="100%"
style="stroke: #000000; fill: url(#myPattern);" />
</svg>
An example of filling a Christmas tree with a pattern
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1" width="267" height="347" viewBox="0 0 267 347" >
<defs>
<pattern id="myPattern"
x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse" >
<rect x="0" y="0" width="40" height="40" fill="green" stroke-width="0.5" stroke="black" />
<g stroke="none" >
<!-- Central circle -->
<circle cx="20" cy="20" r="3" fill="#0051FF"/>
<!-- Top mid circle -->
<circle cx="20" cy="0.5" r="3" fill="gold"/>
<!-- Bottom mid circle -->
<circle cx="20" cy="40" r="3" fill="gold"/>
<!-- left middle circle -->
<circle cx="0" cy="20" r="3" fill="#00FFE1"/>
<!-- right middle circle -->
<circle cx="40" cy="20" r="3" fill="#00FFE1"/>
<!-- upper left corner -->
<circle cx="0" cy="0" r="3" fill="#FF0000"/>
<!-- upper bottom corner -->
<circle cx="0" cy="40" r="3" fill="#FF0000"/>
<!-- top right corner -->
<circle cx="40" cy="0" r="3" fill="#FF0000"/>
<!-- bottom right corner -->
<circle cx="40" cy="40" r="3" fill="#FF0000"/>
</g>
</pattern>
</defs>
<!-- filling a Christmas tree with a pattern -->
<path id="path4146" d="m119 262 28 0 0 86-28-2z" fill="brown" />
<path id="tree" fill="url(#myPattern)" d="M261 327 169 244c16 9 103 34 76 15-25-18-81-74-81-74 8 5 94 45 71 27-24-19-78-88-78-88 7 5 42 11 42 11-24-13-47-73-47-73 11 8 21 7 21 7C149 51 133 0 133 0c0 0-15 51-39 69 0 0 9 1 21-7 0 0-23 60-47 73 0 0 35-7 42-12 0 0-38 58-78 89-20 15 61-23 69-28 0 0-25 38-75 85-14 14 63-13 72-25 0 0-70 64-88 86-6 7 123-56 123-56 0 0 133 70 129 52z" id="path4" fill="#008000"/>
</svg>

Render a single SVG element to canvas or image

How can I render a single SVG element (as opposed to the whole SVG document or parts of it) to an image or a canvas?
I have an SVG document with a lot of nodes. If I render the whole SVG or a part of it, the resulting image will contain other pieces of graphics I'm not interested in. I am interested in one specific SVG element and would like to render all of it without anything else.
Example:
<!DOCTYPE html>
<html>
<body>
<svg height="150" width="150">
<circle cx="50" cy="50" r="25" stroke="green" stroke-width="3" fill="gray" />
<circle id="IWantToRenderThis" cx="75" cy="75" r="25" stroke="red" stroke-width="3" fill="white" />
<circle cx="100" cy="100" r="25" stroke="blue" stroke-width="3" fill="black" />
</svg>
<script>
const target = document.getElementById("IWantToRenderThis");
// how can I render *target* into a texture?
</script>
</body>
</html>
How can I render target alone? I would like to obtain an image which has no traces of the other two circles, a transparent background, and the right size to fit target perfectly.
You could remove all elements but the one you want to render.
To fit the SVG to the remaining element, you need to calculate a new position and size. For your example the code could look like this:
<!DOCTYPE html>
<html>
<body>
<svg height="150" width="150" id="svg">
<circle cx="50" cy="50" r="25" stroke="green" stroke-width="3" fill="gray" />
<circle id="IWantToRenderThis" cx="75" cy="75" r="25" stroke="red" stroke-width="3" fill="white" />
<circle cx="100" cy="100" r="25" stroke="blue" stroke-width="3" fill="black" />
</svg>
<script>
const svg = document.getElementById("svg");
const target = document.getElementById("IWantToRenderThis");
const children = svg.children;
// Remove all child elements but the target
for(let index = 0; index < children.length; index++) {
const child = children[index];
if(child.id !== 'IWantToRenderThis') {
child.remove()
}
}
// Recalculate element position and svg size
const targetSize = parseInt(target.getAttribute('r'))
const targetStroke = parseInt(target.getAttribute('stroke-width'))
target.setAttribute('cx', targetSize + (targetStroke/2))
target.setAttribute('cy', targetSize + (targetStroke/2))
svg.setAttribute('width', targetSize*2 + targetStroke)
svg.setAttribute('height', targetSize*2 + targetStroke)
</script>
</body>
</html>
Note that you have to include the stroke width of your element to properly calculate its new position and the SVG's new size.
Here the target element is just copied using outerHTML into a new data URL representing the new SVG, loaded into an image object, drawn in a canvas and exported as a PNG image.
let img = document.getElementById('img');
let target = document.getElementById("IWantToRenderThis");
let svg = target.closest('svg');
let width = svg.attributes['width'].value;
let height = svg.attributes['height'].value;
let image = new Image();
let canvas = document.getElementById('canvas');
canvas.height = height;
canvas.width = width;
let ctx = canvas.getContext('2d');
image.addEventListener('load', e => {
ctx.drawImage(e.target, 0, 0, e.target.width, e.target.height);
img.src = canvas.toDataURL("image/png");
});
image.src = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"
width="${width}" height="${height}">${target.outerHTML}</svg>`;
<p>Original SVG:</p>
<svg id="svg" height="150" width="150">
<circle cx="50" cy="50" r="25" stroke="green" stroke-width="3" fill="gray" />
<circle id="IWantToRenderThis" cx="75" cy="75" r="25" stroke="red" stroke-width="3" fill="white" />
<circle cx="100" cy="100" r="25" stroke="blue" stroke-width="3" fill="black" />
</svg>
<p>SVG rendered in canvas:</p>
<canvas id="canvas"></canvas>
<p>PNG image based on canvas:</p>
<img id="img" />

Draw rhombus inscribed in a group with circle SVG

I have multiple <g> elements and all of them have a circle:
<g class="my-group">
<circle r="40" cx="10" cy="10"></circle>
</g>
In some cases I need to draw a rectange with rounded corners instead of circle, it should be presented as rhombus. I'm trying to draw it like this:
<g class="my-group">
<rect x="-16" y="-30" width="60" height="60" fill="red" transform="rotate(45)" rx="4"></rect>
</g>
But this is a wrong way to hardcode coordinates and width/height of rect.
How I can calculate width and height of rotated rect to inscribe it in circle, so group will have the same width and height 80px.
Simple pythagorean theorem. Rectangle width (or hypotenuse) equals the square root of two lots of the radius squared.
const radius = document.querySelector('#circle').getAttribute('r')
const rectWidth = Math.sqrt(radius * radius * 2)
const square = document.querySelector('#square')
square.setAttribute('x', -rectWidth/2 + 'px')
square.setAttribute('y', -rectWidth/2 + 'px')
square.setAttribute('width', rectWidth + 'px')
square.setAttribute('height', rectWidth + 'px')
svg {
width: 100vmin;
height: 100vmin;
}
<svg viewbox="0 0 100 100">
<circle id="circle" fill="green" r="40" cx="50" cy="50"></circle>
<rect id="square" fill="red" transform="translate(50, 50) rotate(45)"></rect></svg>

Categories

Resources