block by mgold 295a5c88f82d80643bcf

Triangle Centers

Full Screen

Different ways to find the “center” of a triangle. Inspired by Numberphile. Grab and move the vertices of the large triangle.

The orthocenter is twice as far from the centroid as the circumcenter, and all three lie on a single line known as the Euler line. Of these three, only the centroid always lies inside the triangle. The incenter does not usually lie on this line, except for isoceles triangles. For equilateral triangles, all four centers are the same point.

Triangle centers demonstrate that mathematics isn’t always about a single right answer, but rather, many good ideas can coexist. It also demonstrates how different geometric notions can be brought to bear on a problem and be distinct, rather than be different representations of the same thing.

Built with blockbuilder.org

index.html

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