Skip to content

Instantly share code, notes, and snippets.

@sifbuilder
Last active August 23, 2016 00:01
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/7af5198d380f38c066bb26b116ea6ad7 to your computer and use it in GitHub Desktop.
Save sifbuilder/7af5198d380f38c066bb26b116ea6ad7 to your computer and use it in GitHub Desktop.
d3forms and superformula

d3forms

d3js superformula

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="https://d3js.org/d3.v4.min.js"></script>
<script src="superformula.js"></script>
<script src="index.js"></script>
/* -------------------------- */
/* state */
/* -------------------------- */
var state = {}
state.replacer = function (key,value) {
if (typeof(value) === 'object') return value
else return Math.floor(value * 100) / 100
}
state.width = 600
state.height = 400
state.x0 = 400
state.y0 = 200
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.legend = {} // info
state.legend.xloc = 5
state.legend.fontSize = 10
state.legend.yloc = state.height - state.legend.fontSize
state.legend.text = "superformula"
state.ref = {}
state.ref.rad = 10
state.ref.stroke = 'orange'
state.ref.fill = 'transparent'
state.ref.idx = Math.round(state.segments * (1 / 5))
state.ref.segments = 24
state.rect = {}
state.rect.stroke = 'red'
state.notice = {}
state.notice.xloc = 5 // params
state.notice.fontSize = 6
state.notice.yloc = state.height - state.notice.fontSize
state.notice.text = "{}"
state.controls = {}
state.controls.format = d3.format(".4n");
state.controls.scale = d3.scaleLinear()
.domain([-10, 20, 1000])
.range([0, 800, 1000])
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 svg = d3.select("body")
.append("svg")
.attr("width", state.width)
.attr("height", state.height)
.style("border", "1px solid lightgray")
/* -------------------------- */
/* superform */
/* -------------------------- */
var formShapeTransform = function (params) {
return formShape.formParams(params)
}
var formShape = d3.superformula()
.types(types)
.type("asterisk")
.size(state.size)
.segments(state.segments)
var formElem = svg.append("path")
.attr("class", "sample")
.attr("d", formShape)
.style("stroke-width", "1.5px")
.style("stroke", "#666")
.style("fill", "#ddd")
/* -------------------------- */
/* ref form */
/* -------------------------- */
var points = formShape.points()
var refPt = Math.round(points.length * (1 / 5))
var pt = points[state.ref.idx]
var formRefTransform = function (shape) {
let params = shape.formParams()
let pts = shape.points()
let tx = pts[refPt][0]
let ty = pts[refPt][1]
let p = Object.assign({}, params, {}, {"tx":tx, "ty":ty, "rad": state.ref.rad})
return formRef.formParams(p)
}
var formRef = d3.superformula()
.segments(state.ref.segments)
.formParams({"m":12,"n1":0.3,"n2":0,"n3":17.48,"a":1,"b":1,"tx":pt[0],"ty":pt[1],"rot":0, "rad": state.ref.rad})
var refElem = svg.append("path")
.attr("class", "circlePath")
.attr("d", formRef)
.style("stroke-width", "1.5px")
.style("stroke", state.ref.stroke)
.style("fill", state.ref.fill)
/* -------------------------- */
/* circle form */
/* -------------------------- */
var formCircleTransform = function (params) {
var p = []
for (let k in params) {
if (k === 'tx' || k === 'ty') p[k] = params[k]
}
return formCircle.formParams(p)
}
var formCircle = d3.superformula()
.segments(state.segments)
.formParams({"m":0.64,"n1":-1.57,"n2":0,"n3":10,"a":1,"b":1,"tx":state.x0,"ty":state.y0,"rot":0, "rad": state.rad})
var circleElem = svg.append("path")
.attr("class", "circlePath")
.attr("d", formCircle)
.style("stroke-width", "1.5px")
.style("stroke", "green")
.style("fill", "transparent")
var legendTransform = function (params) { // legend
let r = ''
for (let k in params) {
if (k === 'm') { r = r + 'm: Arity of rotational symmetry. ' }
if (k === 'n1') { r = r + 'n1: Large n1 and equals n2, n3 mark polygonal shapes' }
if (k === 'n2') { r = r + 'n2 and n3 provide axial freedom ' }
if (k === 'n3') { r = r + 'n2 = n3 represents axial symmetry ' }
if (k === 'a') { r = r + 'a excentricity but also perspective ' }
if (k === 'b') { r = r + 'b = a equal axis' }
}
return r
}
/* -------------------------- */
/* rect form */
/* -------------------------- */
var formRectTransform = function (params) {
var p = []
for (let k in params) {
if (k === 'tx' || k === 'ty') p[k] = params[k]
}
return formRect.formParams(p)
}
var formRect = d3.superformula()
.segments(state.segments)
.formParams({"m":4,"n1":100,"n2":100,"n3":100,"a":1,"b":1,"tx":state.x0,"ty":state.y0,"rot":0, "rad": state.rad * Math.sqrt(2)})
var rectInElem = svg.append("path")
.attr("class", "circlePath")
.attr("d", formRect)
.style("stroke-width", "1.5px")
.style("stroke", state.rect.stroke)
.style("fill", "transparent")
/* -------------------------- */
/* scales */
/* -------------------------- */
function changedScale(d) {
let k = d.key
let v = state.controls.scale.invert(this.value);
let params = {}; params[k] = v;
render(params)
}
var control = d3.select("#controls")
.selectAll("div")
.data(d3.entries(types.asterisk))
.enter().append("div")
.attr("id", function(d) { return d.key; })
.style("font", "8px sans-serif")
control.append("label") // input labels
.text(function(d) { return d.key; });
control.append("input") // input controls
.attr("type", "range")
.attr("max", 1000)
.attr("min", 0)
.property("value", function(d) { return state.controls.scale(d.value); })
.on("change", changedScale)
.on("input", changedScale);
control.append("span")
.text(function(d) { return state.controls.format(d.value); }); // scale values
/* -------------------------- */
/* buttons */
/* -------------------------- */
d3.select("#controls")
.append("div")
.selectAll("button")
.data(d3.entries(types))
.enter().append("button")
.text(function(d) { return d.key; })
.on("click", function(d) {
let params = {}
for (var param in d.value) {
let k = param, v = d.value[param]
params[k] = v;
let control = d3.select("#" + k);
control.select("input").property("value", state.controls.scale(v));
control.select("span").text(state.controls.format(v));
}
render(params)
});
/* -------------------------- */
/* notice */
/* -------------------------- */
var noticeElem = d3.select("#controls")
.append("div")
.selectAll(".notice")
.data([state.legend.text])
.enter().append("input")
.attr('type', "text")
.attr('id', 'notice')
.attr("class", "notice")
.attr('style', 'width: 600px;')
.style("font-family", 'sans-serif')
.style("font-size", state.notice.fontSize)
.style("fill-opacity", 1)
.on("change", changedform)
function changedform() {
let params = JSON.parse(this.value)
for (let k in params) {
let v = params[k]
let control = d3.select("#" + k)
if (!control.empty()) {
control.select("input").property("value", state.controls.scale(v))
control.select("span").text(state.controls.format(v))
}
}
render(params)
}
/* -------------------------- */
/* legend */
/* -------------------------- */
var legendElem = d3.select("#controls")
.append("div")
.selectAll(".legend")
.data(['superformula'])
.enter().append("text")
.text(d => d)
.style("font-family", 'sans-serif')
.style("font-size", state.legend.fontSize)
.style("fill-opacity", 1)
.attr('style', 'width: 600px;')
/* -------------------------- */
/* render */
/* -------------------------- */
function render(params) {
let shape = formShape.formParams(params)
formElem.attr("d", shape) // form
circleElem.attr("d", formCircleTransform(params)) // circle
rectInElem.attr("d", formRectTransform(params)) // extent
refElem.attr("d", formRefTransform(shape)) // ref
noticeElem.property('value', // notice
d => JSON.stringify(formShape.formParams(params).defparams(), state.replacer))
// legendElem.text(legendTransform(params)) // legend
}
MIT License
Copyright (c) 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
// 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 _e_tbc
var rpoints = _superformulaPoints(p, _segments, _side)
points = transformPath(p, rpoints) // assign for points getter
return _superformulaPath(points)
}
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) {
let n, p = formTpl
for (n in formParams) p[n] = formParams[n]()
return p
}
for (let n in p) {
formParams[n] = (typeof (p[n]) === "function") ? p[n] : d3_functor(p[n])
}
return superformula;
}
// defparams
superformula.defparams = function() {
if (!arguments.length) return defparams;
}
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
}
return superformula;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment