Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Mean Center Vs. Centroid
license: gpl-3.0

Draw a polygon to see the location of its centroid and its mean center. The centroid is the center of mass, while the mean center is the average of its vertices. For more on the difference, see this thread.

Functions for calculating the centroid and mean center are from Geometric.js.

<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
}
line {
stroke: #000;
stroke-dasharray: 5, 5;
}
polygon {
fill: none;
stroke: #000;
}
text {
font-family: "Helvetica", sans-serif;
pointer-events: none;
text-anchor: middle;
user-select: none;
}
text.small {
font-size: .7em;
}
.centroid {
fill: steelblue;
}
.centroid-line {
display: none;
stroke: steelblue;
}
.centroid-line.show {
display: block;
}
.mean {
fill: tomato;
}
.mean-line {
display: none;
stroke: tomato;
}
.mean-line.show {
display: block;
}
#stats {
background: rgba(255, 255, 255, .8);
font-family: monospace;
left: 10px;
pointer-events: none;
position: absolute;
}
.virtual {
opacity: .5;
}
</style>
</head>
<body>
<div id="stats">
<div>Area of polygon: <span class="area"></span></div>
<div>Share of screen: <span class="pct"></span></div>
</div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/geometric@2"></script>
<script>
const width = innerWidth;
const height = innerHeight;
const svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
const intro = svg.append("text")
.attr("transform", `translate(${[width / 2, height / 2]})`)
.text("Click anywhere to begin.");
const polygon = svg.append("polygon");
const connectLine = svg.append("line");
const endLine = svg.append("line");
const meanG = svg.append("g")
.attr("transform", "translate(-100, -100)")
.attr("class", "mean");
meanG.append("circle")
.attr("r", 5);
meanG.append("text")
.attr("class", "small")
.attr("dy", 15)
.text("Mean");
const centroidG = svg.append("g")
.attr("transform", "translate(-100, -100)")
.attr("class", "centroid");
centroidG.append("circle")
.attr("r", 5);
centroidG.append("text")
.attr("class", "small")
.attr("dy", -8)
.text("Centroid");
const meanVirtualLine = svg.append("line")
.attr("class", "mean-line virtual");
const meanVirtual = svg.append("circle")
.attr("class", "mean virtual").attr("r", 5)
.attr("transform", "translate(-100, -100)");
const centroidVirtualLine = svg.append("line")
.attr("class", "centroid-line virtual");
const centroidVirtual = svg.append("circle")
.attr("class", "centroid virtual")
.attr("r", 5)
.attr("transform", "translate(-100, -100)");
const points = [];
let firstClick = true;
svg
.on("click", clicked)
.on("mousemove", moved);
function clicked({pageX, pageY}){
const p = [pageX, pageY];
svg.append("circle")
.attr("r", 3)
.attr("transform", `translate(${p})`);
points.push(p);
polygon.attr("points", points);
const c = geometric.polygonCentroid(points);
if (c[0]) centroidG.attr("transform", `translate(${c})`);
meanG.attr("transform", `translate(${geometric.polygonMean(points)})`);
if (firstClick){
intro.transition().style("opacity", 1e-6).remove();
firstClick = 0;
}
const a = geometric.polygonArea(points);
d3.select(".area").text(`${d3.format(",")(Math.round(a))}px`);
d3.select(".pct").text(`${(a / (width * height) * 100).toFixed(2)}%`);
meanVirtualLine.classed("show", 0);
centroidVirtualLine.classed("show", 0);
}
function moved({pageX, pageY}){
if (points.length){
const lastPoint = points[points.length - 1];
connectLine
.attr("x1", lastPoint[0])
.attr("y1", lastPoint[1])
.attr("x2", pageX)
.attr("y2", pageY);
const firstPoint = points[0];
endLine
.attr("x1", firstPoint[0])
.attr("y1", firstPoint[1])
.attr("x2", pageX)
.attr("y2", pageY);
const virtualPoints = points.slice();
virtualPoints.push([pageX, pageY]);
const currMean = geometric.polygonMean(points);
const virtualMean = geometric.polygonMean(virtualPoints);
meanVirtual.attr("transform", `translate(${virtualMean})`);
meanVirtualLine
.classed("show", 1)
.attr("x1", currMean[0])
.attr("y1", currMean[1])
.attr("x2", virtualMean[0])
.attr("y2", virtualMean[1]);
const currCentroid = geometric.polygonCentroid(points);
const virtualCentroid = geometric.polygonCentroid(virtualPoints);
if (currCentroid[0]){
centroidVirtual.attr("transform", `translate(${virtualCentroid})`);
centroidVirtualLine
.classed("show", 1)
.attr("x1", currCentroid[0])
.attr("y1", currCentroid[1])
.attr("x2", virtualCentroid[0])
.attr("y2", virtualCentroid[1]);
}
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment