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