block by mbostock 1021103

Superformula Explorer

Full Screen

Christophe Viau implemented a new shape type as a D3 plugin based on superformulas.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>Superformula</title>
<style>

path {
  stroke-width: 1.5px;
  stroke: #666;
  fill: #ddd;
}

#controls {
  position: absolute;
  width: 240px;
  font: 10px sans-serif;
}

#controls span,
#controls label {
  position: relative;
  top: -5px;
  padding: 5px;
  display: inline-block;
  width: 20px;
}

#controls button {
  font: 10px sans-serif;
  padding: 5px;
  width: 70px;
}

</style>
<div id="controls"></div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="superformula.js"></script>
<script>

var types = {
  asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
  bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1},
  butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1},
  circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1},
  clover: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
  cloverFour: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1},
  cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1},
  diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1},
  drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1},
  ellipse: {m: 4, n1: 2, n2: 2, n3: 2, a: 9, b: 6},
  gear: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1},
  heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18},
  heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
  hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
  malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1},
  pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1},
  rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1},
  roundedStar: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1},
  square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1},
  star: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1},
  triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1}
};

var format = d3.format(".4n");

var scale = d3.scale.linear()
    .domain([-10, 20, 1000])
    .range([0, 800, 1000]);

var svg = d3.select("body")
  .append("svg")
    .attr("width", 960)
    .attr("height", 500);

var shape = d3.superformula()
    .type("asterisk")
    .size(100000)
    .segments(3600);

var path = svg.append("path")
    .attr("class", "big")
    .attr("transform", "translate(480,250)")
    .attr("d", shape);

var control = d3.select("#controls")
  .selectAll("div")
    .data(d3.entries(types.asterisk))
  .enter().append("div")
    .attr("id", function(d) { return d.key; });

control.append("label")
    .text(function(d) { return d.key; });

control.append("input")
    .attr("type", "range")
    .attr("max", 1000)
    .attr("min", 0)
    .property("value", function(d) { return scale(d.value); })
    .on("change", changed)
    .on("input", changed);

control.append("span")
    .text(function(d) { return format(d.value); });

d3.select("#controls")
  .append("div")
  .selectAll("button")
    .data(d3.entries(types))
  .enter().append("button")
    .text(function(d) { return d.key; })
    .on("click", function(d) {
      for (var param in d.value) {
        var control = d3.select("#" + param);
        control.select("input").property("value", scale(d.value[param]));
        control.select("span").text(format(d.value[param]));
        shape.param(param, d.value[param]);
      }
      path.attr("d", shape);
    });

function changed(d) {
  var v = scale.invert(this.value);
  path.attr("d", shape.param(d.key, v));
  d3.select(this.nextSibling).text(format(v));
}

</script>

superformula.js

(function() {
  var _symbol = d3.svg.symbol(),
      _line = d3.svg.line();

  d3.superformula = function() {
    var type = _symbol.type(),
        size = _symbol.size(),
        segments = size,
        params = {};

    function superformula(d, i) {
      var n, p = _superformulaTypes[type.call(this, d, i)];
      for (n in params) p[n] = params[n].call(this, d, i);
      return _superformulaPath(p, segments.call(this, d, i), Math.sqrt(size.call(this, d, i)));
    }

    superformula.type = function(x) {
      if (!arguments.length) return type;
      type = d3.functor(x);
      return superformula;
    };

    superformula.param = function(name, value) {
      if (arguments.length < 2) return params[name];
      params[name] = d3.functor(value);
      return superformula;
    };

    // size of superformula in square pixels
    superformula.size = function(x) {
      if (!arguments.length) return size;
      size = d3.functor(x);
      return superformula;
    };

    // number of discrete line segments
    superformula.segments = function(x) {
      if (!arguments.length) return segments;
      segments = d3.functor(x);
      return superformula;
    };

    return superformula;
  };

  function _superformulaPath(params, n, diameter) {
    var i = -1,
        dt = 2 * Math.PI / n,
        t,
        r = 0,
        x,
        y,
        points = [];

    while (++i < n) {
      t = params.m * (i * dt - Math.PI) / 4;
      t = Math.pow(Math.abs(Math.pow(Math.abs(Math.cos(t) / params.a), params.n2)
        + Math.pow(Math.abs(Math.sin(t) / params.b), params.n3)), -1 / params.n1);
      if (t > r) r = t;
      points.push(t);
    }

    r = diameter * Math.SQRT1_2 / r;
    i = -1; while (++i < n) {
      x = (t = points[i] * r) * Math.cos(i * dt);
      y = t * Math.sin(i * dt);
      points[i] = [Math.abs(x) < 1e-6 ? 0 : x, Math.abs(y) < 1e-6 ? 0 : y];
    }

    return _line(points) + "Z";
  }

  var _superformulaTypes = {
    asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
    bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1},
    butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1},
    circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1},
    clover: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
    cloverFour: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1},
    cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1},
    diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1},
    drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1},
    ellipse: {m: 4, n1: 2, n2: 2, n3: 2, a: 9, b: 6},
    gear: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1},
    heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18},
    heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
    hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
    malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1},
    pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1},
    rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1},
    roundedStar: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1},
    square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1},
    star: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1},
    triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1}
  };

  d3.superformulaTypes = d3.keys(_superformulaTypes);
})();