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.
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>