d3js superformula
Last active
August 23, 2016 00:01
-
-
Save sifbuilder/7af5198d380f38c066bb26b116ea6ad7 to your computer and use it in GitHub Desktop.
d3forms and superformula
presented by sifbuilder
based upon
encouraging to read
building on
having found useful
- [Kent C. Dodds' How to Contribute to an Open Source Project on GitHub] (https://github.com/eggheadio-github/stack-overflow-copy-paste)
$ npm install
$ npm start
$ npm run build
MIT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -------------------------- */ | |
/* 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 | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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