SVG partial quadratic curve - svg

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>

Related

How can I fix path data as circle in svg?

i have a problem, i convert the circles I drew to Autocad as high quality pdf, then I import this pdf with inkscape or corelDraw, my aim is to export this pdf as svg. No problems so far.
but circles in pdf appear as path not circle in svg. There are about 25 thousand flats, and showing it in svg as a path causes performance loss.
How can I fix path data as circle in svg?
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" id="path1932"/>
this is the path data for a circle as shown in the image, I want it
<circle cx="100" cy="100" r="75" /> to look like this, is it
possible?
Later edit I can use 3 cmd lines for the above one drawing but each case will need different approach. In the end the solution was ever so simple for this one example.
The compressed pdf is 102,186 bytes but expanded to text.svg (mu draw -o drawing7.svg drawing7.pdf) that is 4 times larger 409,446 bytes
given that this is only part of one array entry at a range of co-ordinates
<g id="Layer-1" data-name="ust-tribun$0$BK-seats">
<path transform="matrix(.0299676,.0013944,.0013944,-.0299676,694.69509,1952.665)" stroke-width="0" stroke-linecap="butt" stroke-miterlimit="10" stroke-linejoin="miter" fill="none" stroke="#808080" d="M 837.5 0 C 837.5 462.5385 462.5385 837.5 0 837.5 C -462.5385 837.5 -837.5 462.5385 -837.5 0 C -837.5 -462.5385 -462.5385 -837.5 0 -837.5 C 462.5385 -837.5 837.5 -462.5385 837.5 0 "/>
<path transform="matrix .......
the fix is to reverse the multiple parts of the symbol into the now rescaled source definition
<circle ........ r="837.5"/>
it will reduce the file to a more binary relavent size of 312,568 bytes but beware all those invisible lines with stroke-width="0" should be changed too (it's an all too common draughtsman error not to define their pen size).
Some background to the complexity of programmatically reversing vectors. Here I am illustrating using HTML in place of your DWG circle (so in one single hop) but we can see that on conversion to PDF the path instructions are translated the same as all other shape vectors into one of many paths. Your aim is to bulk reverse this process !
"WE" tend to think of two text letters as two instructions but in vectors that's at least \/\/ |≡ where that last group is 3 separate paths, however for convenience the plain text in PDF is translated via ttf font look-up table (that themselves are like svg but SVG lettering fonts are generally not tolerated) One of those SVG letters ≡ in the picture is described as  e.g. non reversible, thus using a big square O is not advisable.
So back to your question how to reverse a path to a circle will depend on the path being ALL ways the same string path format (or at least fairly consistent profiles).
So using text parsing method (you have still to show attempt) you need to pick out the arguments and back feed as parameters.
Use a typical test case of known size and position and determine the x and y values from the min and max then you can use the deltas for text substitution in <svg height="100" width="100"> the rest is then fairly simple since radius should be 𝛿x/2 and center should be minimum x + 𝛿x, minimum y + 𝛿y.
The fly in the ointment is how you translate your units so with no hard code to follow, in broad terms consider PDF origin like CAD is cartesian lower left and page units are usually converted using points as the measure so normally 1 point = 0.3527777777777778 mm
Your biggest problem is that the circle shown, inside the PDF will most likely be a series of vector chords. my sample has many small arcs but for your added sample see lower.
/GS0 gs
.24 0 0 -.24 -319.87684 182.32659 cm
1369.325 478.326 312.631 312.631 re
W*
n
3.126313 0 0 3.126313 1369.3252 478.32597 cm
90 50 m
90 51.31 89.936 52.617 89.807 53.921 c
89.679 55.224 89.487 56.519 89.231 57.804 c
88.976 59.088 88.658 60.358 88.278 61.611 c
...
87.457 35.903 87.897 37.135 88.278 38.389 c
88.658 39.642 88.976 40.912 89.231 42.196 c
89.487 43.481 89.679 44.776 89.807 46.079 c
89.936 47.383 90 48.69 90 50 c
h
0 0 0 rg
f
Q
on inspection of the sample it appears the circles are common for CAD output as 4 quarters so compare how Inkscape has reversed this PDF 4 arcs to SVG
q
-0.06113 -0.99813 0.99813 -0.06113 27455.5 34627.5 cm
837.5 0 m
837.5 462.53851 462.53851 837.5 0 837.5 c
-462.53851 837.5 -837.5 462.53851 -837.5 0 c
-837.5 -462.53851 -462.53851 -837.5 0 -837.5 c
462.53851 -837.5 837.5 -462.53851 837.5 0 c S
Q
so similar values to provided Inkscape's SVG conversion
<path d="M
837.5,0 C
837.5,462.53851 462.53851,837.5 0,837.5
-462.53851,837.5 -837.5,462.53851 -837.5,0 c
0,-462.53851 374.96149,-837.5 837.5,-837.5
462.53851,0 837.5,374.96149 837.5,837.5" id="path1932"/>
Short answer
don't embed SVG in PDF if at all possible nor expect to reverse easily. I think those arcs are going to be a problem in conversion it is do able but the svg modified in MS notepad looks good enough as it is.
It is just one line of code to transform the PDF to SVG with no changes.
One line of code to rectify the line thickness omission problem
But it would need many reams of code to convert 4-16 arcs into one circle.
Then reams more to do it for another drawing scale and layout.
The text manipulation could be done by whatever program you are familiar with, I use MS Notepad and CMD because I always find them reliable for hand editing. And cmd is good for basic text parsing, but for bulk programming you need MB of coding in a maths biased application.
anyway the text substitution is
d="M 837.5 0 C 837.5 462.5385 462.5385 837.5 0 837.5 C -862.5385 837.5 -837.5 462.5385 -837.5 0 C -837.5 -462.5385 -462.5385 -837.5 0 -837.5 C 462.5385 -837.5 837.5 -462.5385 837.5 0 "
using a simpler arc d="M -837.5,0 A 837.5,837.5 0 1,1 -837.5,0.001"
or better yet just replace with
r="837.5" and change the corresponding line start from <path to <circle but only for those array lines
CAD to PDF to SVG conversions will always introduce a lot of overhead
In other words you shouldn't expect a perfectly concise svg structure, since CAD formats (e.g DWG, DXF etc.) are quite different.
However, you might compare dedicated CAD to svg converters – some might actually produce a better output due to adaptive conversion techniques (e.g. recognizing primitives like circles).
Referring to your question, how to convert <path> elements to circles
Its <circle> replacement would be:
<circle cx="0" cy="0" r="837.5"/>
Due to its positioning within the svg user units space.
Svg primitives are like circles are not per se more performant than <path> elements, it rather depends on the the complexity of a path and also the total amount of svg elements.
Update: <use> elements and performance
As #Robert Longson pointed out:
use elements are much slower than raw paths because the UA has to
track changes and update all the use instances.
On the other hand, <use> elements help to significantly reduce the overall file size and thus improve loading times.
Solution 2: ungroup all elements to apply transformations
This solution is probably the compromise between file size (~200 KB – 50%) and rendering performance.
We need to ungroup all elements in inkscape (or another editor).
This way all <path> commands will be recalculated to the actual coordinates: See codepen example.
Benchmarks
unoptimized (~400 KB)
2. optimized: paths to use elements (~100 KB)
3. optimized: ungrouped; transformations applied (~200 KB)
Solution1: Vanilla js svg optimizer – replace <path> with <use>
const svg = document.querySelector("svg");
const paths = svg.querySelectorAll("path");
const ns = "http://www.w3.org/2000/svg";
// 0. add <def> if necessary
let defs = svg.querySelector("defs");
if (!defs) {
defs = document.createElementNS(ns, "defs");
svg.insertBefore(defs, svg.children[0]);
}
/**
* 1. inline styles to classes
*/
let styledEls = svg.querySelectorAll("[style]");
styleToClass(styledEls);
function styleToClass(els) {
//add <style> to parent svg if necessary
let css = svg.querySelector("style");
if (!css) {
css = document.createElementNS(ns, "style");
svg.insertBefore(css, svg.children[0]);
}
let styleObj = {};
els.forEach(function(el) {
let id = el.id;
let style = el.getAttribute("style");
style = style ? style.replaceAll(" ", "") : "";
let styleArr = style.split(";");
let stylesRounded = [];
//round nearby numeric values values
styleArr.forEach(function(prop) {
let propEl = prop.split(":");
let name = propEl[0];
let val = propEl[1];
if (parseFloat(val) == val) {
val = +parseFloat(val).toFixed(3);
}
stylesRounded.push(name + ":" + val);
});
style = removeCssProperties(stylesRounded.join(";"));
if (style) {
if (style in styleObj === false) {
styleObj[style] = {
count: 1,
ids: [id]
};
} else {
styleObj[style]["count"] += 1;
styleObj[style]["ids"].push(id);
}
}
});
let cssStr = "";
let classCount = 0;
for (style in styleObj) {
let css = style;
let className = "cl" + classCount;
cssStr += `.${className}{${style}}\n`;
classCount++;
let ids = styleObj[style]["ids"];
ids.forEach(function(id, i) {
let el = document.getElementById(id);
el.classList.add(className);
el.removeAttribute("style");
});
}
css.innerHTML = cssStr;
}
function removeCssProperties(css) {
css = css.replaceAll("; ", "");
let cssArr = css.split(";");
let cssFilter = [];
//default or propriatary properties
const remove = [
"stroke-miterlimit:10",
"stroke-dasharray:none",
"stroke-opacity:1",
"fill-opacity:1",
"-inkscape-font-specification:ArialMT",
"fill-rule:nonzero",
"fill:#000000",
"fill:black",
"stroke:none",
"writing-mode:lr-tb",
"stroke-linejoin:miter",
"font-variant:normal",
"font-weight:normal"
];
cssArr.forEach(function(prop) {
if (remove.indexOf(prop) === -1) {
cssFilter.push(prop);
}
});
cssFilter = cssFilter.join(";");
return cssFilter;
}
/**
* find repeated path "d" attributes
* replace them with <use> elements
*/
pathsToUse(paths);
function pathsToUse(paths) {
let useObj = {};
paths.forEach(function(path, i) {
let d = path.getAttribute("d").replaceAll(",", " ");
let id = path.id;
//add auto ids
if (!id) {
path.setAttribute("id", "pathID" + i);
}
//collect all d/pathdata
if (d in useObj === false) {
useObj[d] = {
count: 1,
ids: [id]
};
} else {
useObj[d]["count"] += 1;
useObj[d]["ids"].push(id);
}
});
//replace paths with <use> elements
let useDefs = "";
let useCount = 0;
for (d in useObj) {
let current = useObj[d];
let occurrences = current["ids"];
if (occurrences.length > 1) {
let useID = "p" + useCount;
//create def
useDefs += `<path id="${useID}" d="${d}" />\n`;
useCount++;
occurrences.forEach(function(id, i) {
let el = svg.getElementById(id);
let className = el.getAttribute("class");
let use = document.createElementNS(ns, "use");
use.setAttribute("href", "#" + useID);
use.setAttribute("xlink:href", "#" + useID);
use.classList.add(className);
el.replaceWith(use);
});
}
}
defs.insertAdjacentHTML("beforeend", useDefs);
}
// optimize d strings
let pathsOpt = svg.querySelectorAll("path");
pathsOpt.forEach(function(path) {
let d = path
.getAttribute("d")
.replace(/([a-zA-Z])(,)/g, "$1")
.replace(/( )([a-zA-Z])/g, "$2")
.replace(/([a-zA-Z])( )/g, "$1")
.replaceAll(" 0.", " .")
.replaceAll(",", " ")
.replaceAll(" -", "-");
path.setAttribute("d", d);
});
// optimize svg Markup
let svgMin = svg.outerHTML;
// minifying
svgMin = svgMin
.replaceAll("></path>", "/>")
.replaceAll("></use>", "/>")
.replace(/([ |\n|\r|\t])/g, " ")
.replace(/ +/g, " ")
.trim()
.replaceAll("> <", "><")
.replaceAll("><", ">\n<")
.replaceAll("} ", "}")
.replaceAll("}", "}\n");
//populate textarea
svgOpt.value = svgMin;
svg {
max-height: 90vh;
width: auto;
border: 1px solid #ccc
}
.cl0 {
stroke: green!important;
stroke-width: 10%!important;
}
<svg version="1.1" id="svg2" xml:space="preserve" viewBox="0 0 4224 3264" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="g8" transform="matrix(0,-0.04,-0.04,0,4160,3245.3333)">
<g id="g10">
<g id="g12" clip-path="url(#clipPath16)">
<g id="g18" transform="matrix(-0.04648,-0.99892,0.99892,-0.04648,16044.5,80843.5)">
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" style="fill:none;stroke:#808080;stroke-width:25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path20" />
</g>
<g id="g22" transform="matrix(-0.04715,-0.99889,0.99889,-0.04715,15943.5,78677.5)">
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" style="fill:none;stroke:#808080;stroke-width:24.9999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path24" />
</g>
<g id="g26" transform="matrix(-0.04782,-0.99886,0.99886,-0.04782,15840.5,76512.5)">
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" style="fill:none;stroke:#808080;stroke-width:24.9999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path28" />
</g>
<path d="m 41675,88799 -6933,313 -30,-649 6283,-284 -391,-8667 -6283,284 -30,-650 6934,-313 450,9966" style="fill:none;stroke:#dcdcdc;stroke-width:25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path2680" />
</g>
</g>
<g id="g2702">
<g id="g2704"><text transform="matrix(0,-1,-1,0,14155,86256)" style="font-variant:normal;font-weight:normal;font-size:2120.87px;font-family:Arial;-inkscape-font-specification:ArialMT;writing-mode:lr-tb;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="text2712">
<tspan x="0 1179.2031" y="0" id="tspan2710">17</tspan>
</text>
</g>
</g>
<g id="g2714">
<g id="g2716" clip-path="url(#clipPath2720)">
<g id="g3830" transform="rotate(-90,31516,-5789.5)">
<path d="M 1507.5,0 C 1507.5,832.56927 832.56927,1507.5 0,1507.5 -832.56927,1507.5 -1507.5,832.56927 -1507.5,0 c 0,-832.56927 674.93073,-1507.5 1507.5,-1507.5 832.56927,0 1507.5,674.93073 1507.5,1507.5" style="fill:none;stroke:#000000;stroke-width:25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" id="path3832" />
</g><text transform="matrix(-0.08733,0.99618,0.99618,0.08733,37824,24280)" style="font-variant:normal;font-weight:normal;font-size:1211.93px;font-family:Arial;-inkscape-font-specification:ArialMT;writing-mode:lr-tb;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="text3836">
<tspan x="0 875.01031 1548.8411 2222.6716" y="0" id="tspan3834">C129</tspan>
</text>
</g>
</g>
</g>
</svg>
<textarea name="svgOpt" id="svgOpt" style="width:100%; min-height:30em"></textarea>
The above snippet queries all <path> elements.
If there are recurring d path data attributes – we can save them to a reusable asset within the <defs> element and replace these instances with a referenced <use> element:
So each instance of
<path d="M 837.5,0 C 837.5,462.53851 462.53851,837.5 0,837.5 -462.53851,837.5 -837.5,462.53851 -837.5,0 c 0,-462.53851 374.96149,-837.5 837.5,-837.5 462.53851,0 837.5,374.96149 837.5,837.5" />
will be replaced by
<use href="#p0" />
defined in <defs> by
<path id="p0" d="M837.5 0C837.5 462.53851 462.53851 837.5 0 837.5-462.53851 837.5-837.5 462.53851-837.5 0c0-462.53851 374.96149-837.5 837.5-837.5 462.53851 0 837.5 374.96149 837.5 837.5" />
This replacements are achieved with a helper method pathsToUse()
function pathsToUse(paths) {
let useObj = {};
paths.forEach(function (path, i) {
let d = path.getAttribute("d").replaceAll(",", " ");
let id = path.id;
//add auto ids
if (!id) {
path.setAttribute("id", "pathID" + i);
}
//collect all// d/pathdata
if (d in useObj === false) {
useObj[d] = {
count: 1,
ids: [id]
};
} else {
useObj[d]["count"] += 1;
useObj[d]["ids"].push(id);
}
});
//replace paths with <use> elements
let useDefs = "";
let useCount = 0;
for (d in useObj) {
let current = useObj[d];
let occurrences = current["ids"];
if (occurrences.length > 1) {
let useID = "p" + useCount;
//create def
useDefs += `<path id="${useID}" d="${d}" />\n`;
useCount++;
occurrences.forEach(function (id, i) {
let el = svg.getElementById(id);
let className = el.getAttribute("class");
let use = document.createElementNS(ns, "use");
use.setAttribute("href", "#" + useID);
use.setAttribute("xlink:href", "#" + useID);
use.classList.add(className);
el.replaceWith(use);
});
}
}
defs.insertAdjacentHTML("beforeend", useDefs);
}
It loops through all <path> elements saving their d attributes to an data object also counting the number of occurrences/instances. In another loop we're replacing repeatedly used <path> instances with <use> elements.
We are saving the different d values as the object keys – this way we can easily increment an occurrence counter.
Besides we can save a lot of redundant code by replacing inline styles with class rules and removing useless or propriatary style properties like opacity:1 (default anyway) or -inkscape-font-specification:ArialMT (inkscape specific).
Other optimizations
reduce unnecessary groups (containing only one child node)
reduce unnecessary <tspan> elements that could be reduced to one single <text> element
concatenate single adjacent <path> elements
Complete optimization codepen example
(shrinking the filesize from ~400 KB to 100 KB)
Other optimization methods
Obviously you could also try Jake Archibald's SVGOMG
But always keep your original svg file as a backup, since SVGOMG tends to have quite aggressive optimization default settings.
So you need to compare different settings to get the best result – don't just look at the decreased file size!
Otherwise you might loose valuable markup/attributes such as ids or classes that you might need for your sites's js logic or even your css styling concept.
What about changing it to circle type in the SVG:
<circle cx="100" cy="100" r="75" />

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>

Put arrow head on path where intersects with a circle

I have a path that ends at the center of a circle. I want to place an arrow head on the path pointing toward the circle. I want the point of the arrow head to be at the point of intersection between the path and the circle. How can I achieve this?
I know the radius of the circle. The path is a quadratic curve between two points from this accepted answer: Create svg arcs between two points
You just need to calculate the coordinates of a suitable point on the circumference of the circle.
I've simplified here and just moved the start and end points in (on the X axis) by the circle radius. If you want it to start and end at a different point on the circle(s), then you'll need to invoke a little simple geometry. But this example should at least give you a head start.
The arrowhead marker I've used is configured to point at the end point of the line (as opposed to starting at the end of it).
function arc_links(dwg,x1,y1,x2,y2,k) {
var cx = (x1+x2)/2;
var cy = (y1+y2)/2;
var dx = (x2-x1)/2;
var dy = (y2-y1)/2;
dd = Math.sqrt(dx*dx+dy*dy);
ex = cx + dy/dd * k;
ey = cy - dx/dd * k;
var path = dwg.path("M"+x1+" "+y1+"Q"+ex+" "+ey+" "+x2+" "+y2).stroke({width:1}).fill('none')
.marker('end', SVG.select('#arrowhead').first());
}
function create_svg() {
var draw = SVG.select('#drawing').first();
draw.circle(50).move(25,25).fill('#fff').stroke({width:1});
draw.circle(50).move(225,25).fill('#fff').stroke({width:1});
arc_links(draw,75,50,225,50,40);
}
create_svg();
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.3.2/svg.min.js"></script>
<svg id="drawing" width="300" height="300">
<defs>
<marker id="arrowhead" markerUnits="userSpaceOnUse" markerWidth="10" markerHeight="10"
refX="10" refY="0" viewBox="0 -5 10 10" orient="auto">
<path d="M0 -5L10 0L0 5 Z"></path>
</marker>
</defs>
</svg>

Draw a hollow circle in SVG

I'm not sure how to approach drawing a hollow circle in SVG.
I would like a ring shape filled with a colour and then have a black outline.
The way I thought about doing it was have 2 circles, one with a smaller radius than the other. The problem is when I fill them, how do I make the smaller circle take the same fill colour as what it sits on?
Just use fill="none" and then only the stroke (outline) will be drawn.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="none" />
</svg>
Or this if you want two colours:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="3" fill="none" />
<circle cx="100" cy="50" r="39" stroke="red" stroke-width="2" fill="none" />
</svg>
MDragon00's answer works, but the inner and outer circles are not perfectly aligned (e.g. centered).
I modified his approach a little, using 4 semi-circle arcs (2 outer and 2 inner in reversed direction) to get the alignment exactly right.
<svg width="100" height="100">
<path d="M 50 10 A 40 40 0 1 0 50 90 A 40 40 0 1 0 50 10 Z M 50 30 A 20 20 0 1 1 50 70 A 20 20 0 1 1 50 30 Z" fill="#0000dd" stroke="#00aaff" stroke-width="3" />
</svg>
<!--
Using this path definition as d:
M centerX (centerY-outerRadius)
A outerRadius outerRadius 0 1 0 centerX (centerY+outerRadius)
A outerRadius outerRadius 0 1 0 centerX (centerY-outerRadius)
Z
M centerX (centerY-innerRadius)
A innerRadius innerRadius 0 1 1 centerX (centerY+innerRadius)
A innerRadius innerRadius 0 1 1 centerX (centerY-innerRadius)
Z
-->
Thanks to Chasbeen, I figured out how to make a true ring/donut in SVG. Note that the outer circle actually isn't closed, which is only apparent when you use a stroke. Very useful when you have many concentric rings, especially if they're interactive (say, with CSS hover commands).
For the draw command...
M cx, cy // Move to center of ring
m 0, -outerRadius // Move to top of ring
a outerRadius, outerRadius, 0, 1, 0, 1, 0 // Draw outer arc, but don't close it
Z // default fill-rule:even-odd will help create the empty innards
m 0 outerRadius-innerRadius // Move to top point of inner radius
a innerRadius, innerRadius, 0, 1, 1, -1, 0 // Draw inner arc, but don't close it
Z // Close the inner ring. Actually will still work without, but inner ring will have one unit missing in stroke
JSFiddle - Contains several rings and CSS to simulate interactivity. Note the downside that there's a single pixel missing at the starting point (at the top), which is only there if you add a stroke on.
Edit:
Found this SO answer (and better yet, this answer) which describes how to get the empty innards in general
You can do this as per the SVG spec by using a path with two components and fill-rule="evenodd". The two components are semi-circular arcs which join to form a circle (in the "d" attribute below, they each end with a 'z'). The area inside the inner circle does not count as part of the shape, hence interactivity is good.
To decode the below a little, the "340 260" is the top middle of the outer circle, the "290 290" is the radius of the outer circle (twice), the "340 840" is the bottom middle of the outer circle, the "340 492" is the top middle of the inner circle, the "58 58" is the radius of the inner circle (twice) and the "340 608" is the bottom middle of the inner circle.
<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M340 260A290 290 0 0 1 340 840A290 290 0 0 1 340 260zM340 492A58 58 0 0 1 340 608A58 58 0 0 1 340 492z" stroke-width="4" stroke="rgb(0,0,0)" fill="rgb(0,0,255)">
<title>This will only display on the donut</title>
</path>
</svg>
This is the classic donut shape
I'm not sure if you are trying to achieve this with standard SVG or JavaScript that produces SVG
The objective can be achieved by including a relative "moveto" command in a single path definition
And click "donut holes" on the right side of the interactive examples.
At the very least you can see the path definition that made the red donut.
Here's a routine to create a bezier arc which is as close as makes no odds to a circle. You need four of them in a path for a complete circle.
BezierCurve BezierArc(double ox, double oy, double r, double thetaa, double thetab)
{
double theta;
double cpx[4];
double cpy[4];
int i;
int sign = 1;
while (thetaa > thetab)
thetab += 2 * Pi;
theta = thetab - thetaa;
if (theta > Pi)
{
theta = 2 * Pi - theta;
sign = -1;
}
cpx[0] = 1;
cpy[0] = 0;
cpx[1] = 1;
cpy[1] = 4.0 / 3.0 * tan(theta / 4);
cpx[2] = cos(theta) + cpy[1] * sin(theta);
cpy[2] = sin(theta) - cpy[1] * cos(theta);
cpx[3] = cos(theta);
cpy[3] = sin(theta);
cpy[1] *= sign;
cpy[2] *= sign;
cpy[3] *= sign;
for (i = 0; i < 4; i++)
{
double xp = cpx[i] * cos(thetaa) + cpy[i] * -sin(thetaa);
double yp = cpx[i] * sin(thetaa) + cpy[i] * cos(thetaa);
cpx[i] = xp;
cpy[i] = yp;
cpx[i] *= r;
cpy[i] *= r;
cpx[i] += ox;
cpy[i] += oy;
}
return BezierCurve({cpx[0], cpy[0]},{cpx[1], cpy[1]}, {cpx[2], cpy[2]}, {cpx[3], cpy[3]});
}

Resources