Skip to content

Instantly share code, notes, and snippets.

@sifbuilder
Last active August 21, 2016 18:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sifbuilder/64ba67dd7ef09cc466efdb3a9bfafe2a to your computer and use it in GitHub Desktop.
Save sifbuilder/64ba67dd7ef09cc466efdb3a9bfafe2a to your computer and use it in GitHub Desktop.
d3js superformula mathematical force of nature forms or the beauty of the symmetry of the area invariant

d3js superformula

mathematics of natural forms or the beauty of the symmetry of the area invariant

Author

presented by sifbuilder

based upon

encouraging to read

building on

having found useful

Setup

$ npm install

Running

$ npm start

Build

$ npm run build

License

MIT

<!DOCTYPE html>
<meta charset="utf-8">
<title>Superformula</title>
<style>
path {
stroke-width: 1.5px;
stroke: #666;
fill: #ddd;
}
#controls {
position: absolute;
width: 270px;
font: 8px sans-serif;
}
#controls span,
#controls label {
position: relative;
top: -5px;
padding: 5px;
display: inline-block;
width: 8px;
}
#controls button {
font: 10px sans-serif;
padding: 5px;
width: 70px;
}
</style>
<div id="controls"></div>
<script src="d3.v4.js"></script>
<script src="superformula.js"></script>
<script src="index.js"></script>
var state = {}
state.replacer = function (key,value) {
if (typeof(value) === 'object') return value
else return Math.floor(value * 100) / 100
}
state.width = 960
state.height = 500
state.x0 = 480
state.y0 = 250
state.rot0 = 0
state.segments = 360
state.size = 100000
state.side = Math.sqrt(state.size)
state.rad = state.side / 2
state.refdot = Math.round(state.segments * (1 / 4))
state.legendXloc = 5
state.legendFontSize = 30
state.legendYloc = state.height - state.legendFontSize
state.noticeXloc = 5
state.noticeFontSize = 12
state.noticeYloc = state.height - state.noticeFontSize
var types = {
asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
clover: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
cloverFour: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
ellipse: {m: 4, n1: 2, n2: 2, n3: 2, a: 9, b: 6, tx: state.x0, ty: state.y0, rot: state.rot0},
gear: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18, tx: state.x0, ty: state.y0, rot: state.rot0},
heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
roundedStar: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
star: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1, tx: state.x0, ty: state.y0, rot: state.rot0},
}
var format = d3.format(".4n");
var scale = d3.scaleLinear()
.domain([-10, 20, 1000])
.range([0, 800, 1000])
var svg = d3.select("body")
.append("svg")
.attr("width", state.width)
.attr("height", state.height)
.style("border", "1px solid lightgray")
var formShape = d3.superformula() // form
.types(types)
.type("asterisk")
.size(state.size)
.segments(state.segments)
var formPath = svg.append("path")
.attr("class", "sample")
.attr("d", formShape);
var refAttrKeyVal = function (shape) { // refs
let pts = shape.points()
let r = {}
r.cx = pts[state.refdot][0]
r.cy = pts[state.refdot][1]
return r
}
var points = formShape.points()
var refPoint = points[Math.round(points.length * (1 / 4))]
var jsonCircle =
{ "x": refPoint[0], "y": refPoint[1], "rad": 4, "fill" : "black" }
var refElem = svg.append("circle")
.datum(jsonCircle)
.attr("class", "refcircle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.rad)
.attr("fill", d => d.fill)
.attr("stroke", d => d.stroke)
var circleAttrKeyVal = function (key, val) { // circle
let r = {key, val}
if (key === 'tx') {r.key = 'cx'; r.val = val }
if (key === 'ty') {r.key = 'cy'; r.val = val }
return r
}
var circleShape =
{ "x": state.x0, "y": state.y0, "rad": state.rad, "fill" : "transparent", "stroke" : "green" }
var circleElem = svg.append("circle")
.datum(circleShape)
.attr("class", "circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.rad)
.attr("fill", d => d.fill)
.attr("stroke", d => d.stroke)
var legendAttrKeyVal = function (key, val) { // legend
let r = ''
if (key === 'm') { r = 'Arity of rotational symmetry. ' }
if (key === 'n1') { r = 'Large n1 and equals n2, n3 mark polygonal shapes' }
if (key === 'n2') { r = 'n2 and n3 provide axial freedom ' }
if (key === 'n3') { r = 'n2 = n3 represents axial symmetry ' }
if (key === 'a') { r = ' ' }
if (key === 'b') { r = ' ' }
return r
}
var legendElem = svg.append("text")
.data(['mathematical beauty of natural forms'])
.classed("info", true)
.style("font-family", 'sans-serif')
.attr("x", d => state.legendXloc)
.attr("y", d => state.legendYloc)
.style("font-size", d => state.legendFontSize)
.text(d => d)
.style("fill-opacity", 1)
var noticeElem = svg.append("text") // notice
.data([''])
.classed("notice", true)
.style("font-family", 'sans-serif')
.attr("x", d => state.noticeXloc)
.attr("y", d => state.noticeYloc)
.style("font-size", d => state.noticeFontSize)
.text(d => d)
.style("fill-opacity", 1)
var rectInAttrKeyVal = function (key, val) { // extent
let r = {key, val}
if (key === 'tx') {r.key = 'x'; r.val = val - state.side / 2}
if (key === 'ty') {r.key = 'y'; r.val = val - state.side / 2 }
return r
}
var rectInShape =
{ "x": state.x0 - state.side / 2, "y": state.y0 - state.side / 2, "width": state.side, "height": state.side, "fill" : "transparent", "stroke" : "red" }
var rectInElem = svg.append("rect")
.datum(rectInShape)
.attr("class", "extent")
.attr("x", d => d.x)
.attr("y", d => d.y )
.attr("width", d => d.width)
.attr("height", d => d.height)
.attr("fill", d => d.fill)
.attr("stroke", d => d.stroke)
var control = d3.select("#controls") // control
.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]));
formShape.param(param, d.value[param]);
}
formPath.attr("d", formShape);
refElem.attr("cx", refAttrKeyVal(formShape).cx)
refElem.attr("cy", refAttrKeyVal(formShape).cy)
});
function changed(d) { // board
var v = scale.invert(this.value);
formShape.formParam(d.key, v)
formPath.attr("d", formShape)
circleElem.attr(circleAttrKeyVal(d.key, v).key, circleAttrKeyVal(d.key, v).val)
rectInElem.attr(rectInAttrKeyVal(d.key, v).key, rectInAttrKeyVal(d.key, v).val)
legendElem.text(legendAttrKeyVal(d.key, v))
noticeElem.text(JSON.stringify(formShape.defparams(), state.replacer) )
refElem.attr("cx", refAttrKeyVal(formShape).cx)
refElem.attr("cy", refAttrKeyVal(formShape).cy)
d3.select(this.nextSibling).text(format(v));
}
// Mike Bostock’s Block http://bl.ocks.org/mbostock/1021103
// Christophe Viau implemented a new shape type as a D3 plugin based on superformulas.
(function() {
d3.superformula = function superformula() {
var _symbol = d3.symbol(),
_line = d3.line();
function d3_functor(v) {
return typeof v === "function" ? v : function() {
return v;
};
}
function transform(_) {
if (!_) return noop;
var x0 = 0,
y0 = 0,
kx = 1,
ky = 1,
dx = _.tx,
dy = _.ty,
rot = _.rot // rads
var transform = function(p = [1, 1]) {
var point = []
x0 = (x0 === undefined) ? x0 : 0
y0 = (y0 === undefined) ? y0 : 0
var pointx = p[0] * Math.cos(rot) - p[1] * Math.sin(rot)
var pointy = p[0] * Math.sin(rot) + p[1] * Math.cos(rot)
point[0] = (x0 += pointx) * kx + dx
point[1] = (y0 += pointy) * ky + dy
return point
}
return transform
}
function transformPath(params, path) {
var _transform = transform(params)
let tpath = []
for (let i = 0; i < path.length; i++) {
tpath.push(_transform(path[i]))
}
return tpath
}
var formTpl = {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1, tx: 0, ty: 0, rot: 0} // formParams
var type = null
var types = {
asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1, tx: 0, ty: 0, rot: 0},
circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
cloverot: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
cloverFourot: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
ellipse: {m: 4, n1: 2, n2: 2, n3: 2, a: 9, b: 6, tx: 0, ty: 0, rot: 0},
gearot: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18, tx: 0, ty: 0, rot: 0},
heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1, tx: 0, ty: 0, rot: 0},
roundedStarot: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
starot: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1, tx: 0, ty: 0, rot: 0},
}
var superformulaTypes = Object.keys(types)
var size = _symbol.size() // size of the extent
var segments = segments // number of segments of resulting path
var formParams = {} // passed superformula and transform formParams
var points = [] // transformed path points
var extent = [] // transformed suprescribing square
var tcenter = [] // transformed center transform[0, 0]
var defparams = {} // form definition params
function _superformulaPath(points) {
return _line(points) + "Z"; // return path
}
function _superformulaPoints(params, n, diameter) {
var i = -1,
dt = 2 * Math.PI / n, // sector per symmetry dimension
t,
r = 0, // initialize
x,
y,
pts = []; // points in path
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;
pts.push(t);
}
// superseed diameter with params.rad if defined _e_ tbc
if (params.rad !== undefined && params.rad !== 0) diameter = 2 * params.rad
r = diameter / 2 / r // * Math.SQRT1_2 / r // normalize _e_ tbc
i = -1; while (++i < n) {
let pt = [(t = pts[i] * r) * Math.cos(i * dt), t * Math.sin(i * dt)]
pts[i] = [Math.abs(pt[0]) < 1e-6 ? 0 : pt[0], Math.abs(pt[1]) < 1e-6 ? 0 : pt[1]]
}
return pts;
}
function superformula(d, i) {
var n
var _segments = segments.call(this, d, i)
var _side = Math.sqrt(size.call(this, d, i)) //
var p = formTpl // initialize form params
if (typeof(type) === 'function') {
var _type = type.call(this, d, i)
p = types[_type]
}
for (n in formParams) p[n] = formParams[n].call(this, d, i)
if (d && d.shapeParams !== 'undefined') {
for (n in d.shapeParams) p[n] = d3_functor(d.shapeParams[n]).call(this, d, i)
}
defparams = p // assign for defparams getter
var rpoints = _superformulaPoints(p, _segments, _side)
points = transformPath(p, rpoints) // assign for points getter
var path = _superformulaPath(points)
return path
}
superformula.type = function(x) {
if (!arguments.length) return type;
type = d3_functor(x);
return superformula;
};
superformula.formParam = function(name, value) {
if (arguments.length < 2) return formParams[name];
formParams[name] = d3_functor(value);
return superformula;
};
superformula.formParams = function(p) {
if (arguments.length < 1) return formParams
for (let n in p) {
formParams[n] = (typeof (p[n]) === "function") ? p[n] : d3_functor(p[n])
}
return superformula;
}
// defparams
superformula.defparams = function(x) {
if (!arguments.length) return defparams;
defparams = d3_functor(x);
return superformula;
}
superformula.types = function(x) {
if (!arguments.length) return types
types = x
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;
};
// line segments
superformula.points = function() {
if (!arguments.length) return points;
points = d3_functor(x);
return superformula;
}
return superformula;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment