Skip to content

Instantly share code, notes, and snippets.

@spikelynch
Created May 1, 2019 06:22
Show Gist options
  • Save spikelynch/21040b0a641a9957a5080370f97c4220 to your computer and use it in GitHub Desktop.
Save spikelynch/21040b0a641a9957a5080370f97c4220 to your computer and use it in GitHub Desktop.
// URL: https://observablehq.com/@spikelynch/fibomod
// Title: Fibomod
// Author: Mike Lynch (@spikelynch)
// Version: 286
// Runtime version: 1
const m0 = {
id: "0e0d3a0cc59bfb27@286",
variables: [
{
inputs: ["md"],
value: (function(md){return(
md`# Fibomod
A visualisation of the Fibonacci series modulo a chosen integer. I wrote a version of this in the early 90s on a PC: I often use it to try out new graphics or visualisation systems.
The first slider sets N, the modulo value. The series always seems to be periodic, but the orbit length varies wildly for different values of N. I expect there's some interesting mathematics behind all this, but I don't know if I'd understand it. Values with long orbits will make your laptop heat up.
The second slider adjusts the curvature of the beziers.`
)})
},
{
from: "@jashkenas/inputs",
name: "select",
remote: "select"
},
{
from: "@jashkenas/inputs",
name: "slider",
remote: "slider"
},
{
from: "@jashkenas/inputs",
name: "color",
remote: "color"
},
{
name: "viewof n",
inputs: ["slider"],
value: (function(slider){return(
slider({
min: 2,
max: 1000,
step: 1,
value: 600,
description: "Display Fibonacci sequence mod N"
})
)})
},
{
name: "n",
inputs: ["Generators","viewof n"],
value: (G, _) => G.input(_)
},
{
name: "viewof flex",
inputs: ["slider"],
value: (function(slider){return(
slider({
min: 0,
max: 2.0,
value: 0.85,
description: "Bezier curvature"
})
)})
},
{
name: "flex",
inputs: ["Generators","viewof flex"],
value: (G, _) => G.input(_)
},
{
name: "viewof colourscheme",
inputs: ["select","colourSchemes"],
value: (function(select,colourSchemes){return(
select({
options: Object.keys(colourSchemes),
description: "Colour scheme"
})
)})
},
{
name: "colourscheme",
inputs: ["Generators","viewof colourscheme"],
value: (G, _) => G.input(_)
},
{
name: "viewof backgroundc",
inputs: ["color"],
value: (function(color){return(
color()
)})
},
{
name: "backgroundc",
inputs: ["Generators","viewof backgroundc"],
value: (G, _) => G.input(_)
},
{
name: "chart",
inputs: ["d3","DOM","width","height","backgroundc","n","fibomod","f2radians","r","bezierLink","colourSchemes","colourscheme"],
value: (function(d3,DOM,width,height,backgroundc,n,fibomod,f2radians,r,bezierLink,colourSchemes,colourscheme)
{
const svg = d3.select(DOM.svg(width, height));
svg.append("style").text(`svg { background-color: ${backgroundc} } .label { fill: white }`);
const captions = svg.append("g")
.attr("transform",`translate(${width * 0.01},${height * 0.8})`);
captions.append("text")
.attr("class", "label")
.attr("x", 0)
.attr("y", 10)
.text(`n = ${n}`);
captions.append("text")
.attr("class", "label")
.attr("x", 0)
.attr("y", 30)
.text(`length = ${fibomod.length - 1}`);
const linkg = d3.linkRadial()
.angle(f2radians)
.radius(r);
svg.append("g")
.selectAll("path")
.data(fibomod)
.enter()
.append("path")
.attr("class", "Hello")
.attr("d", bezierLink)
.attr("stroke-width", 2)
.attr("stroke", (d,i) => colourSchemes[colourscheme](i / fibomod.length))
.attr("opacity", .4)
.attr("fill", "none");
// svg.append("g")
// .selectAll("line")
// .data(fibomod)
// .enter()
// .append("line")
// .attr("x1", d => xpt(d[0]))
// .attr("y1", d => ypt(d[0]))
// .attr("x2", d => xpt(d[1]))
// .attr("y2", d => ypt(d[1]))
// .attr("stroke-width", .5)
// .attr("stroke", (d,i) => d3.interpolateMagma(i / fibomod.length))
// .attr("opacity", .7)
// .attr("fill", "none");
return svg.node();
}
)
},
{
name: "fibomod",
inputs: ["Promises","n"],
value: (async function*(Promises,n)
{
let xs = [ [ 1, 0 ] ];
let running = true;
while( running ) {
await Promises.delay(1);
yield xs;
let x3 = (xs[0][0] + xs[0][1]) % n;
xs.unshift([ x3, xs[0][0] ]);
if( xs[0][0] === 1 && xs[0][1] === 0 ) {
running = false;
}
}
}
)
},
{
name: "f2radians",
inputs: ["n"],
value: (function(n)
{
return (i) => 2 * Math.PI * i / n
}
)
},
{
name: "xpt",
inputs: ["x0","f2radians"],
value: (function(x0,f2radians)
{
return (d, r0) => x0 - Math.sin(f2radians(d)) * r0;
}
)
},
{
name: "ypt",
inputs: ["x0","f2radians"],
value: (function(x0,f2radians)
{
return ( d, r0 ) => x0 - Math.cos(f2radians(d)) * r0
}
)
},
{
name: "bezierLink",
inputs: ["d3","xpt","r","ypt","flex"],
value: (function(d3,xpt,r,ypt,flex)
{
return (d) => {
const p = d3.path();
p.moveTo(xpt(d[0], r), ypt(d[0], r));
p.bezierCurveTo(xpt(d[0], r * flex), ypt(d[0], r * flex), xpt(d[1], r * flex), ypt(d[1], r * flex), xpt(d[1], r), ypt(d[1], r));
return p.toString();
}
}
)
},
{
name: "d3",
inputs: ["require"],
value: (function(require){return(
require("d3@5")
)})
},
{
name: "height",
value: (function(){return(
800
)})
},
{
name: "width",
value: (function(){return(
800
)})
},
{
name: "r",
inputs: ["width"],
value: (function(width){return(
0.8 * width / 2
)})
},
{
name: "x0",
inputs: ["width"],
value: (function(width){return(
width / 2
)})
},
{
name: "y0",
inputs: ["x0"],
value: (function(x0){return(
x0
)})
},
{
name: "colourSchemes",
inputs: ["d3"],
value: (function(d3)
{
return {
'Blues': d3.interpolateBlues,
'Greens': d3.interpolateGreens,
'Greys': d3.interpolateGreys,
'Oranges': d3.interpolateOranges,
'Purples': d3.interpolatePurples,
'Reds': d3.interpolateReds,
'Cubehelix': d3.interpolateCubehelixDefault,
'Rainbow': d3.interpolateRainbow,
'Sinebow': d3.interpolateSinebow,
'Inferno': d3.interpolateInferno,
'Plasma': d3.interpolatePlasma,
'Magma': d3.interpolateMagma,
'Cool': d3.interpolateCool,
'Warm': d3.interpolateWarm,
'Viridis': d3.interpolateViridis,
'Spectral': d3.interpolateSpectral,
'BrBG': d3.interpolateBrBG,
'PRGn': d3.interpolatePRGn,
'PiYG': d3.interpolatePiYG,
'PuOr': d3.interpolatePuOr,
'RdBu': d3.interpolateRdBu,
'RdGy': d3.interpolateRdGy,
'RdYlBu': d3.interpolateRdYlBu,
'RdYlGn': d3.interpolateRdYlGn,
'BuGn': d3.interpolateBuGn,
'BuPu': d3.interpolateBuPu,
'GnBu': d3.interpolateGnBu,
'OrRd': d3.interpolateOrRd,
'PuBu': d3.interpolatePuBu,
'PuBuGn': d3.interpolatePuBuGn,
'PuRd': d3.interpolatePuRd,
'RdPu': d3.interpolateRdPu,
'YlGn': d3.interpolateYlGn,
'YlGnBu': d3.interpolateYlGnBu,
'YlOrBr': d3.interpolateYlOrBr,
'YlOrRd': d3.interpolateYlOrRd
}
}
)
},
{
name: "htmlOption",
inputs: ["html"],
value: (function(html)
{
return (n) => html`<option value=${n}>${n}</option>`;
}
)
}
]
};
const m1 = {
id: "@jashkenas/inputs",
variables: [
{
name: "select",
inputs: ["input","html"],
value: (function(input,html){return(
function select(config = {}) {
let {
value: formValue,
title,
description,
submit,
multiple,
size,
options
} = config;
if (Array.isArray(config)) options = config;
options = options.map(
o => (typeof o === "object" ? o : { value: o, label: o })
);
const form = input({
type: "select",
title,
description,
submit,
getValue: input => {
const selected = Array.prototype.filter
.call(input.options, i => i.selected)
.map(i => i.value);
return multiple ? selected : selected[0];
},
form: html`
<form>
<select name="input" ${
multiple ? `multiple size="${size || options.length}"` : ""
}>
${options.map(({ value, label }) => Object.assign(html`<option>`, {
value,
selected: Array.isArray(formValue)
? formValue.includes(value)
: formValue === value,
textContent: label
}))}
</select>
</form>
`
});
form.output.remove();
return form;
}
)})
},
{
name: "slider",
inputs: ["input"],
value: (function(input){return(
function slider(config = {}) {
let {value, min = 0, max = 1, step = "any", precision = 2, title, description, getValue, format, display, submit} = config;
if (typeof config == "number") value = config;
if (value == null) value = (max + min) / 2;
precision = Math.pow(10, precision);
if (!getValue) getValue = input => Math.round(input.valueAsNumber * precision) / precision;
return input({
type: "range", title, description, submit, format, display,
attributes: {min, max, step, value},
getValue
});
}
)})
},
{
name: "color",
inputs: ["input"],
value: (function(input){return(
function color(config = {}) {
let {value, title, description, submit, display} = config;
if (typeof config == "string") value = config;
if (value == null) value = '#000000';
const form = input({
type: "color", title, description, submit, display,
attributes: {value}
});
if (title || description) form.input.style.margin = "5px 0";
return form;
}
)})
},
{
name: "input",
inputs: ["html","d3format"],
value: (function(html,d3format){return(
function input(config) {
let {
form,
type = "text",
attributes = {},
action,
getValue,
title,
description,
format,
display,
submit,
options
} = config;
const wrapper = html`<div></div>`;
if (!form)
form = html`<form>
<input name=input type=${type} />
</form>`;
Object.keys(attributes).forEach(key => {
const val = attributes[key];
if (val != null) form.input.setAttribute(key, val);
});
if (submit)
form.append(
html`<input name=submit type=submit style="margin: 0 0.75em" value="${
typeof submit == "string" ? submit : "Submit"
}" />`
);
form.append(
html`<output name=output style="font: 14px Menlo, Consolas, monospace; margin-left: 0.5em;"></output>`
);
if (title)
form.prepend(
html`<div style="font: 700 0.9rem sans-serif;">${title}</div>`
);
if (description)
form.append(
html`<div style="font-size: 0.85rem; font-style: italic;">${description}</div>`
);
if (format) format = typeof format === "function" ? format : d3format.format(format);
if (action) {
action(form);
} else {
const verb = submit
? "onsubmit"
: type == "button"
? "onclick"
: type == "checkbox" || type == "radio"
? "onchange"
: "oninput";
form[verb] = e => {
e && e.preventDefault();
const value = getValue ? getValue(form.input) : form.input.value;
if (form.output) {
const out = display ? display(value) : format ? format(value) : value;
if (out instanceof window.Element) {
while (form.output.hasChildNodes()) {
form.output.removeChild(form.output.lastChild);
}
form.output.append(out);
} else {
form.output.value = out;
}
}
form.value = value;
if (verb !== "oninput")
form.dispatchEvent(new CustomEvent("input", { bubbles: true }));
};
if (verb !== "oninput")
wrapper.oninput = e => e && e.stopPropagation() && e.preventDefault();
if (verb !== "onsubmit") form.onsubmit = e => e && e.preventDefault();
form[verb]();
}
while (form.childNodes.length) {
wrapper.appendChild(form.childNodes[0]);
}
form.append(wrapper);
return form;
}
)})
},
{
name: "d3format",
inputs: ["require"],
value: (function(require){return(
require("d3-format@1")
)})
}
]
};
const notebook = {
id: "0e0d3a0cc59bfb27@286",
modules: [m0,m1]
};
export default notebook;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment