|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<style> |
|
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } |
|
svg { width:100%; height: 100% } |
|
text { text-anchor: middle; font-family: avenir, sans-serif} |
|
path.triangle { fill: none; stroke: black; stroke-width: 2px; shape-rendering: geometricPrecision;} |
|
circle.grabme { cursor: pointer} |
|
|
|
line.centroid { stroke: #FF4136 } |
|
circle.centroid, text.centroid { fill: #FF4136 } |
|
|
|
line.circumcenter { stroke: #2ECC40 } |
|
circle.circumcenter, text.circumcenter { fill: #2ECC40 } |
|
|
|
line.orthocenter { stroke: #0074D9 } |
|
circle.orthocenter, text.orthocenter { fill: #0074D9 } |
|
|
|
line.incenter { stroke: #FFDC00 } |
|
circle.incenter, text.incenter { fill: #FFDC00 } |
|
|
|
line.euler { stroke: #AAAAAA; stroke-width: 4px} |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<script> |
|
var width = 960; |
|
var height = 500; |
|
var centerNames = ["Centroid", "Circumcenter", "Orthocenter", "Incenter"]; |
|
var centerWidth = width/centerNames.length; |
|
var centerHeight = height/3; |
|
|
|
// The only piece of mutable state |
|
var points = [{x:-3.5, y:1}, {x:7, y:4}, {x:0.84,y:-4}]; |
|
|
|
var lineGen = d3.svg.line(); |
|
function line(scale){ |
|
return lineGen(points.map(function(p){return [p.x*scale, p.y*scale]}))+"Z"; |
|
} |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr({width: width, height: height}) |
|
var panels = svg.selectAll("nonexistant") |
|
.data(centerNames) |
|
.enter() |
|
.append("g") |
|
.attr("transform", function(d,i){ |
|
return "translate("+(i+0.5)*centerWidth+","+centerHeight/2+")" |
|
}) |
|
|
|
panels.append("text") |
|
.text(function(d){return d}) |
|
.attr("dy", centerHeight/2 + "px") |
|
.attr("class", function(d){ return d.toLowerCase()}) |
|
|
|
panels.append("path") |
|
.attr("class", "triangle") |
|
.datum(15) |
|
|
|
panels.each(function(d,i){ |
|
var center = d3.select(this); |
|
center.selectAll("line") |
|
.data(d3.range(3)) |
|
.enter() |
|
.append("line") |
|
.attr("class", d.toLowerCase()) |
|
}) |
|
|
|
|
|
var main = svg.append("g") |
|
.attr("transform", "translate("+width/2+","+2*centerHeight+")") |
|
|
|
main.append("line") |
|
.attr("class", "euler") |
|
|
|
main.append("path") |
|
.attr("class", "triangle") |
|
.datum(25) |
|
|
|
main.selectAll("circle.center") |
|
.data(centerNames) |
|
.enter() |
|
.append("circle") |
|
.attr("class", function(d){ return "center " + d.toLowerCase()}) |
|
.attr("r", 4) |
|
|
|
var drag = d3.behavior.drag() |
|
.origin(function(d){return {x:points[d].x*25, y:points[d].y*25}}) |
|
.on("drag", function(d){ |
|
points[d] = {x:d3.event.x/25, y:d3.event.y/25} |
|
update(); |
|
}) |
|
|
|
var handles = main.selectAll("nonexistant") |
|
.data(d3.range(3)) |
|
.enter() |
|
.append("circle") |
|
.attr("class", "grabme") |
|
.attr("r", 4) |
|
.call(drag) |
|
|
|
function midpoint(p1, p2){ |
|
var dx = p1.x + p2.x; |
|
var dy = p1.y + p2.y; |
|
return {x: dx/2, y: dy/2} |
|
} |
|
|
|
function slope(p1,p2){ |
|
return (p2.y - p1.y) / (p2.x - p1.x); |
|
} |
|
|
|
function perpendicular(m){ |
|
return -1/m; |
|
} |
|
|
|
function intersect(p1, m1, p2, m2){ |
|
var j = (m1*(p1.x - p2.x) - p1.y + p2.y) / (m1 - m2); |
|
return {x: p2.x + j, y: p2.y + j*m2}; |
|
} |
|
|
|
function distance2(p1, p2){ |
|
var dx = p1.x - p2.x; |
|
var dy = p1.y - p2.y; |
|
return dx*dx + dy*dy; |
|
} |
|
|
|
function angle (p1, p2, p3){ |
|
var angle1 = Math.atan2(p1.y - p2.y, p1.x - p2.x) |
|
var angle3 = Math.atan2(p3.y - p2.y, p3.x - p2.x) |
|
var delta = angle1 - angle3; |
|
if (delta < 0){ |
|
delta += 2*Math.PI |
|
} |
|
return delta || 0; |
|
} |
|
|
|
function makeLine(line, p1, p2){ |
|
line |
|
.attr("x1", p1.x*15) |
|
.attr("y1", p1.y*15) |
|
.attr("x2", p2.x*15) |
|
.attr("y2", p2.y*15) |
|
} |
|
|
|
var invalidPoint = {x: 9999, y: 9999} |
|
|
|
function update(){ |
|
var centers = []; |
|
|
|
d3.selectAll(".triangle") |
|
.attr("d", line); |
|
|
|
d3.selectAll("line.centroid") |
|
.each(function(d){ |
|
var p1 = points[d]; |
|
var mp1 = midpoint(points[(d+1)%3], points[(d+2)%3]) |
|
d3.select(this).call(makeLine, p1, mp1) |
|
|
|
if (d == 0){ |
|
var m1 = slope(p1, mp1); |
|
var p2 = points[1]; |
|
var m2 = slope(p2, midpoint(p1, points[2])); |
|
centers.push(intersect(p1, m1, p2, m2)) |
|
} |
|
}) |
|
|
|
d3.selectAll("line.circumcenter") |
|
.each(function(d){ |
|
var p1 = points[d]; |
|
var mp1 = midpoint(p1, points[(d+1)%3]); |
|
if (d == 0){ |
|
var p2 = points[1]; |
|
var p3 = points[2]; |
|
var m1 = perpendicular(slope(p1, p2)); |
|
var mp2 = midpoint(p2, p3); |
|
var m2 = perpendicular(slope(p2, p3)); |
|
centers.push(intersect(mp1, m1, mp2, m2)) |
|
} |
|
d3.select(this).call(makeLine, mp1, centers[1]) |
|
}) |
|
|
|
d3.selectAll("line.orthocenter") |
|
.each(function(d){ |
|
var p1 = points[d]; |
|
var p2 = points[(d+1)%3] |
|
var m2 = slope(p2, points[(d+2)%3]); |
|
var m1 = perpendicular(m2); |
|
var base = intersect(p1, m1, p2, m2); |
|
|
|
if (d == 0){ |
|
var p3 = points[2] |
|
var m4 = slope(p3, p1); |
|
var m3 = perpendicular(m4); |
|
var otherBase = intersect(p2, m3, p3, m4) |
|
centers.push(intersect(p1, slope(p1, base), |
|
p2, slope(p2, otherBase))); |
|
} |
|
if (distance2(p1, base) > distance2(p1, centers[2])) |
|
d3.select(this).call(makeLine, p1, base) |
|
else |
|
d3.select(this).call(makeLine, p1, centers[2]) |
|
}) |
|
|
|
var stashP, stashM; |
|
d3.selectAll("line.incenter") |
|
.each(function(d){ |
|
var p0 = points[(d+2)%3]; |
|
var p1 = points[d]; |
|
var p2 = points[(d+1)%3]; |
|
var d_theta = angle(p0, p1, p2) / 2; |
|
var baseTheta = Math.atan2(p0.y - p1.y, p0.x - p1.x); |
|
var theta = baseTheta - d_theta; |
|
var m = Math.tan(theta); |
|
var farPoint = intersect(p1, m, p0, slope(p0, p2)); |
|
if (d==0){ |
|
stashP = p1; |
|
stashM = m; |
|
}else if (d==1){ |
|
var ic = intersect(stashP, stashM, p1, m); |
|
if (isNaN(ic.x) || isNaN(ic.y)) ic = invalidPoint; |
|
centers.push(ic) |
|
} |
|
d3.select(this).call(makeLine, p1, farPoint); |
|
}) |
|
|
|
main.selectAll("circle.center") |
|
.attr("cx", function(d,i){ return centers[i].x*25}) |
|
.attr("cy", function(d,i){ return centers[i].y*25}) |
|
|
|
handles |
|
.attr("cx", function(d,i){return points[i].x*25}) |
|
.attr("cy", function(d,i){return points[i].y*25}) |
|
|
|
main.select("line.euler") |
|
.attr("x1", centers[1].x*25) |
|
.attr("y1", centers[1].y*25) |
|
.attr("x2", centers[2].x*25) |
|
.attr("y2", centers[2].y*25) |
|
} |
|
update(); |
|
|
|
|
|
</script> |
|
</body> |