block by armollica 5a9541beb07565a37d32

Shape Objects

Full Screen

Creating simple shape objects.

This can simplify drawing to canvas:

ellipse.context(canvas).stroke();

It also lets you encapsulates geometry-related helper functions. For example, the parametric function of an ellipse mapping to Cartesian coordinates:

var point = ellipse.cartesian(theta); // point = {x, y}

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Shape Objects</title>
  </head>
  <body>
    <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script src="shapes.js"></script>
    <script>
      var width = 960,
          height = 500,
          t0 = Date.now();

      var canvas = d3.select("body").append("canvas")
        .attr("width", width)
        .attr("height", height);

      var context = canvas.node().getContext("2d");

      var ellipse = shapes.ellipse().context(canvas.node());

      var circles = d3.range(0, 2*Math.PI, 2*Math.PI/25)
        .map(function(t0) {
          return {
            t0: t0,
            shape: shapes.circle().context(canvas.node())
          };
        });

      d3.timer(function() {
        var dt = Date.now() - t0,
            tick = dt/3000;

        context.clearRect(0, 0, width, height);

        ellipse
          .update(width/2, height/2, 400*Math.cos(tick), 200*Math.sin(tick))
          .stroke();

        circles.forEach(function(circle) {
          var center = ellipse.cartesian(tick + circle.t0),
              radius = 5*Math.sin(tick + circle.t0) + 10;
          circle.shape
            .update(center.x, center.y, radius)
            .fill();
        });
      });
    </script>
  </body>
</html>

shapes.js

var shapes = {};

(function() {
  function Ellipse(cx, cy, rx, ry) {
    this.cx = cx;
    this.cy = cy;
    this.rx = rx;
    this.ry = ry;
    this._context = null;
  }

  Ellipse.prototype.update = function(cx, cy, rx, ry) {
    this.cx = cx;
    this.cy = cy;
    this.rx = rx;
    this.ry = ry;

    return this;
  };

  Ellipse.prototype.context = function(canvas) {
    if (!arguments.length) return this._context;
    this._context = canvas.getContext("2d");
    return this;
  };

  Ellipse.prototype.cartesian = function(theta) {
    var x = this.rx * Math.cos(theta) + this.cx,
        y = this.ry * Math.sin(theta) + this.cy;
    return {x, y}
  };

  Ellipse.prototype.stroke = function() {
    var context = this.context(),
        theta = 0,
        dt = Math.PI/24,
        point = this.cartesian(theta);

    context.beginPath();
    context.moveTo(point.x, point.y);
    for (theta = dt; theta <= 2*Math.PI; theta += dt) {
      point = this.cartesian(theta);
      context.lineTo(point.x, point.y);
    }
    context.stroke();
    context.closePath();

    return this;
  };

  Ellipse.prototype.fill = function() {
    var context = this.context(),
        theta = 0,
        dt = Math.PI/24,
        point = this.cartesian(theta);

    context.beginPath();
    context.moveTo(point.x, point.y);
    for (theta = dt; theta <= 2*Math.PI; theta += dt) {
      point = this.cartesian(theta);
      context.lineTo(point.x, point.y);
    }
    context.fill();
    context.closePath();

    return this;
  };

  function Circle(cx, cy, r) {
    Ellipse.call(this, cx, cy, r, r);
  }

  Circle.prototype = Object.create(Ellipse.prototype);

  Circle.prototype.update = function(cx, cy, r) {
    this.cx = cx;
    this.cy = cy;
    this.rx = r;
    this.ry = r;

    return this;
  };

  shapes.ellipse = function(cx, cy, rx, ry) {
    var ellipse = new Ellipse(cx, cy, rx, ry);
    return ellipse;
  };

  shapes.circle = function(cx, cy, r) {
    var circle = new Circle(cx, cy, r);
    return circle;
  };
})();