端末エミュレータもそっちのけで。3 つのボックス A、B、C を互いに線でつなぐ!?

canvas の弄り方が、まだ理解できていないようです。もう今日は力尽きました。
書けば書くほどに、「それってオブジェクトだよな」と考え出すと長くなり、現在収拾がつかない状態です。眠い。リポDも切れる。
三角形の辺(ちょっと改良)をクリックすると色が赤にかわり、ドラッグして形を変える。
力量の無さに orz

<!DOCTYPE html>
<title>Test</title>
  <style>
#HOGE {
  background  : #eef;
}
  </style>
<body>
<h1>PointModeler by Canvas</h1>
<canvas id="HOGE" width="800" height="400">
  canvas による描画
</canvas>
<p>
  x = <input type="text" id="px" value="0"> /
  y = <input type="text" id="py" value="0"> /
  b = <input type="text" id="bt" value="0"> /
  d = <input type="text" id="dt" value="0">
</p>

<script>
/**
 * オブジェクトのクローンを作る
 */
(function () {
  function Qbject (arg) {
    if (arg)
      for (var p in arg)
        if (arg.hasOwnProperty (p))
          this[p] = arg[p];
  }

  Qbject.create = function (arg, args) {
    var F = new Function;
    F.prototype = arg;
    var o = new F;
    if (args) Qbject.call (o, args);
    return o;
  };
  
  this.Qbject = Qbject;
})();


//________________________________
/**
 * マウスの位置を監視する
 */
(function () {
  function MouseMonitor () {
    this.x = null;
    this.y = null;
    this.currentNode = null;
    this.button = false;
  }
 
  function create (view) {
    if (1 > arguments.length)
      view = window;
    
    var doc = view.document;
    var pointer = new MouseMonitor;
    
    function onMouseEvents (event) {
      switch (event.type) {

      case 'mousemove' :
        /*@cc_on @if (1)
          var n = event.srcElement;
          var c = ('CSS1Compat' === doc.compatMode) ? doc.documentElement : doc.body;
          pointer.x = event.clientX + c.scrollLeft;
          pointer.y = event.clientY + c.scrollTop;
          pointer.currentNode = n;
        @else@*/
          pointer.x = event.clientX + view.pageXOffset;
          pointer.y = event.clientY + view.pageYOffset;
          pointer.currentNode = event.target;
        /*@end@*/
        break;
      
      case 'mousedown' :
        pointer.shiftKey = event.shiftKey;
        pointer.ctrlKey = event.ctrlKey;
        pointer.altKey = event.altKey;
        pointer.button = true;
        pointer.currentNode = event.target;
        break;
        
      case 'mouseup' :
        pointer.shiftKey = event.shiftKey;
        pointer.ctrlKey = event.ctrlKey;
        pointer.altKey = event.altKey;
        pointer.button = false;
        pointer.currentNode = event.target;
        break;
      
      case 'unload' :
        doc.removeEventListener('mousemove', arguments.callee, false);
        doc.removeEventListener('mousedown', arguments.callee, false);
        doc.removeEventListener('mouseup', arguments.callee, false);
        view.removeEventListener('unload', arguments.callee, false);
      
        view = doc = pointer = onMouseEvents = null;
      }
    }
    
    doc.addEventListener ('mousemove', onMouseEvents, false);
    doc.addEventListener ('mousedown', onMouseEvents, false);
    doc.addEventListener ('mouseup', onMouseEvents, false);
    view.addEventListener ('unload', onMouseEvents, false);

    return pointer;
  }
  
  MouseMonitor.create = create;
  
  //this.MouseMonitor = MouseMonitor;
  this.MouseMonitor = MouseMonitor.create (this); // view は window のみということで
})();


//________________________________
/**
 * 与えられたHTML要素の位置を基準に、オフセット位置を返す
 */
(function () {
  function Converter (node, offset, currentFlag) {
    this.node = node;
    this.currentFlag = !!currentFlag;
    this.offset = offset;
  }
  
  
  function getPosition (base) {
    if (1 > arguments.length)
      throw new Error ('引数が無い');
    
    if (this.currentFlag)
      refresh.call (this);
    
    return { 'x': base.x -this.offset.x, 'y': base.y - this.offset.y };
  }
  
  
  function refresh () {
    this.offset = getElementPosition (this.node);
  }
  
  
  function getElementPosition (node) {
    var x = 0;
    var y = 0;

    while (node) { 
      x += node.offsetLeft;
      y += node.offsetTop;
      node = node.offsetParent;
    }
    return { 'x': x, 'y': y };
  };


  function OffsetConverter () { ;}
  
  function create (target, currentFlag) {
    if (1 > arguments.length)
      throw new Error ('ノードがない');
    
    var position = getElementPosition (target);
    var obj = new Converter (target, position, currentFlag);
    var func = new Function;
    
    func.getPosition =
      (function () { return getPosition.apply (obj, arguments); });
    
    func.refresh =
      (function () { refresh.apply (obj, arguments); });
    
    return func;
  }
  
  
  OffsetConverter.create = create;
  
  this.OffsetConverter = OffsetConverter;
})();


//________________________________
/**
 * 多角形
 */
(function () {
  var DEFAULT_POINTS = [{x:50, y:50}, {x:150, y:50}, {x:100, y:136.3}];
  var DEFAULT_COLOR  = { 'strok': 'rgb(0, 0, 0)', 'fill': null };
  var SELECTED_COLOR = { 'strok': 'rgb(255, 0, 0)', 'fill': 'rgb(255, 0, 0)' };
  var SELECTED_POINT_RADIUS = 5;


  function Polygon (name, points, color, selected) {
    this.name = name;
    this.points = points;
    this.color = color;
    this.selected = selected;
  }


  function draw (ctrx) {
    var fill;
    if (this.selected) {
      this.points.forEach (function (p) {
        this.beginPath ();
        this.fillStyle = SELECTED_COLOR.fill;
        this.arc (p.x, p.y, SELECTED_POINT_RADIUS, 0, Math.PI*2, false);
        this.fill ();
      }, ctrx);
      
      ctrx.strokeStyle = SELECTED_COLOR.strok;
      ctrx.beginPath ();
      this.points.forEach (function (p) { this.lineTo (p.x, p.y); }, ctrx);
      ctrx.closePath ();
      ctrx.stroke ();
    }

    else {
      ctrx.strokeStyle = this.color.strok;
      if ((fill = this.color.fill))
        ctrx.fillStyle = fill;

      ctrx.beginPath ();
      this.points.forEach (function (p) { this.lineTo (p.x, p.y); }, ctrx);
      ctrx.closePath ();
      ctrx.stroke ();

      if (fill)
        this.ctrx.fill ();
      
    }
  }
  
  
  function isSelected (point, range) {
    var i = 0, I = this.points.length;
    var p0 = this.points[I-1];
    var p1;
    var r;
    
    range = (range) ? range: 500;
    
    for (; i < I ; i++) {
      p1 = this.points[i];
      r = Math.round (((p1.x - p0.x) * (point.y - p0.y)) / range - ((point.x - p0.x) * (p1.y - p0.y)) / range);
      if (0 === r)
        return true;
      p0 = p1;
    }
    return false;

  }
  
  function hasInRange (point, range) {
    if (2 > arguments.length)
      range = { 'x': SELECTED_POINT_RADIUS, 'y': SELECTED_POINT_RADIUS };
    
    var L = point.x - range.x;
    var R = point.x + range.x;
    var T = point.y - range.y;
    var B = point.y + range.y;
    var x, y;

    for (var i = 0, p; p = this.points[i]; i++)
      if (L < (x = p.x) && (x < R))
        if (T < (y = p.y) && (y < B))
          return { 'index': i };
    
    return null;
  }


  function create (name, points, color, selected) {
    if ('undefined' === typeof points)
      points = DEFAULT_POINTS.splice (0);
    
    if ('undefined' === typeof color)
      color = { 'strok': DEFAULT_COLOR.strok, 'fill': DEFAULT_COLOR.fill };
    
    var polygon = new Polygon (name, points, color, !!selected);
    
    polygon.draw =
      (function () { draw.apply (polygon, arguments); });
    
    polygon.hasInRange =
      (function () { return hasInRange.apply (polygon, arguments); });
    
    polygon.isSelected =
      (function () { return isSelected.apply (polygon, arguments); });
    
    return polygon;
  }
  
  Polygon.create = create;
  this.Polygon = Polygon;
})();


//________________________________
/**
 * 与えられたHTML要素の位置を基準に、オフセット位置を返す
 *
 */
(function () {

  function PointModeler (canvas) {
    this.canvas = canvas;
    this.ctrx = canvas.getContext ('2d');
    this.stock = [];
    this.current = null;
    this.mode = null;
  }
  
  // 登録されているモデルを全て描画する
  function allDraw (override) {
    if (! override) {
      clearScreen.call (this);
    }
    this.stock.forEach (function (md) { md.draw (this); }, this.ctrx);
  }
  
  // キャンバス領域を削除する
  function clearScreen (draw) {
    this.ctrx.clearRect (0, 0, this.canvas.width, this.canvas.height);
    if (draw)
      allDraw.call (this);
  }
  
  // モデルを追加する
  function add (name, points, color, selected) {
    var polygon = Polygon.create (name, points, color, selected);
    this.stock.push (polygon);
    polygon.draw (this.ctrx);
  }
  
  
  function click (position) {
    var r;
    for (var i = 0, md; md = this.stock[i]; i++) {
      if (md.isSelected (position)) {
        md.selected = ! md.selected;
        clearScreen.call (this, true);
        this.current = md;
        return;
      }
    }
    if (this.current) {
      this.current.selected = false;
      this.ctrx.clearRect (0, 0, this.canvas.width, this.canvas.height);
      this.current.draw (this.ctrx);
      this.current = null;
    }
     
  }
  
  
  function up (position) {
    if (this.mode && this.mode.state == 'move') {
      this.current.points[this.mode.index] = position;
      this.ctrx.clearRect (0, 0, this.canvas.width, this.canvas.height);
      this.current.draw (this.ctrx);
    }
      this.mode = null;
  }
  
  
  function move (position) {
    if (this.mode && this.mode.state == 'move') {
      this.current.points[this.mode.index] = position;
      this.ctrx.clearRect (0, 0, this.canvas.width, this.canvas.height);
      this.current.draw (this.ctrx);
    }
  }
  
  function down (position) {
    var r;
    if (! this.mode) {
      for (var i = 0, md; md = this.stock[i]; i++) {
        if (md.selected && (r = md.hasInRange (position))) {
          if (md == this.current) {
            if (md.selected) {
              this.mode = { 'state': 'move', 'index': r.index };
              return;
            }
          }
        }
      }
    }
  }
  
  
  function PointModeler_create (canvas) {
    var pointModeler;
    var func = new Function;

    if (canvas && canvas.getContext)
      pointModeler = new PointModeler (canvas);
    else
      throw new Erorr ('利用できません');
    


    func.add =
      (function () { add.apply (pointModeler, arguments); });

    func.click =
      (function () { click.apply (pointModeler, arguments); });

    func.down =
      (function () { down.apply (pointModeler, arguments); });

    func.up =
      (function () { up.apply (pointModeler, arguments); });

    func.move =
      (function () { move.apply (pointModeler, arguments); });
    
    func.clearScreen =
      (function () { clearScreen.apply (pointModeler, arguments); });
    
    func.allDraw =
      (function () { allDraw.apply (pointModeler, arguments); });
    
    return func;
  }
  
  PointModeler.create = PointModeler_create;

  this.PointModeler = PointModeler;
})();   


//________________________________
(function () {
  var target = document.getElementById ('HOGE');
  var offset = OffsetConverter.create (target);
  var mouse  = MouseMonitor;
  var modeler= PointModeler.create (target);
  var count  = 0;
  var mx, my;
  var x, y;
  
  modeler.add ();

  setInterval (function () {
    var position = offset.getPosition (mouse);
    document.getElementById ('px').value = x = position.x;
    document.getElementById ('py').value = y = position.y;
    document.getElementById ('bt').value = count;
    
    if (mouse.button) {
      if (count++)
        modeler.down (position);

      if ((x !== mx) || (y !== my)) {
        mx = x; my = y;
        modeler.move (position);
      }
    }
    else {
      if (0 < count) {
        modeler.up (position);
        modeler.click (position);
        count = 0;
      }
    }
     
  }, 20);
})();

      


</script>