SVG mouse position relative to transformed element - svg

I have some SVG elements and I would like to add the ability to resize using mouse.
It works great when element doesn't have any transforms (matrix, rotate, scale etc).
If I remove transform (transform="matrix(1.1,1.1,1,0,0,0)") from the rect element - it works great.
My understanding is that I should use matrix.inverse() function and transform it so I get mouse coordinates in the element space, but everything I tried didn't work for me.
I need to correctly calculate the x, y difference between start drag point and current point, but it doesn't work.
var rect = document.getElementById('rect');
var grip = document.getElementById('grip');
function moveGrip() {
var baseMatrix = rect.getCTM(),
x = rect.width.baseVal.value + rect.x.baseVal.value,
y = rect.height.baseVal.value + rect.y.baseVal.value;
grip.cx.baseVal.value = x * baseMatrix.a + y * baseMatrix.c + baseMatrix.e;
grip.cy.baseVal.value = x * baseMatrix.b + y * baseMatrix.d + baseMatrix.f;
}
grip.addEventListener('mousedown', onGripMouseDown);
document.addEventListener('mousemove', onDocMouseMove);
var startDragPoint, startDragSize;
function onGripMouseDown(evt) {
startDragSize = {w: rect.width.baseVal.value, h: rect.height.baseVal.value};
startDragPoint = {x: evt.clientX, y: evt.clientY};
}
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
rect.width.baseVal.value = startDragSize.w + evt.clientX - startDragPoint.x;
rect.height.baseVal.value = startDragSize.h + evt.clientY - startDragPoint.y;
moveGrip();
}
}
moveGrip();
<svg width="500" height="400">
<rect id="rect" x="20" y="20" width="200" height="100"
transform="matrix(1.1,1.1,1,0,0,0)"
style="fill:none;stroke: #3a2dd0; stroke-width: 2px;"></rect>
<g>
<circle id="grip" r="3" stroke-width="1" stroke="green"></circle>
</g>
</svg>

Probably, you'd need to apply the inverse matrix to delta-x,y as follows;
function onDocMouseMove(evt) {
if (evt.buttons & 1) {
// dragging
var invMatrix = rect.getCTM().inverse(),
x = evt.clientX - startDragPoint.x,
y = evt.clientY - startDragPoint.y;
rect.width.baseVal.value = startDragSize.w + x * invMatrix.a + y * invMatrix.c;
rect.height.baseVal.value = startDragSize.h + x * invMatrix.b + y * invMatrix.d;
moveGrip();
}
}

Related

SVG partial quadratic curve

I have an SVG path defined using a quadratic path:
M x11 y11 Q cx cy x12 y12
This intersects with another line:
M x21 y21 L x22 y22
I am using a library which determines the intersection point of two SVG paths, xintersection yintersection. What I want to do now is draw the quadratic path only up to the intersection point, but following the same curve as though the full path were being drawn, i.e.:
M x11 y11 Q cxModified cyModified xintersection yintersection
How do I determine what the new control point, cxModified cyModified, should be?
As commented by #ccprog bezier.js can do this.
You need to convert your svg <path> elements to bezier.js objects like so:
let line = {
p1: {x:100, y:20},
p2: {x:99, y:180}
};
let curve = new Bezier.fromSVG(curveEl.getAttribute('d'));
This won't work for horizontal or vertical lines, you need to give them a slight x or y slant.
Bezier.fromSVG() can convert yout <path> d attribute to a bezier curve object (quadratic or cubic) - but it expects absolute commands. Shorthand commands like sor t won't be normalized automatically.
Then you can retrieve intersection t values (0-1) via intersects() method and call split(t) to get the curve command coordinates for left and right segment:
let intersections = curve.intersects(line);
let splitSegments = curve.split(intersections);
let left = splitSegments.left.points;
let right = splitSegments.right.points;
let line = {
p1: {x:100, y:20},
p2: {x:99, y:180}
};
let curve = new Bezier.fromSVG(curve1.getAttribute('d'));
// get intersection t value 0-1
let intersectT = curve.intersects(line);
let splitSegments = curve.split(intersectT);
// render
let dLeft = pointsToD(splitSegments.left.points);
left.setAttribute("d", dLeft);
let dRight = pointsToD(splitSegments.right.points);
right.setAttribute("d", dRight);
/**
* convert intersection result to
* svg d attribute
*/
function pointsToD(points) {
let d = `M ${points[0].x} ${points[0].y} `;
// cubic or quadratic bezier
let command = points.length > 3 ? "C" : "Q";
points.shift();
d +=
command +
points
.map((val) => {
return `${val.x} ${val.y}`;
})
.join(" ");
return d;
}
svg{
width:20em;
border:1px solid #ccc;
}
path{
fill:none;
}
<svg id="svg" viewBox="0 0 200 200">
<path id="line1" d="M 100 20 L 99 180" fill="none" stroke="#ccc" stroke-width="5" />
<path id="curve1" d="M 48 84 Q 100 187 166 37" fill="none" stroke="#ccc" stroke-width="5" />
<!-- result -->
<path id="left" d="" fill="none" stroke="green" stroke-width="2" />
<path id="right" d="" fill="none" stroke="red" stroke-width="2" />
</svg>
<script src="https://cdn.jsdelivr.net/npm/bezier-js#1.0.1/bezier.js"></script>

How can I distribute points evenly along an oval?

I have an oval (as an svg)
I want to distribute n points along the oval:
evenly
inset the points by x percent
constrained to the lower f percent of the oval
How can I do this programmatically? I just need a list of coordinates as output.
SVG of an ellipse:
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="297mm" width="210mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 744.09448819 1052.3622047">
<g id="layer1">
<ellipse id="path8074" rx="78.559" ry="105.84" stroke="#000" cy="489.51" cx="314.29" stroke-width=".38188px" fill="none"/>
</g>
</svg>
Calculating equidistant point on ellipse circumference is quite complex math problem. Moreover, parallel curve (inward) for an ellipse is not ellipse.
If your ellipse is not very squeezed (a/b ratio is in range 0.5..2), you can use simple approximation through equidistantly spaced t parameter of ellipse equaion. Otherwise distance variance will too high and you need more complex approach based on arc length/distance calculation (requires numerical integration).
[Edit]: I added some correction of t to make point distribution better. Idea taken from here,
Using parallel curve equation, we can calculate points in such way (Delphi code as reference):
var
i, a, b, cx, cy, x, y, k, N: Integer;
sq, t: Double;
begin
N := 30; // Number of points
a := 120; //semiaxes
b := 200;
cx := 300; //center
cy:= 300;
k := 30; //inward distance
Canvas.Ellipse(cx - a, cy - b, cx + a, cy + b);
for i := 0 to N - 1 do begin
t := 2 * Pi * i / N;
//empirically adopted coefficient 0.3
t := t + 0.3 * ArcTan((a-b)*tan(t)/(a + b * sqr(tan(t))));
sq := 1.0 / Hypot(a * sin(t), b * cos(t));
x := Round(cx + (a - b * k * sq) * Cos(t));
y := Round(cy + (b - a * k * sq) * Sin(t));
Canvas.Ellipse(x-2,y-2,x+3,y+3);
end;
This is how I would do it:
I've changed the ellipse you have so that I center it around 0. To keep the position I translate the group <g id="layer1" transform="translate(314.29,489.51)">.
I draw another ellipse inside. The rx attribute of this ellipse is the rx of the path8074 ellipse multiplied by a factor. Let's say .8. The same for the ry. I'm calling this ellipse inner
I calculate the total length of the inner ellipse using let innerLength = inner.getTotalLength();
let n = 10: this is the number of points you need to inset
I'm using a loop for(let i = 0; i < n; i++){ to calculate the coords of the points on the inner path let length = i * innerLength / n; let p = inner.getPointAtLength(length);and to draw a circle to mark the point: drawCircle({cx:p.x,cy:p.y,r:2}, layer1)
const SVG_NS = 'http://www.w3.org/2000/svg';
let rx = path8074.getAttribute("rx");
let ry = path8074.getAttribute("ry");
let factor = .8;
inner.setAttributeNS(null,"rx",rx*factor);
inner.setAttributeNS(null,"ry",ry*factor);
let innerLength = inner.getTotalLength();
let n = 10;//n points
for(let i = 0; i < n; i++){
let length = i * innerLength / n;
let p = inner.getPointAtLength(length);
drawCircle({cx:p.x,cy:p.y,r:2}, layer1)
}
// a function to draw a circle
function drawCircle(o, parent) {
var circle = document.createElementNS(SVG_NS, 'circle');
for (var name in o) {
if (o.hasOwnProperty(name)) {
circle.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(circle);
return circle;
}
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="297mm" width="210mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="200 350 744.09448819 1052.3622047">
<g id="layer1" transform="translate(314.29,489.51)">
<ellipse id="path8074" rx="78.559" ry="105.84" stroke="#000" stroke-width=".38188px" fill="none"/>
<ellipse id="inner" stroke="#000" stroke-width=".38188px" fill="none"/>
</g>
</svg>
I hope it helps.
OBSERVATION: I've changed the viewBox of the svg element because I wanted the ellipses in view. You can change it back to what it was.

I have one path(heart) , scaled to reduce size(width and height) with added transform value. I need difference width of added and scaled path

Below different transform values of same path
Path with first transform value.
<path id="cropMarkMask" class="bleedPath" d="M418.8267682588199,227.4779418368866C418.19489615365677,228.3214910972795,417.6135738169066,229.21559012608546,417.08911996962104,230.1633982838303C384.0200933459036,280.60890779953655,338.2093657215696,328.4195106367121,279.6569370966192,373.5983661558829C276.601835468155,375.8067591634284,273.5467338396908,378.0720206604385,270.4947915717524,380.38783192586175C254.17353509538623,391.86894807667755,239.22028172669843,397.3441198679169,225.63187210516324,396.81650666010563C225.42335431045936,396.81650666010563,225.26222692364274,396.81650666010563,225.1579680262908,396.81650666010563C225.1579680262908,396.81650666010563,224.8420319737092,396.81650666010563,224.8420319737092,396.81650666010563C224.63351417900532,396.81650666010563,224.4723867921887,396.81650666010563,224.36812789483676,396.81650666010563C210.78287763382738,397.3441198679169,195.82962426513959,391.86894807667755,179.5052084282476,380.38783192586175C176.45326616030923,378.0720206604385,173.39816453184503,375.8067591634284,170.34306290338083,373.5983661558829C111.79063427843042,328.4195106367121,65.97990665409648,280.60890779953655,32.91088003037898,230.1633982838303C32.383266822567684,229.21559012608546,31.805103846343336,228.3214910972795,31.173231741180103,227.4779418368866C7.689704952788771,188.09335352206264,-2.527666987700611,149.91564092810046,0.5274346407635936,112.95112277605168C3.4782773718758646,76.09086352135482,19.221370872017665,47.02474668384638,47.75987450171482,25.752772263526335C93.25466607346718,-8.156644260058247,144.3825974627497,-8.57999857051761,201.14682803008822,24.48902805319989C209.1494882419805,29.22806884192409,216.88992153023005,31.439621209995384,224.36812789483682,31.123685157413775C224.57980505006645,31.123685157413775,224.78832284477033,31.123685157413775,225.00000000000003,31.123685157413775C225.2085177947039,31.123685157413775,225.42019494993357,31.123685157413775,225.63187210516327,31.123685157413775C233.1100784697701,31.335362312643458,240.85051175801962,29.123809944572166,248.85317196991187,24.48902805319989C305.61740253725037,-8.579998570517605,356.7453339265329,-8.156644260058242,402.2401254982853,25.752772263526335C430.78178848850814,47.02474668384638,446.52488198865,76.09402288188062,449.4725653592364,112.95112277605168C452.5276669877007,149.91564092810046,442.31029504721124,188.09335352206264,418.8267682588199,227.4779418368866C418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866" width="357.21000000000004" height="315.02055178728943" aWidth="0" aHeight="0" x="71.39499999999998" y="92.48972410635528" aX="0" aY="0" fill="none" stroke="#ff0000" style="pointer-events: none; display: block;" display="none" transform="translate(107.11599999999999,128.2063399552543) scale(0.63504,0.6138000000000001)"></path>
Path with second transform value.
<path id="safeMarkMask" class="bleedPath" d="M418.8267682588199,227.4779418368866C418.19489615365677,228.3214910972795,417.6135738169066,229.21559012608546,417.08911996962104,230.1633982838303C384.0200933459036,280.60890779953655,338.2093657215696,328.4195106367121,279.6569370966192,373.5983661558829C276.601835468155,375.8067591634284,273.5467338396908,378.0720206604385,270.4947915717524,380.38783192586175C254.17353509538623,391.86894807667755,239.22028172669843,397.3441198679169,225.63187210516324,396.81650666010563C225.42335431045936,396.81650666010563,225.26222692364274,396.81650666010563,225.1579680262908,396.81650666010563C225.1579680262908,396.81650666010563,224.8420319737092,396.81650666010563,224.8420319737092,396.81650666010563C224.63351417900532,396.81650666010563,224.4723867921887,396.81650666010563,224.36812789483676,396.81650666010563C210.78287763382738,397.3441198679169,195.82962426513959,391.86894807667755,179.5052084282476,380.38783192586175C176.45326616030923,378.0720206604385,173.39816453184503,375.8067591634284,170.34306290338083,373.5983661558829C111.79063427843042,328.4195106367121,65.97990665409648,280.60890779953655,32.91088003037898,230.1633982838303C32.383266822567684,229.21559012608546,31.805103846343336,228.3214910972795,31.173231741180103,227.4779418368866C7.689704952788771,188.09335352206264,-2.527666987700611,149.91564092810046,0.5274346407635936,112.95112277605168C3.4782773718758646,76.09086352135482,19.221370872017665,47.02474668384638,47.75987450171482,25.752772263526335C93.25466607346718,-8.156644260058247,144.3825974627497,-8.57999857051761,201.14682803008822,24.48902805319989C209.1494882419805,29.22806884192409,216.88992153023005,31.439621209995384,224.36812789483682,31.123685157413775C224.57980505006645,31.123685157413775,224.78832284477033,31.123685157413775,225.00000000000003,31.123685157413775C225.2085177947039,31.123685157413775,225.42019494993357,31.123685157413775,225.63187210516327,31.123685157413775C233.1100784697701,31.335362312643458,240.85051175801962,29.123809944572166,248.85317196991187,24.48902805319989C305.61740253725037,-8.579998570517605,356.7453339265329,-8.156644260058242,402.2401254982853,25.752772263526335C430.78178848850814,47.02474668384638,446.52488198865,76.09402288188062,449.4725653592364,112.95112277605168C452.5276669877007,149.91564092810046,442.31029504721124,188.09335352206264,418.8267682588199,227.4779418368866C418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866" width="357.21000000000004" height="315.02055178728943" aWidth="0" aHeight="0" x="71.39499999999998" y="92.48972410635528" aX="0" aY="0" fill="none" stroke="#2B8F49" stroke-width="2" stroke-dasharray="5,5" style="pointer-events: none; display: block;" display="none" transform="translate(142.837,163.92295580415333) scale(0.47628000000000004,0.43380000000000013)"></path>
This is not exactly answering your question but I think you'll find it useful. I draw 2 rectangles, one for each heart: the transformed bounding box. The code is giving you (please open the console) the size and the position of those boxes so that you can calculate the difference you need:
const SVG_NS = 'http://www.w3.org/2000/svg';
const SVG_XLINK = "http://www.w3.org/1999/xlink"
const svg = document.querySelector("svg")
let heart_bbox = heart.getBBox();
// untransformed heart
let W0 = heart_bbox.width;
let H0 = heart_bbox.height;
let x0 = heart_bbox.x;
let y0 = heart_bbox.y;
// red heart
let bbox1 = {
width: W0 * 0.635,
height: H0 * 0.614,
x : x0 + 107.116,
y : y0 + 128.206
}
console.log("red heart",bbox1);
// green heart
let bbox2 = {
width: W0 * 0.476,
height: H0 * 0.434,
x : x0 + 142.837,
y : y0 + 163.923
}
console.log("green heart",bbox2);
drawRect(bbox1, svg)
drawRect(bbox2, svg)
function drawRect(o, parent) {
let rect = document.createElementNS(SVG_NS, 'rect');
for (let name in o) {
if (o.hasOwnProperty(name)) {
rect.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(rect);
return rect;
}
rect{stroke:#d9d9d9; fill:none;}
<svg viewBox = "-10 -10 500 500">
<defs>
<path id="heart" d="M418.8267682588199,227.4779418368866C418.19489615365677,228.3214910972795,417.6135738169066,229.21559012608546,417.08911996962104,230.1633982838303C384.0200933459036,280.60890779953655,338.2093657215696,328.4195106367121,279.6569370966192,373.5983661558829C276.601835468155,375.8067591634284,273.5467338396908,378.0720206604385,270.4947915717524,380.38783192586175C254.17353509538623,391.86894807667755,239.22028172669843,397.3441198679169,225.63187210516324,396.81650666010563C225.42335431045936,396.81650666010563,225.26222692364274,396.81650666010563,225.1579680262908,396.81650666010563C225.1579680262908,396.81650666010563,224.8420319737092,396.81650666010563,224.8420319737092,396.81650666010563C224.63351417900532,396.81650666010563,224.4723867921887,396.81650666010563,224.36812789483676,396.81650666010563C210.78287763382738,397.3441198679169,195.82962426513959,391.86894807667755,179.5052084282476,380.38783192586175C176.45326616030923,378.0720206604385,173.39816453184503,375.8067591634284,170.34306290338083,373.5983661558829C111.79063427843042,328.4195106367121,65.97990665409648,280.60890779953655,32.91088003037898,230.1633982838303C32.383266822567684,229.21559012608546,31.805103846343336,228.3214910972795,31.173231741180103,227.4779418368866C7.689704952788771,188.09335352206264,-2.527666987700611,149.91564092810046,0.5274346407635936,112.95112277605168C3.4782773718758646,76.09086352135482,19.221370872017665,47.02474668384638,47.75987450171482,25.752772263526335C93.25466607346718,-8.156644260058247,144.3825974627497,-8.57999857051761,201.14682803008822,24.48902805319989C209.1494882419805,29.22806884192409,216.88992153023005,31.439621209995384,224.36812789483682,31.123685157413775C224.57980505006645,31.123685157413775,224.78832284477033,31.123685157413775,225.00000000000003,31.123685157413775C225.2085177947039,31.123685157413775,225.42019494993357,31.123685157413775,225.63187210516327,31.123685157413775C233.1100784697701,31.335362312643458,240.85051175801962,29.123809944572166,248.85317196991187,24.48902805319989C305.61740253725037,-8.579998570517605,356.7453339265329,-8.156644260058242,402.2401254982853,25.752772263526335C430.78178848850814,47.02474668384638,446.52488198865,76.09402288188062,449.4725653592364,112.95112277605168C452.5276669877007,149.91564092810046,442.31029504721124,188.09335352206264,418.8267682588199,227.4779418368866C418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866,418.8267682588199,227.4779418368866" />
</defs>
<use xlink:href ="#heart" fill="none" stroke="#ff0000" transform="translate(107.11599999999999,128.2063399552543) scale(0.63504,0.6138000000000001)"></use>
<use xlink:href ="#heart" fill="none" stroke="#2B8F49" vector-effect="non-scaling-stroke" stroke-dasharray="5,5" transform="translate(142.837,163.92295580415333) scale(0.47628000000000004,0.43380000000000013)"></path>
</svg>

Rotate rectangle around its own center in SVG

I have following piece of code :
<svg>
<defs>
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "red"
fill = "blue" />
</defs>
<g transform = "translate(100,30)">
<use xlink:href = "#myRect" />
</g>
<g transform = "translate(100, 100) rotate(45 ? ?)">
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "green"
fill = "yellow" />
</g>
</svg>
When I translate rectangle without rotation, it is working fine. But when I rotate it, I wanted to rotate it around its center axis point. What should I need to pass to rotate attribute?
You would have to set the center as the center of the filled element. Like this:
svg .rotate {
transform-box: fill-box;
transform-origin: center;
transform: rotate(45deg);
}
You just need to add half the width/height of the rectangle to get its centre.
<g transform = "translate(100, 100) rotate(45 60 60)">
See transform documentation of the rotate function for more information.
The accepted answer works if you are drawing the rectangle starting at point (0,0) which was the OP case. However for me it was not!
Here is what worked for me:
To get the rectangle coordinates i used $('#rectID').getBBox()
method, should return [rect-height , rect-width , rect-y , rect x ]
The center point is ( rect-x + (rect-width/2) , rect-y + (rect-height/2) )
Here is a snippet i used on the browser console:
var coord = $('#elemID')[0].getBBox();
coord.x + (coord.width/2) +' '+ coord.y + (coord.height/2)
origin
x = x + width / 2
y = y + height / 2
here
x is 10
y is 10
width is 120
height is 120
<g transform = "translate(100, 100) rotate(45 70 70)">
I know this is an old post but if there are people out there who are looking make the values modifiable outside the group element
const centerX=100;
const centerY=100;
const firstAngle=45;
const secondAngle=60;
const thirdAngle =60;
<g transform ={`translate(${centerX}, ${centerY}) rotate(${firstAngle} ${secondAngle},
${thirdAngle})`}>
Nothing you just need to write the following code with the element in javascript:
element.rotate(angle Degree);

SVG Coordinate translation

Why doesn't this SVG inline code produce a box 60 by 40 with a dot in the middle?
I would like to plot items on a regular Cartesian coordinate map using SVG. The data I would have would be the size of the displayable map and center point on the Cartesian main map.
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="40" viewBox="0 0 60 40">
<g transform="translate(-470,480) scale(1,-1)">
<circle title="Center" cx="500" cy="500" r="3" fill="red"/>
</g>
</svg>
Your code is creating an 60 by 40 pixel SVG, then:
Draws a circle centred on (500, 500)
The scale transform moves the circle to (500, -500)
The translate transform moves it to (30, -20)
If you change the transform to transform="translate(-470,520) scale(1,-1)" you should get what I think you want.
I came up with this code snippet that will create a map of with Cartesian coordinates and plot them in an SVG window using those coordinates. Hope this will help somebody.
The function takes the center of the map as $x, $y and draws the map around that coordinate.
public static function xyMap( $x, $y, $width = 0, $height = 0, $show = array('X')) {
$minx = $x - ($width / 2);
$maxx = $x + ($width / 2);
if ($minx < 0) {
$minx = 0;
$maxx = $width;
} elseif ($maxx > Yii::app()->params['maxMapX']) {
$maxx = Yii::app()->params['maxMapX'];
$minx = Yii::app()->params['maxMapX'] - $width;
}
$miny = $y - ($height / 2);
$maxy = $y + ($height / 2);
if ($miny < 0) {
$miny = 0;
$maxy = $height;
} elseif ($maxy > Yii::app()->params['maxMapY']) {
$maxy = Yii::app()->params['maxMapY'];
$miny = Yii::app()->params['maxMapY'] - $height;
}
$x_xform = -1 * $minx;
$y_xform = $maxy;
$x_scale = 1;
$y_scale = -1;
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"$width\" height=\"$height\" viewBox=\"0 0 $width $height\">\n";
echo "<g transform=\"translate($x_xform, $y_xform) scale($x_scale,$y_scale)\">\n";
echo "<rect x=\"$minx\" y=\"$miny\" width=\"$width\" height=\"$height\" stroke=\"black\" stroke-width=\"2\" fill=\"white\" />\n";
echo "<circle title=\"Center\" cx=\"$x\" cy=\"$y\" r=\"3\" fill=\"red\"/>\n";
echo "</g>\n</svg>\n";
}

Resources