i am trying to convert an svg path to an svg polygon in javascript. i found this function to crawl along the path and extract its coordinates.
var length = path.getTotalLength();
var p=path.getPointAtLength(0);
var stp=p.x+","+p.y;
for(var i=1; i<length; i++){
p=path.getPointAtLength(i);
stp=stp+" "+p.x+","+p.y;
}
this works but it returns some hundreds of points for a polygon that has only six points originally. how would i get only the necessary points (all paths are straight lines, no curves)
var path = $.find("path")[0];
var len = path.getTotalLength();
var p = path.getPointAtLength(0);
var stp = p.x + "," + p.y;
var newp;
for (var i = 1; i < len; i++) {
newp = path.getPointAtLength(i);
if (newp.x != p.x && newp.y != p.y) {
p = newp;
} else {
continue;
}
stp = stp + " " + p.x + " " + p.y;
}
$('#poly').text(stp);
pre {
display: block;
white-space: pre-wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="425.2px" height="303.31px" viewBox="0 0 425.2 303.31" enable-background="new 0 0 425.2 303.31" xml:space="preserve">
<g>
<path id="path" fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" d="M256.768,227.211h-33.013l4.902-65.682l-40.357,59.06
l-26.654-37.931l0.292-0.298L264.865,77.415L256.768,227.211z M224.833,226.211h30.987l7.903-146.205L162.942,182.764
l25.346,36.069l41.643-60.94L224.833,226.211z"/>
</g>
</svg>
<code>
<pre id="poly">
</pre>
</code>
ok got it.. the function getPathSegAtLength() returns the number of the actual path segment. with that it's easy then.
var len = path.getTotalLength();
var p=path.getPointAtLength(0);
var seg = path.getPathSegAtLength(0);
var stp=p.x+","+p.y;
for(var i=1; i<len; i++){
p=path.getPointAtLength(i);
if (path.getPathSegAtLength(i)>seg) {
stp=stp+" "+p.x+","+p.y;
seg = path.getPathSegAtLength(i);
}
}
how would i get only the necessary points (all paths are straight lines, no curves)
If your path actually contains only linetos you can take a shortcut to retrieve polygon vertices by parsing the path data.
This approach requires to:
d attributeh, v to their longhand l equivalentsWe can check whether a path could be represented as a polygon by inspecting its d attribute like so:
function pathIsPolygon(d) {
// any beziers or arc commands?
let isPolygon = /[csqta]/gi.test(d) ? false : true
return isPolygon;
}
We're basically testing if the path data string contains any bézier or arc commands. If not (no c,s,q,t,a commands): we can proceed to retrieve vertices from the path data by getting the last couple of values representing the final on-path point. This check improves both performance and accuracy as we avoid unnecessary iterative pointAtLength() calculations and retain the original polygonal geometry of the path.
/**
* 1. is polygon:
*/
let path = document.getElementById("path");
let poly = document.getElementById("poly");
// parse pathdata
let d = path.getAttribute("d");
let pathData = parsePathDataNormalized(d);
/**
* check if path is already a polygon:
* just return the final command points
*/
let vertices;
let isPolygon = pathIsPolygon(d);
if (isPolygon) {
console.log(path.id, "is polygon");
vertices = getPathDataVertices(pathData);
}
//apply
let ptAtt = vertices
.map((pt) => {
return Object.values(pt);
})
.flat()
.join(" ");
poly.setAttribute("points", ptAtt);
//output vertices/point array
verticesOut.value = JSON.stringify(vertices, null, ' ');
function pathIsPolygon(d) {
// any beziers or arc commands?
let isPolygon = /[csqta]/gi.test(d) ? false : true;
return isPolygon;
}
function getPathDataVertices(pathData) {
let polyPoints = [];
pathData.forEach((com) => {
let values = com.values;
// get final on path point from last 2 values
if (values.length) {
let pt = {
x: values[values.length - 2],
y: values[values.length - 1]
};
polyPoints.push(pt);
}
});
return polyPoints;
}
/**
* Standalone pathData parser
* returns a pathData array compliant
* with the w3C SVGPathData interface draft
* https://svgwg.org/specs/paths/#InterfaceSVGPathData
*/
function parsePathDataNormalized(d) {
d = d
.replace(/[\n\r\t|,]/g, " ")
.trim()
.replace(/(\d)-/g, "$1 -")
.replace(/(\.)(?=(\d+\.\d+)+)(\d+)/g, "$1$3 ");
let pathData = [];
let cmdRegEx = /([mlcqazvhst])([^mlcqazvhst]*)/gi;
let commands = d.match(cmdRegEx);
// valid command value lengths
let comLengths = {m: 2,a: 7,c: 6,h: 1,l: 2,q: 4,s: 4,t: 2,v: 1,z: 0
};
// offsets for absolute conversion
let offX, offY, lastX, lastY;
for (let c = 0; c < commands.length; c++) {
let com = commands[c];
let type = com.substring(0, 1);
let typeRel = type.toLowerCase();
let typeAbs = type.toUpperCase();
let isRel = type === typeRel;
let chunkSize = comLengths[typeRel];
// split values to array
let values = com.substring(1, com.length).trim().split(" ").filter(Boolean);
/**
* A - Arc commands
* large arc and sweep flags
* are boolean and can be concatenated like
*/
if (typeRel === "a" && values.length != comLengths.a) {
let n = 0,
arcValues = [];
for (let i = 0; i < values.length; i++) {
let value = values[i];
// reset counter
if (n >= chunkSize) {
n = 0;
}
// if 3. or 4. parameter longer than 1
if ((n === 3 || n === 4) && value.length > 1) {
let largeArc = n === 3 ? value.substring(0, 1) : "";
let sweep = n === 3 ? value.substring(1, 2) : value.substring(0, 1);
let finalX = n === 3 ? value.substring(2) : value.substring(1);
let comN = [largeArc, sweep, finalX].filter(Boolean);
arcValues.push(comN);
n += comN.length;
} else {
arcValues.push(value);
n++;
}
}
values = arcValues.flat().filter(Boolean);
}
// string to number
values = values.map(Number);
// if string contains repeated shorthand commands - split them
let hasMultiple = values.length > chunkSize;
let chunk = hasMultiple ? values.slice(0, chunkSize) : values;
let comChunks = [{
type: type,
values: chunk
}];
// has implicit or repeated commands – split into chunks
if (hasMultiple) {
let typeImplicit = typeRel === "m" ? (isRel ? "l" : "L") : type;
for (let i = chunkSize; i < values.length; i += chunkSize) {
let chunk = values.slice(i, i + chunkSize);
comChunks.push({
type: typeImplicit,
values: chunk
});
}
}
// convert to absolute
if (c === 0) {
offX = values[0];
offY = values[1];
lastX = offX;
lastY = offY;
}
let typeFirst = comChunks[0].type;
typeAbs = typeFirst.toUpperCase();
isRel =
typeFirst.toLowerCase() === typeFirst && pathData.length ? true : false;
for (let i = 0; i < comChunks.length; i++) {
let com = comChunks[i];
let type = com.type;
let values = com.values;
let valuesL = values.length;
let comPrev = comChunks[i - 1] ?
comChunks[i - 1] :
c > 0 && pathData[pathData.length - 1] ?
pathData[pathData.length - 1] :
comChunks[i];
let valuesPrev = comPrev.values;
let valuesPrevL = valuesPrev.length;
isRel =
comChunks.length > 1 ?
type.toLowerCase() === type && pathData.length :
isRel;
if (isRel) {
com.type = comChunks.length > 1 ? type.toUpperCase() : typeAbs;
switch (typeRel) {
case "a":
com.values = [
values[0],
values[1],
values[2],
values[3],
values[4],
values[5] + offX,
values[6] + offY
];
break;
case "h":
case "v":
com.values = type === "h" ? [values[0] + offX] : [values[0] + offY];
break;
case "m":
case "l":
case "t":
com.values = [values[0] + offX, values[1] + offY];
break;
case "c":
com.values = [
values[0] + offX,
values[1] + offY,
values[2] + offX,
values[3] + offY,
values[4] + offX,
values[5] + offY
];
break;
case "q":
case "s":
com.values = [
values[0] + offX,
values[1] + offY,
values[2] + offX,
values[3] + offY
];
break;
}
}
// is absolute
else {
offX = 0;
offY = 0;
}
// convert shorthands
let shorthandTypes = ["H", "V", "S", "T"];
if (shorthandTypes.includes(typeAbs)) {
let cp1X, cp1Y, cpN1X, cpN1Y, cp2X, cp2Y;
if (com.type === "H" || com.type === "V") {
com.values =
com.type === "H" ? [com.values[0], lastY] : [lastX, com.values[0]];
com.type = "L";
} else if (com.type === "T" || com.type === "S") {
[cp1X, cp1Y] = [valuesPrev[0], valuesPrev[1]];
[cp2X, cp2Y] =
valuesPrevL > 2 ? [valuesPrev[2], valuesPrev[3]] : [valuesPrev[0], valuesPrev[1]];
// new control point
cpN1X = com.type === "T" ? lastX * 2 - cp1X : lastX * 2 - cp2X;
cpN1Y = com.type === "T" ? lastY * 2 - cp1Y : lastY * 2 - cp2Y;
com.values = [cpN1X, cpN1Y, com.values].flat();
com.type = com.type === "T" ? "Q" : "C";
}
}
pathData.push(com);
lastX =
valuesL > 1 ?
values[valuesL - 2] + offX :
typeRel === "h" ?
values[0] + offX :
lastX;
lastY =
valuesL > 1 ?
values[valuesL - 1] + offY :
typeRel === "v" ?
values[0] + offY :
lastY;
offX = lastX;
offY = lastY;
}
}
pathData[0].type = "M";
return pathData;
}
svg {
overflow: visible;
width:25%;
}
svg path {
stroke-width: 2%;
stroke: #ccc;
}
svg polygon {
stroke-width: 0.75%;
marker-start: url(#markerStart);
marker-mid: url(#markerRound);
}
textarea {
width: 100%;
display: block;
min-height: 10em;
}
<h3>Path is already a polygon</h3>
<svg id="svg" viewBox="0 0 425.2 303.31" enable-background="new 0 0 425.2 303.31" xml:space="preserve">
<g>
<path id="path" fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" d="M256.768,227.211h-33.013l4.902-65.682l-40.357,59.06
l-26.654-37.931l0.292-0.298L264.865,77.415L256.768,227.211z M224.833,226.211h30.987l7.903-146.205L162.942,182.764
l25.346,36.069l41.643-60.94L224.833,226.211z"/>
</g>
<polygon id="poly" />
</svg>
<h3>Point/vertices data</h3>
<textarea id="verticesOut"></textarea>
<!-- markers to show commands -->
<svg id="svgMarkers" style="width:0; height:0; position:absolute; z-index:-1;float:left;opacity:0">
<defs>
<marker id="markerStart" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth"
markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="5" fill="green"></circle>
<marker id="markerRound" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5"
markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="2.5" fill="red"></circle>
</marker>
</defs>
</svg>
By parsing the path data we can easily retrieve x and y coordinates from each command to create an array of point objects.
The parsing is done via a custom parsing script trying to be compliant with the suggested W3C pathdata interface spec draft. This draft is intended to replace the (mostly) deprecated/unsupported SVGPathSeg interface (although I won't hold my breath ... this concept is around since 2016).
Worth noting: there is still a SVGPathSeg polyfill.
You can actually use any path data parser (as long as it provides a way to convert commands to all absolute and "unshort" commands like hand v to l) for instance Jarek Foksa's path-data-polyfill setting the normalize parameter like so path.getPathData({normalize:true}).
If a path also contains curves, getPointAtlength() may not be ideal as it wont't retain the basic geometry – respecting command final points.

Despite points are calculated based on equal path length intervals the visual result appears to be rather arbitrary. We lose the expected symmetry of the original shape.
We can fix this issue by adding vertices according to each segment's length.
let decimals = 2;
let split = 32;
/**
* 1. is polygon:
*/
let path = document.getElementById('path')
let poly = document.getElementById('poly')
let vertices = getPathPolygonVertices(path, split, decimals);
//apply
let ptAtt = vertices.map(pt => {
return Object.values(pt)
}).flat().join(' ')
poly.setAttribute('points', ptAtt)
/**
* 2. has curves:
*/
let path1 = document.getElementById('path1')
let poly1 = document.getElementById('poly1')
let vertices1 = getPathPolygonVertices(path1, split, decimals);
//apply
let ptAtt1 = vertices1.map(pt => {
return Object.values(pt)
}).flat().join(' ')
poly1.setAttribute('points', ptAtt1)
function getPathPolygonVertices(path, split = 16, decimals = 3) {
let pts = []
let ns = 'http://www.w3.org/2000/svg'
// parse pathdata
let d = path.getAttribute('d')
let pathData = parsePathDataNormalized(d)
/**
* check if path is already polygon:
* just return the final command points
*/
let isPolygon = pathIsPolygon(d);
if (isPolygon) {
console.log(path.id, 'is polygon');
pts = getPathDataVertices(pathData)
return pts
}
// target side length
let totalLength = path.getTotalLength();
let step = totalLength / split;
let lastLength = 0;
let Mvalues = pathData[0].values;
let M = {
x: Mvalues[Mvalues.length - 2],
y: Mvalues[Mvalues.length - 1]
};
for (let i = 1; i < pathData.length; i++) {
let com = pathData[i];
let comPrev = pathData[i - 1];
let type = com.type.toLowerCase();
let [values, valuesPrev] = [com.values, comPrev.values];
//previous commands final point
let p0 = {
x: valuesPrev[valuesPrev.length - 2],
y: valuesPrev[valuesPrev.length - 1]
};
let p = values.length ? {
x: values[values.length - 2],
y: values[values.length - 1]
} : p0;
if (values.length) {
// create temporary path to get segment length
let pathSeg = document.createElementNS(ns, 'path')
pathSeg.setAttribute('d', `M ${p0.x} ${p0.y} ${com.type} ${com.values.join(' ')}`)
let segLength = pathSeg.getTotalLength()
// fit to segment length – keep command end points to better retain shape
let segSplits = Math.ceil(segLength / step);
// if lineto: no need to calculate points
if (type === 'l') {
pts.push(p0);
pts.push(p);
} else {
for (let s = 0; s < segSplits; s++) {
let len = lastLength + (segLength / segSplits) * s;
// get point
let pt = path.getPointAtLength(len);
pts.push(pt);
}
}
//remove temorary path
pathSeg.remove()
lastLength += segLength;
}
// is Z/closepath: add previous end point
else {
pts.push(p0);
}
}
//round coordinates
pts = Array.from(pts).map(pt => {
return {
x: +pt.x.toFixed(decimals),
y: y = +pt.y.toFixed(decimals)
}
});
return pts
}
function pathIsPolygon(d) {
// any beziers or arc commands?
let isPolygon = /[csqta]/gi.test(d) ? false : true
return isPolygon;
}
function getPathDataVertices(pathData) {
let polyPoints = [];
pathData.forEach(com => {
let values = com.values;
// get final on path point from last 2 values
if (values.length) {
let pt = {
x: values[values.length - 2],
y: values[values.length - 1]
}
polyPoints.push(pt)
}
})
return polyPoints
}
/**
* Standalone pathData parser
*/
function parsePathDataNormalized(d) {
d = d
.replace(/[\n\r\t|,]/g, " ")
.trim()
.replace(/(\d)-/g, "$1 -")
.replace(/(\.)(?=(\d+\.\d+)+)(\d+)/g, "$1$3 ");
let pathData = [];
let cmdRegEx = /([mlcqazvhst])([^mlcqazvhst]*)/gi;
let commands = d.match(cmdRegEx);
// valid command value lengths
let comLengths = {
m: 2,
a: 7,
c: 6,
h: 1,
l: 2,
q: 4,
s: 4,
t: 2,
v: 1,
z: 0
};
// offsets for absolute conversion
let offX, offY, lastX, lastY;
for (let c = 0; c < commands.length; c++) {
let com = commands[c];
let type = com.substring(0, 1);
let typeRel = type.toLowerCase();
let typeAbs = type.toUpperCase();
let isRel = type === typeRel;
let chunkSize = comLengths[typeRel];
// split values to array
let values = com.substring(1, com.length).trim().split(" ").filter(Boolean);
/**
* fix A - Arc commands
*/
if (typeRel === "a" && values.length != comLengths.a) {
let n = 0,
arcValues = [];
for (let i = 0; i < values.length; i++) {
let value = values[i];
// reset counter
if (n >= chunkSize) {
n = 0;
}
// if 3. or 4. parameter longer than 1
if ((n === 3 || n === 4) && value.length > 1) {
let largeArc = n === 3 ? value.substring(0, 1) : "";
let sweep = n === 3 ? value.substring(1, 2) : value.substring(0, 1);
let finalX = n === 3 ? value.substring(2) : value.substring(1);
let comN = [largeArc, sweep, finalX].filter(Boolean);
arcValues.push(comN);
n += comN.length;
} else {
// regular
arcValues.push(value);
n++;
}
}
values = arcValues.flat().filter(Boolean);
}
// string to number
values = values.map(Number);
let hasMultiple = values.length > chunkSize;
let chunk = hasMultiple ? values.slice(0, chunkSize) : values;
let comChunks = [{
type: type,
values: chunk
}];
if (hasMultiple) {
let typeImplicit = typeRel === "m" ? (isRel ? "l" : "L") : type;
for (let i = chunkSize; i < values.length; i += chunkSize) {
let chunk = values.slice(i, i + chunkSize);
comChunks.push({
type: typeImplicit,
values: chunk
});
}
}
/**
* convert to absolute
*/
if (c === 0) {
offX = values[0];
offY = values[1];
lastX = offX;
lastY = offY;
}
let typeFirst = comChunks[0].type;
typeAbs = typeFirst.toUpperCase();
isRel =
typeFirst.toLowerCase() === typeFirst && pathData.length ? true : false;
for (let i = 0; i < comChunks.length; i++) {
let com = comChunks[i];
let type = com.type;
let values = com.values;
let valuesL = values.length;
let comPrev = comChunks[i - 1] ?
comChunks[i - 1] :
c > 0 && pathData[pathData.length - 1] ?
pathData[pathData.length - 1] :
comChunks[i];
let valuesPrev = comPrev.values;
let valuesPrevL = valuesPrev.length;
isRel =
comChunks.length > 1 ?
type.toLowerCase() === type && pathData.length :
isRel;
if (isRel) {
com.type = comChunks.length > 1 ? type.toUpperCase() : typeAbs;
switch (typeRel) {
case "a":
com.values = [
values[0],
values[1],
values[2],
values[3],
values[4],
values[5] + offX,
values[6] + offY
];
break;
case "h":
case "v":
com.values = type === "h" ? [values[0] + offX] : [values[0] + offY];
break;
case "m":
case "l":
case "t":
com.values = [values[0] + offX, values[1] + offY];
break;
case "c":
com.values = [
values[0] + offX,
values[1] + offY,
values[2] + offX,
values[3] + offY,
values[4] + offX,
values[5] + offY
];
break;
case "q":
case "s":
com.values = [
values[0] + offX,
values[1] + offY,
values[2] + offX,
values[3] + offY
];
break;
}
}
// is absolute
else {
offX = 0;
offY = 0;
}
// convert shorthands
let shorthandTypes = ["H", "V", "S", "T"];
if (shorthandTypes.includes(typeAbs)) {
let cp1X, cp1Y, cpN1X, cpN1Y, cp2X, cp2Y;
if (com.type === "H" || com.type === "V") {
com.values =
com.type === "H" ? [com.values[0], lastY] : [lastX, com.values[0]];
com.type = "L";
} else if (com.type === "T" || com.type === "S") {
[cp1X, cp1Y] = [valuesPrev[0], valuesPrev[1]];
[cp2X, cp2Y] =
valuesPrevL > 2 ? [valuesPrev[2], valuesPrev[3]] : [valuesPrev[0], valuesPrev[1]];
// new control point
cpN1X = com.type === "T" ? lastX * 2 - cp1X : lastX * 2 - cp2X;
cpN1Y = com.type === "T" ? lastY * 2 - cp1Y : lastY * 2 - cp2Y;
com.values = [cpN1X, cpN1Y, com.values].flat();
com.type = com.type === "T" ? "Q" : "C";
}
}
// add to pathData array
pathData.push(com);
// update offsets
lastX =
valuesL > 1 ?
values[valuesL - 2] + offX :
typeRel === "h" ?
values[0] + offX :
lastX;
lastY =
valuesL > 1 ?
values[valuesL - 1] + offY :
typeRel === "v" ?
values[0] + offY :
lastY;
offX = lastX;
offY = lastY;
}
}
pathData[0].type = "M";
return pathData;
}
svg {
border: 1px solid #ccc;
overflow: visible;
padding: 1em
}
.grd {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
}
path {
stroke: #ccc;
stroke-width: 1.5%;
}
polygon {
marker-start: url(#markerStart);
marker-mid: url(#markerRound);
stroke-width: 0.5%;
}
<div class="grd">
<div class="col">
<h3>Path is already a polygon – skip more expensive calulations</h3>
<svg id="svg" viewBox="10 10 94 80">
<path id="path" fill="none" stroke="black" d="m104 33.4-6.9-16.6-16.6-6.8-16.6 6.8-6.9 16.6-6.9-16.6-16.6-6.8-16.6 6.8-6.9 16.6 9.4 23 17.5 18.3 20.1 15.3 20.1-15.3 17.5-18.3z" />
<polygon id="poly" points="" fill="none" stroke="red" />
</svg>
</div>
<div class="col">
<h3>Path has curves – retain on-path final points</h3>
<svg id="svg1" viewBox="0 0 100 85">
<path id="path1" fill="none" stroke="black" d="m50 85.2 33.4-27.8c9.9-9.9 16.6-17.9 16.6-32.4 0-13.7-11.2-24.9-25-24.9s-25 11.2-25 24.9c0-13.7-11.2-24.9-25-24.9s-25 11.2-25 24.9c0 16.6 8.7 24.5 16.6 32.4z" />
<polygon id="poly1" points="" fill="none" stroke="red" />
</svg>
</div>
</div>
<!-- markers -->
<svg id="svgMarkers" style="width:0; height:0; position:absolute; opacity:0">
<defs>
<marker id="markerStart" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="5" fill="green" />
</marker>
<marker id="markerRound" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="2.5" fill="red" />
</marker>
</defs>
</svg>
l lineto we omit any splittingSometimes you may need a close approximation so a highly detailed polygon mimicking a path's curvature (e.g for CSS clip-paths using polygon()).
When working on my custom pathLength library I came up with a helper that can auto-detect a suitable number of vertices based on a maximum length difference between path and polygon:
See codepen path to polygon converter
The vertice calculation is also based on parsing the path data so we can retain the original geometry of a path and skip additional point calculations for line segments
let d = path.getAttribute("d");
let options = {
decimals: 3,
adaptive: true,
retainPoly: true,
// polygon length can deviate 0.1 length units
tolerance: 0.1
};
let vertices = polygonFromPathData(d, options);
// show output
renderPolygon(poly, vertices);
polyPointsOut.value = "let points=" + JSON.stringify(vertices);
function renderPolygon(poly, pts) {
let polyAtt = pts
.map((pt) => {
return `${pt.x} ${pt.y}`;
})
.join(" ");
poly.setAttribute("points", polyAtt);
}
svg{
overflow:visible
}
svg path {
stroke-width: 2%;
stroke: #ccc;
}
svg polygon {
stroke-width: 0.75%;
marker-start: url(#markerStart);
marker-mid: url(#markerRound);
}
textarea {
width: 100%;
display: block;
min-height: 10em;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/getPointAtLengthLookup.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/getPointAtLengthLookup_getPolygon.js"></script>
<svg id="svg" viewBox="0 0 100 85">
<defs>
<marker id="markerStart" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="5" fill="green" />
< <marker id="markerRound" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="2.5" fill="red" />
</marker>
</defs>
<path id="path" fill="none" stroke="black" d=" m51 86.2 33.4-27.8c9.9-9.9 16.6-17.9 16.6-32.4 0-13.7-11.2-24.9-25-24.9s-25 11.2-25 24.9c0-13.7-11.2-24.9-25-24.9s-25 11.2-25 24.9c0 16.6 8.7 24.5 16.6 32.4z" />
<polygon id="poly" points="" fill="none" stroke="red" />
</svg>
<h3>Polygon vertices</h3>
<textarea id="polyPointsOut">
</textarea>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With