Say I have a pentagon (and we number the sides, like moving around a clock):
Starting from the center of the polygon, how do you compute the position of a point in these spots (along the line of the edge of the polygon):
- At the vertex between sides 2 and 3 (this is the max distance from the center).
- At the midpoint of 4 (this is the min distance from the center).
- At a point 2/3 across side 3, moving clockwise (randomly chosen distance from the center).
Knowing how to compute the x/y coordinates relative to the center will mean I can plot points along the straight line-segments of an arbitrary polygon (ranging from say 3 to 20 sides). I am having a really time conceptualizing how to do this, let alone getting it to work in code. Doesn’t matter what language it is in, but preferably JavaScript/TypeScript, or else Python or C (or self-explanatory pseudocode).
Here is a combination of what I conjured up. The polygon layout works properly, but the point positioning isn’t working. How do you layout these 3 points?
const ANGLE = -Math.PI / 2 // Start the first vertex at the top center
function computePolygonPoints({
width,
height,
sides,
strokeWidth = 0,
rotation = 0,
}) {
const centerX = width / 2 + strokeWidth / 2
const centerY = height / 2 + strokeWidth / 2
const radiusX = width / 2 - strokeWidth / 2
const radiusY = height / 2 - strokeWidth / 2
const offsetX = strokeWidth / 2
const offsetY = strokeWidth / 2
const rotationRad = (rotation * Math.PI) / 180
const points = Array.from({ length: sides }, (_, i) => {
const angle = (i * 2 * Math.PI) / sides + ANGLE
const x = centerX + radiusX * Math.cos(angle)
const y = centerY + radiusY * Math.sin(angle)
// Apply rotation around the center
const rotatedX =
centerX +
(x - centerX) * Math.cos(rotationRad) -
(y - centerY) * Math.sin(rotationRad)
const rotatedY =
centerY +
(x - centerX) * Math.sin(rotationRad) +
(y - centerY) * Math.cos(rotationRad)
return { x: rotatedX, y: rotatedY }
})
const minX = Math.min(...points.map(p => p.x))
const minY = Math.min(...points.map(p => p.y))
const adjustedPoints = points.map(p => ({
x: offsetX + p.x - minX,
y: offsetY + p.y - minY,
}))
return adjustedPoints
}
function vertexCoordinates(n, R, vertexIndex) {
const angle = 2 * Math.PI * vertexIndex / n - Math.PI / 2; // Adjusting to start from the top
return {
x: R * Math.cos(angle),
y: R * Math.sin(angle),
}
}
function midpointCoordinates(x1, y1, x2) {
return {
x: (x1 + x2.x) / 2,
y: (y1 + x2.y) / 2,
}
}
function fractionalPoint(x1, y1, x2, fraction) {
return {
x: x1 + fraction * (x2.x - x1),
y: y1 + fraction * (x2.y - y1),
}
}
const pentagonPoints = computePolygonPoints({ width: 300, height: 300, sides: 5 })
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", 300)
svg.setAttribute("height", 300);
const pentagon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
pentagon.setAttribute('fill', 'cyan')
pentagon.setAttribute('points', pentagonPoints
.map((p) => `${p.x},${p.y}`)
.join(" "))
svg.appendChild(pentagon)
document.body.appendChild(svg);
const n = 5 // Number of sides for a pentagon
const width = 300; // Width of the pentagon
const R = width / (2 * Math.cos(Math.PI / n)); // Radius of the circumscribed circle
const centerX = 150; // Center of the canvas
const centerY = 150;
// Vertex between sides 2 and 3
const vertex23 = vertexCoordinates(n, R, 2)
const vertex23Adjusted = {
x: centerX + vertex23.x, // subtract radius too?
y: centerY + vertex23.y
};
console.log('Vertex between sides 2 and 3:', vertex23Adjusted)
const circle23 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle23.setAttribute('fill', 'magenta')
circle23.setAttribute('r', 16)
circle23.setAttribute('cx', vertex23Adjusted.x)
circle23.setAttribute('cy', vertex23Adjusted.y)
svg.appendChild(circle23)
// Midpoint of side 4
const vertex4_1 = vertexCoordinates(n, R, 3)
const vertex4_2 = vertexCoordinates(n, R, 4)
const mid4 = midpointCoordinates(vertex4_1.x, vertex4_1.y, vertex4_2)
console.log('Midpoint of side 4:', mid4)
const mid4Circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
mid4Circle.setAttribute('fill', 'magenta')
mid4Circle.setAttribute('r', 16)
mid4Circle.setAttribute('cx', mid4.x)
mid4Circle.setAttribute('cy', mid4.y)
svg.appendChild(mid4Circle)
// Point 2/3 across side 3, moving clockwise
const vertex3_1 = vertexCoordinates(n, R, 2)
const vertex3_2 = vertexCoordinates(n, R, 3)
const frac3 = fractionalPoint(
vertex3_1.x,
vertex3_1.y,
vertex3_2,
2 / 3,
)
console.log('Point 2/3 across side 3:', frac3)
const frac3Circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
frac3Circle.setAttribute('fill', 'magenta')
frac3Circle.setAttribute('r', 16)
frac3Circle.setAttribute('cx', frac3.x)
frac3Circle.setAttribute('cy', frac3.y)
svg.appendChild(frac3Circle)
I’d like to be able to solve this for any polygon from 3 to 20 sides, not just for the pentagon.
2
Answers
Here is the answer, I finally figured it out.
Usage:
Here are 4 points laid out just outside the outer edge.
The problem with your trigonometrical computation is that the circumscribed circle is too big. You can see it if you add
to your diagram:
But you need not do the computation yourself, you can let SVG do it for you with the
path.getPointAtLength(length)
function. In your cases,length=2
length=3.5
length=2.67
.