How can I fix path data as circle in svg? - 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" />

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 to know the m or M point of path from all of points in svg?(use raphael.js)

I have an svg picture like this, with M and m in the path. I use raphael.js to calculate. I can get all the points by calling Raphael.getTotalLength(path), but how can I know from which point is the path behind m.
I want to know all the absolute coordinate points of the inner border of the svg, so that I can get a data format similar to the following [[points of the outer border], [points of the inner border]].
This is how I achieved it. I can get all the points, but I can’t distinguish which are the points of the inner frame and which are the points of the outer frame.
import Raphael from 'raphael';
function getPoints(path) {
let points = [];
for (let i = 0; i < Raphael.getTotalLength(path); i += step_point) {
const point = Raphael.getPointAtLength(path, i);
points.push(point);
}
}
This is the svg content:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<path
d="M72.8525 8.0439c-.405 0-.816.022-1.222.064-4.973.523-8.939 4.112-10.048 8.833-.73-.028-1.453-.043-2.162-.043-5.453 0-10.307.814-14.464 2.423-1.988-3.703-5.849-6.071-10.173-6.071-.973 0-1.949.124-2.899.37-2.994.773-5.508 2.667-7.078 5.331-1.571 2.664-2.01 5.781-1.236 8.775.772 2.986 2.693 5.506 5.301 7.052-1.056 2.419-1.535 4.533-1.814 6.02-.623 3.314-2.519 13.398 5.355 20.728 3.209 2.988 6.672 4.84 10.937 5.8-3.558 4.888-7.226 11.138-8.02 16.945-.349 2.543-.027 4.749.956 6.576l-.149.355c-.034.081-.062.165-.085.25-.315 1.166-.025 2.398.778 3.295.672.754 1.639 1.187 2.649 1.187.044 0 .088-.001.131-.002.27.406.626.758 1.053 1.029.518.33 1.109.519 1.719.55l-1.044 4.167c-.571 2.283.813 4.636 3.086 5.245l10.106 2.708c.372.1.753.15 1.132.15 1.299 0 2.521-.582 3.351-1.595.825-1.008 1.151-2.32.894-3.6-.013-.064-.007-.13.016-.189l1.1-2.829 3.729 6.22c.78 1.3 2.206 2.109 3.723 2.109.759 0 1.509-.202 2.168-.582l9.061-5.232c1.308-.756 2.119-2.108 2.167-3.619.03-.959-.249-1.873-.779-2.627.6-.052 1.175-.255 1.679-.595.42-.283.766-.644 1.024-1.058h.035c1.047 0 2.037-.459 2.713-1.259.778-.92 1.035-2.16.687-3.318-.025-.084-.056-.166-.092-.246l-.158-.35c.933-1.854 1.194-4.068.777-6.6-1.181-7.17-6.763-14.845-10.84-19.646 1.556-.529 3.061-1.122 4.547-1.793 6.708-3.027 9.062-8.913 9.395-11.913.346-3.113-.969-9.08-2.01-12.015-1.056-2.977-3.244-8.332-6.599-12.674 1.647-2.29 2.406-5.105 2.106-7.957-.621-5.911-5.566-10.369-11.503-10.369m0 2c4.84 0 8.997 3.657 9.514 8.578.312 2.97-.769 5.764-2.716 7.735 4.039 4.59 6.48 11.006 7.313 13.355 1.049 2.957 2.192 8.566 1.908 11.126-.285 2.56-2.435 7.696-8.23 10.311-2.229 1.006-4.658 1.897-7.363 2.639.216.171.429.348.617.556 3.231 3.599 10.589 12.513 11.888 20.392.453 2.753-.065 4.727-1.052 6.153l.583 1.294c.149.498.04 1.049-.299 1.451-.296.349-.728.55-1.186.55-.367 0-.722-.13-1.002-.367-.127-.107-.234-.231-.32-.372l-.182-.406c-.053.03-.108.056-.161.085l.17 1.142c.01.521-.245 1.023-.682 1.318-.258.174-.558.266-.867.266-.518 0-.999-.256-1.288-.685-.093-.138-.163-.287-.208-.447l-.078-.525s0 .001-.001.001c-.092.021-.184.022-.277.035-.125.259-.314.488-.566.645-.247.154-.531.235-.82.235-.22 0-.427-.055-.621-.14l1.058 2.404c.18.409.472.758.845 1.006 1.444.961 1.377 3.104-.126 3.973l-9.061 5.23c-.368.213-.77.315-1.168.315-.795 0-1.57-.407-2.008-1.137l-4.403-7.347c-.752-.015-1.524-.056-2.306-.11l-1.698 4.368c-.163.417-.202.87-.114 1.309.3 1.49-.864 2.801-2.284 2.801-.201 0-.407-.026-.614-.082l-10.106-2.708c-1.234-.33-1.974-1.589-1.664-2.827l1.907-7.612c-.062-.027-.127-.044-.185-.077-.256-.151-.451-.374-.584-.631-.092-.01-.185-.009-.278-.027 0 0-.001 0-.001-.001l-.065.532c-.04.159-.105.309-.193.448-.287.45-.777.72-1.312.72-.294 0-.582-.084-.832-.243-.444-.283-.713-.777-.717-1.298l.139-1.147c-.054-.027-.11-.052-.163-.081l-.174.415c-.082.142-.184.268-.307.377-.285.254-.652.395-1.034.395-.441 0-.864-.19-1.158-.519-.35-.392-.474-.939-.339-1.441l.548-1.311c-1.026-1.397-1.598-3.356-1.219-6.121.917-6.699 6.151-14.247 9.637-18.644-4.885-.547-9.142-2.083-13.173-5.836-6.746-6.28-5.521-14.805-4.752-18.894.384-2.041 1.039-4.558 2.526-7.33-2.881-1.035-5.218-3.424-6.041-6.612-1.324-5.122 1.756-10.347 6.877-11.67.802-.207 1.607-.306 2.399-.306 4.097 0 7.844 2.654 9.119 6.698 5.236-2.473 11.057-3.05 15.518-3.05 1.264 0 2.419.047 3.42.109 0 0 .169.006.449.024.281-4.587 3.828-8.437 8.55-8.934.34-.035.678-.053 1.013-.053"
fill="#F00" stroke="#000" />
</svg>
I really look forward to your answers, thank you!
As I mentioned elsewhere. You can split on M. If for some reason this doesn't make sense (as you mentioned multiple 'm's) as you say "behind m", then you need to amend your question to be more precise about where the split would be, if this is part of a more generic issue.
So we can split the string on "m"
var match = new RegExp("(^[^m]*)(.*)", "").exec(path);
And get the last point of the first path part...
var point = Raphael.getPointAtLength(match[1], p.getTotalLength(match[1]));
Then add the final point to the first path...
var p2 = r.path("M" + point.x + "," + point.y + match[2]).attr('stroke','blue')
And I've amended getPoints()...
function getPoints(path) {
let points = [];
let step_point = 10;
for (let i = 0; i < path.getTotalLength(); i += step_point) {
const point = path.getPointAtLength(i);
points.push(point);
}
return points;
}
And get the sets of points...
console.log(getPoints(p1));
console.log(getPoints(p2));
jsfiddle showing different colours for outer/inner and it dumps in the console the two sets of points.

SVG TextPath characters missing

I am trying to display a curved text that I'm generating with JavaScript
When I increase the curvature, some characters disappear
Here a JSFiddle I created
Here's my function which generated the path data based on the curve percentage and the text size
public getPathData (width, height, curve): string {
const positive_curve = (curve > 0);
const perimeter = width / Math.abs(curve) * 100;
const radius = perimeter / (2 * Math.PI) + (positive_curve ? 0 : height);
const sign = positive_curve ? 1 : 1;
const side = positive_curve ? 1 : 0;
const diameter_parameter = sign * (2 * radius);
return `m0,${radius}
a${radius},${radius} 0 0 ${side} ${diameter_parameter},0
a${radius},${radius} 0 0 ${side} ${-diameter_parameter},0Z`;
}
Is this a browser issue? Or is the issue lying in the path itself?
Updated code based on #Paul LeBeau answer
public getPathData (width, height, curve): string {
const perimeter = width / Math.abs(curve) * 100;
const radius = perimeter / (2 * Math.PI);
const diameter = radius * 2;
if (curve > 0) {
return `m${radius},${diameter}
a${radius},${radius} 0 0 1 0 ${diameter}
a${radius},${radius} 0 0 1 0 ${diameter}Z`;
} else {
return `m${radius},${diameter}
a${radius},${radius} 0 0 0 0 ${diameter}
a${radius},${radius} 0 0 0 0 ${-diameter}Z`;
}
}
It basically wraps a text around the inside or outside of a circle based on a curve percentage [-100%, 100%]
Your problem is due to the position of the start of the path. Path text won't be drawn past the start or end of the path. In the image below, I have put a black dot where the start of the path is:
<svg viewBox="-270 -270 1290 1280" width="257" height="256">
<path id="curve" fill="white" stroke="red" stroke-width="1px"
d="m0,400
a400,400 0 0 1 800,0
a400,400 0 0 1 -800,0Z">
</path>
<circle cx="0" cy="400" r="20"/>
<text font-size="300" font-family="'Arial'" fill="#ff0000" x="0" y="0" text-anchor="middle">
<textPath href="#curve" startOffset="25%">This is a new test</textPath>
</text>
</svg>
Even though it is a closed path, the text will not wrap back past the start of the path and onto the end part of the path. Any text that overflows the start, will just get cut off.
Since I assume you probably want the text to wrap around the circle from 7 o'clock to 5 o'clock, your simplest solution would be to move the start of the path to the bottom of the circle (6 o'clock):
<svg viewBox="-270 -270 1290 1280" width="257" height="256">
<path id="curve" fill="white" stroke="red" stroke-width="1px"
d="m400,800
a400,400 0 0 1 0,-800
a400,400 0 0 1 0,800Z">
</path>
<circle cx="400" cy="800" r="20"/>
<text font-size="300" font-family="'Arial'" fill="#ff0000" x="0" y="0" text-anchor="middle">
<textPath href="#curve" startOffset="50%">This is a new test</textPath>
</text>
</svg>

How can I get a rect with just two of the corners rounded? [duplicate]

I have the following SVG:
<svg>
<g>
<path id="k9ffd8001" d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z" stroke="#808600" stroke-width="0" transform="rotate(0 0 0)" stroke-linecap="square" stroke-linejoin="round" fill-opacity="1" stroke-opacity="1" fill="#a0a700"></path>
<path id="kb8000001" d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z" stroke="#808600" stroke-width="0" transform="rotate(0 0 0)" stroke-linecap="square" stroke-linejoin="round" fill-opacity="1" stroke-opacity="1" fill="url(#k9ffb0001)"></path>
</g>
</svg>
I want to get a CSS-like border-top-right-radius and border-top-bottom-radius effect.
How can I achieve that rounded corner effect?
Here is how you can create a rounded rectangle with SVG Path:
<path d="M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z" />
Explanation
m100,100: move to point(100,100)
h200: draw a 200px horizontal line from where we are
a20,20 0 0 1 20,20: draw an arc with 20px X radius, 20px Y radius, clockwise, to a point with 20px difference in X and Y axis
v200: draw a 200px vertical line from where we are
a20,20 0 0 1 -20,20: draw an arc with 20px X and Y radius, clockwise, to a point with -20px difference in X and 20px difference in Y axis
h-200: draw a -200px horizontal line from where we are
a20,20 0 0 1 -20,-20: draw an arc with 20px X and Y radius, clockwise, to a point with -20px difference in X and -20px difference in Y axis
v-200: draw a -200px vertical line from where we are
a20,20 0 0 1 20,-20: draw an arc with 20px X and Y radius, clockwise, to a point with 20px difference in X and -20px difference in Y axis
z: close the path
<svg width="440" height="440">
<path d="M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z" fill="none" stroke="black" stroke-width="3" />
</svg>
Not sure why nobody posted an actual SVG answer. Here is an SVG rectangle with rounded corners (radius 3) on the top:
<path d="M0,0 L0,27 A3,3 0 0,0 3,30 L7,30 A3,3 0 0,0 10,27 L10,0 Z" />
This is a Move To (M), Line To (L), Arc To (A), Line To (L), Arc To (A), Line To (L), Close Path (Z).
The comma-delimited numbers are absolute coordinates. The arcs are defined with additional parameters specifying the radius and type of arc. This could also be accomplished with relative coordinates (use lower-case letters for L and A).
The complete reference for those commands is on the W3C SVG Paths page, and additional reference material on SVG paths can be found in this article.
As referenced in my answer to Applying rounded corners to paths/polygons, I have written a routine in javascript for generically rounding corners of SVG paths, with examples, here: http://plnkr.co/edit/kGnGGyoOCKil02k04snu.
It will work independently from any stroke effects you may have. To use, include the rounding.js file from the Plnkr and call the function like so:
roundPathCorners(pathString, radius, useFractionalRadius)
The result will be the rounded path.
The results look like this:
You have explicitly set your stroke-linejoin to round but your stroke-width to 0, so of course you're not going to see rounded corners if you have no stroke to round.
Here's a modified example with rounded corners made through strokes:
http://jsfiddle.net/8uxqK/1/
<path d="M64.5 45.5 82.5 45.5 82.5 64.5 64.5 64.5 z"
stroke-width="5"
stroke-linejoin="round"
stroke="#808600"
fill="#a0a700" />
Otherwise—if you need an actual rounded shape fill and not just a rounded fatty stroke—you must do what #Jlange says and make an actual rounded shape.
I'd also consider using a plain old <rect> which provides the rx and ry attributes
MDN SVG docs <- note the second drawn rect element
I've happened upon this problem today myself and managed to solve it by writing a small JavaScript function.
From what I can tell, there is no easy way to give a path element in an SVG rounded corners except if you only need the borders to be rounded, in which case the (CSS) attributes stroke, stroke-width and most importantly stroke-linejoin="round" are perfectly sufficient.
However, in my case I used a path object to create custom shapes with n corners that are filled out with a certain color and don't have visible borders, much like this:
I managed to write a quick function that takes an array of coordinates for an SVG path and returns the finished path string to put in the d attribute of the path html element. The resulting shape will then look something like this:
Here is the function:
/**
* Creates a coordinate path for the Path SVG element with rounded corners
* #param pathCoords - An array of coordinates in the form [{x: Number, y: Number}, ...]
*/
function createRoundedPathString(pathCoords) {
const path = [];
const curveRadius = 3;
// Reset indexes, so there are no gaps
pathCoords = pathCoords.slice();
for (let i = 0; i < pathCoords.length; i++) {
// 1. Get current coord and the next two (startpoint, cornerpoint, endpoint) to calculate rounded curve
const c2Index = ((i + 1) > pathCoords.length - 1) ? (i + 1) % pathCoords.length : i + 1;
const c3Index = ((i + 2) > pathCoords.length - 1) ? (i + 2) % pathCoords.length : i + 2;
const c1 = pathCoords[i];
const c2 = pathCoords[c2Index];
const c3 = pathCoords[c3Index];
// 2. For each 3 coords, enter two new path commands: Line to start of curve, bezier curve around corner.
// Calculate curvePoint c1 -> c2
const c1c2Distance = Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
const c1c2DistanceRatio = (c1c2Distance - curveRadius) / c1c2Distance;
const c1c2CurvePoint = [
((1 - c1c2DistanceRatio) * c1.x + c1c2DistanceRatio * c2.x).toFixed(1),
((1 - c1c2DistanceRatio) * c1.y + c1c2DistanceRatio * c2.y).toFixed(1)
];
// Calculate curvePoint c2 -> c3
const c2c3Distance = Math.sqrt(Math.pow(c2.x - c3.x, 2) + Math.pow(c2.y - c3.y, 2));
const c2c3DistanceRatio = curveRadius / c2c3Distance;
const c2c3CurvePoint = [
((1 - c2c3DistanceRatio) * c2.x + c2c3DistanceRatio * c3.x).toFixed(1),
((1 - c2c3DistanceRatio) * c2.y + c2c3DistanceRatio * c3.y).toFixed(1)
];
// If at last coord of polygon, also save that as starting point
if (i === pathCoords.length - 1) {
path.unshift('M' + c2c3CurvePoint.join(','));
}
// Line to start of curve (L endcoord)
path.push('L' + c1c2CurvePoint.join(','));
// Bezier line around curve (Q controlcoord endcoord)
path.push('Q' + c2.x + ',' + c2.y + ',' + c2c3CurvePoint.join(','));
}
// Logically connect path to starting point again (shouldn't be necessary as path ends there anyway, but seems cleaner)
path.push('Z');
return path.join(' ');
}
You can determine the rounding strength by setting the curveRadius variable at the top. The default is 3 for a 100x100 (viewport) coordinate system, but depending on the size of your SVG, you may need to adjust this.
For my case I need to radius begin and end of path:
With stroke-linecap: round; I change it to what I want:
This question is the first result for Googling "svg rounded corners path". Phrogz suggestion to use stroke has some limitations (namely, that I cannot use stroke for other purposes, and that the dimensions have to be corrected for the stroke width).
Jlange suggestion to use a curve is better, but not very concrete. I ended up using quadratic Bézier curves for drawing rounded corners. Consider this picture of a corner marked with a blue dot and two red points on adjacent edges:
The two lines could be made with the L command. To turn this sharp corner into a rounded corner, start drawing a curve from the left red point (use M x,y to move to that point). Now a quadratic Bézier curve has just a single control point which you must set on the blue point. Set the end of the curve at the right red point. As the tangent at the two red points are in the direction of the previous lines, you will see a fluent transition, "rounded corners".
Now to continue the shape after the rounded corner, a straight line in a Bézier curve can be achieved by setting the control point between on the line between the two corners.
To help me with determining the path, I wrote this Python script that accepts edges and a radius. Vector math makes this actually very easy. The resulting image from the output:
#!/usr/bin/env python
# Given some vectors and a border-radius, output a SVG path with rounded
# corners.
#
# Copyright (C) Peter Wu <peter#lekensteyn.nl>
from math import sqrt
class Vector(object):
def __init__(self, x, y):
self.x = x
self.y = y
def sub(self, vec):
return Vector(self.x - vec.x, self.y - vec.y)
def add(self, vec):
return Vector(self.x + vec.x, self.y + vec.y)
def scale(self, n):
return Vector(self.x * n, self.y * n)
def length(self):
return sqrt(self.x**2 + self.y**2)
def normal(self):
length = self.length()
return Vector(self.x / length, self.y / length)
def __str__(self):
x = round(self.x, 2)
y = round(self.y, 2)
return '{},{}'.format(x, y)
# A line from vec_from to vec_to
def line(vec_from, vec_to):
half_vec = vec_from.add(vec_to.sub(vec_from).scale(.5))
return '{} {}'.format(half_vec, vec_to)
# Adds 'n' units to vec_from pointing in direction vec_to
def vecDir(vec_from, vec_to, n):
return vec_from.add(vec_to.sub(vec_from).normal().scale(n))
# Draws a line, but skips 'r' units from the begin and end
def lineR(vec_from, vec_to, r):
vec = vec_to.sub(vec_from).normal().scale(r)
return line(vec_from.add(vec), vec_to.sub(vec))
# An edge in vec_from, to vec_to with radius r
def edge(vec_from, vec_to, r):
v = vecDir(vec_from, vec_to, r)
return '{} {}'.format(vec_from, v)
# Hard-coded border-radius and vectors
r = 5
a = Vector( 0, 60)
b = Vector(100, 0)
c = Vector(100, 200)
d = Vector( 0, 200 - 60)
path = []
# Start below top-left edge
path.append('M {} Q'.format(a.add(Vector(0, r))))
# top-left edge...
path.append(edge(a, b, r))
path.append(lineR(a, b, r))
path.append(edge(b, c, r))
path.append(lineR(b, c, r))
path.append(edge(c, d, r))
path.append(lineR(c, d, r))
path.append(edge(d, a, r))
path.append(lineR(d, a, r))
# Show results that can be pushed into a <path d="..." />
for part in path:
print(part)
Here are some paths for tabs:
https://codepen.io/mochime/pen/VxxzMW
<!-- left tab -->
<div>
<svg width="60" height="60">
<path d="M10,10
a10 10 0 0 1 10 -10
h 50
v 47
h -50
a10 10 0 0 1 -10 -10
z"
fill="#ff3600"></path>
</svg>
</div>
<!-- right tab -->
<div>
<svg width="60" height="60">
<path d="M10 0
h 40
a10 10 0 0 1 10 10
v 27
a10 10 0 0 1 -10 10
h -40
z"
fill="#ff3600"></path>
</svg>
</div>
<!-- tab tab :) -->
<div>
<svg width="60" height="60">
<path d="M10,40
v -30
a10 10 0 0 1 10 -10
h 30
a10 10 0 0 1 10 10
v 30
z"
fill="#ff3600"></path>
</svg>
</div>
The other answers explained the mechanics. I especially liked hossein-maktoobian's answer.
The paths in the pen do the brunt of the work, the values can be modified to suite whatever desired dimensions.
This basically does the same as Mvins answer, but is a more compressed down and simplified version. It works by going back the distance of the radius of the lines adjacent to the corner and connecting both ends with a bezier curve whose control point is at the original corner point.
function createRoundedPath(coords, radius, close) {
let path = ""
const length = coords.length + (close ? 1 : -1)
for (let i = 0; i < length; i++) {
const a = coords[i % coords.length]
const b = coords[(i + 1) % coords.length]
const t = Math.min(radius / Math.hypot(b.x - a.x, b.y - a.y), 0.5)
if (i > 0) path += `Q${a.x},${a.y} ${a.x * (1 - t) + b.x * t},${a.y * (1 - t) + b.y * t}`
if (!close && i == 0) path += `M${a.x},${a.y}`
else if (i == 0) path += `M${a.x * (1 - t) + b.x * t},${a.y * (1 - t) + b.y * t}`
if (!close && i == length - 1) path += `L${b.x},${b.y}`
else if (i < length - 1) path += `L${a.x * t + b.x * (1 - t)},${a.y * t + b.y * (1 - t)}`
}
if (close) path += "Z"
return path
}
Here’s a piece of react code to generate rectangles with different corner radiuses:
const Rect = ({width, height, tl, tr, br, bl}) => {
const top = width - tl - tr;
const right = height - tr - br;
const bottom = width - br - bl;
const left = height - bl - tl;
const d = `
M${tl},0
h${top}
a${tr},${tr} 0 0 1 ${tr},${tr}
v${right}
a${br},${br} 0 0 1 -${br},${br}
h-${bottom}
a${bl},${bl} 0 0 1 -${bl},-${bl}
v-${left}
a${tl},${tl} 0 0 1 ${tl},-${tl}
z
`;
return (
<svg width={width} height={height}>
<path d={d} fill="black" />
</svg>
);
};
ReactDOM.render(
<Rect width={200} height={100} tl={20} tr={0} br={20} bl={60} />,
document.querySelector('#app'),
);
https://jsfiddle.net/v1Ljpxh7/
Just to simplify implementing answer of #hmak.me, here's a commented piece of React code to generate rounded rectangles.
const Rect = ({width, height, round, strokeWidth}) => {
// overhang over given width and height that we get due to stroke width
const s = strokeWidth / 2;
// how many pixels do we need to cut from vertical and horizontal parts
// due to rounded corners and stroke width
const over = 2 * round + strokeWidth;
// lengths of straight lines
const w = width - over;
const h = height - over;
// beware that extra spaces will not be minified
// they are added for clarity
const d = `
M${round + s},${s}
h${w}
a${round},${round} 0 0 1 ${round},${round}
v${h}
a${round},${round} 0 0 1 -${round},${round}
h-${w}
a${round},${round} 0 0 1 -${round},-${round}
v-${h}
a${round},${round} 0 0 1 ${round},-${round}
z
`;
return (
<svg width={width} height={height}>
<path d={d} fill="none" stroke="black" strokeWidth={strokeWidth} />
</svg>
);
};
ReactDOM.render(
<Rect width={64} height={32} strokeWidth={2} round={4} />,
document.querySelector('#app'),
);
Jsfiddle link.
I wrote this little typescript function so I can dynamically create the path for a complex rounded rectangle that function similar to a div with border-radius.
export function roundedRectPath(
x: number,
y: number,
width: number,
height: number,
bevel: [number, number, number, number] = [3, 3, 3, 3]
): string {
return "M" + x + "," + y
+ `m 0 ${bevel[0]}`
+ `q 0 -${bevel[0]} ${bevel[0]} -${bevel[0]}`
+ `l ${width - bevel[0] - bevel[1]} 0`
+ `q ${bevel[1]} 0 ${bevel[1]} ${bevel[1]}`
+ `l 0 ${height - bevel[1] - bevel[2]}`
+ `q 0 ${bevel[2]} -${bevel[2]} ${bevel[2]}`
+ `l -${width - bevel[2] - bevel[3]} 0`
+ `q -${bevel[3]} 0 -${bevel[3]} -${bevel[3]}`
+ `z`;
}
I found a solution but it is a bit hacky so it may not always work. I found that if you have an arc (A or a) with really small values it forces it to create a curve in one spot thus forming a rounded comer...
<svg viewBox="0 0 1 0.6" stroke="black" fill="grey" style="stroke-width:0.05px;">
<path d="M0.7 0.2 L0.1 0.1 A0.0001 0.0001 0 0 0 0.099 0.101 L0.5 0.5Z"></path>
</svg>
You are using a path element, why don't you just give the path a curve? See here for how to make curves using path elements: http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands

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