Public
Edited
Jul 31, 2024
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function paperTexture(options = {}) {
let {
width = 800,
height = 600,
baseAngle = 45,
angleVar = 3,
coarseness = 10,
lengthVariation = 0.2,
layers = 10,
background = "white",
color = "rgb(200,0,0)",
colorVariation = 0.1,
opacity = 0.7,
blur = 2,
randomSeed = 0
} = options;
const sep = coarseness;
baseAngle *= Math.PI / 180;
angleVar *= Math.PI / 180;
const [w, h] = [width + sep * 2, height + sep * 2];
const canvas = DOM.canvas(w, h);
const ctx = canvas.getContext("2d");
const baseColor = d3.hsl(d3.color(color));
const rand = makeRandFunction(randomSeed);
const avgLen = sep;
const pds = new Poisson(
{
shape: [w, h],
minDistance: sep * 0.8,
maxDistance: sep / 0.8,
tries: 15
},
rand
);
ctx.lineWidth = sep / 4;
const [minv, maxv] = [
Math.max(0.5, 1 - colorVariation),
Math.min(1.5, 1 + colorVariation)
];
ctx.fillStyle = background;
ctx.fillRect(0, 0, w, h);
for (let [x, y] of pds.fill()) {
ctx.beginPath();
const vlen = avgLen * rand(1 - lengthVariation, 1 + lengthVariation);
const ang = baseAngle + rand(-angleVar, angleVar);
const [vx, vy] = [vlen * Math.cos(ang), vlen * Math.sin(ang)];
ctx.moveTo(x - vx, y - vy);
ctx.lineTo(x + vx, y + vy);
let color = d3.hsl(
baseColor.h,
baseColor.s * rand(minv, maxv),
baseColor.l * rand(minv, maxv)
);
color.opacity = opacity;
ctx.strokeStyle = color.formatRgb();
ctx.stroke();
}
const canvas2 = DOM.canvas(width, height);
const ctx2 = canvas2.getContext("2d");
ctx2.filter = `blur(${blur}px)`;
ctx2.drawImage(canvas, 0, 0);
if (layers > 1) {
ctx2.globalAlpha = 0.5;
for (let i = 1; i < layers; i++)
ctx2.drawImage(canvas, rand(-sep, 0), rand(-sep, 0));
}
return canvas2;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function fiberTexture(options = {}) {
let {
width = 800,
height = 600,
linewidth = 1,
coarseness = 10,
waviness = 0.2,
background = "white",
color = "black",
colorVariation = 0,
threadLen = 10,
opacity = 0.2,
randomSeed = 0
} = options;
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext("2d");
const baseColor = d3.hsl(color);
const sep = coarseness;
const avgLen = sep * 2;
const rand = makeRandFunction(randomSeed);
const pds = new Poisson(
{
shape: [width, height],
minDistance: sep * 0.8,
maxDistance: sep / 0.8,
tries: 15
},
rand
);
ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height);
ctx.lineWidth = linewidth;
const [minv, maxv] = [
Math.max(0.5, 1 - colorVariation),
Math.min(1.5, 1 + colorVariation)
];
for (let [x, y] of pds.fill()) {
let angle = rand(-Math.PI, Math.PI);
let [px, py] = [x, y];
ctx.beginPath();
ctx.moveTo(x, y);
for (let i = 0; i < threadLen; i++) {
const pts = d3.range(3).map((k) => {
const len = sep * 0.3 * rand(0.7, 1 / 0.7);
if (k != 0) angle += Math.PI * 0.25 * rand(-waviness, waviness);
const p = [px + len * Math.cos(angle), py + len * Math.sin(angle)];
[px, py] = p;
return p;
});
ctx.bezierCurveTo(...pts.flat());
}
let color = d3.hsl(
baseColor.h * rand(minv, maxv),
baseColor.s * rand(minv, maxv),
baseColor.l * rand(minv, maxv)
);
color.opacity = opacity;
ctx.strokeStyle = color.formatRgb();
ctx.stroke();
}
return canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
sampleNoiseTexture = noiseTexture(noiseTexturePresets)
Insert cell
function noiseTexture(options = {}) {
let {
width = 800,
height = 600,
coarseness = 30,
background = "white",
color = "red",
baseSize = 1,
sizeModulation = 1,
aspectModulation = 1,
angleModulation = 1,
hueModulation = 0.5,
lumModulation = 0.5,
primitive = "line",
sizeNoiseAmplitude = 100,
aspectNoiseAmplitude = 100,
angleNoiseAmplitude = 200,
hueNoiseAmplitude = 100,
lumNoiseAmplitude = 200,
opacity = 0.7,
randomSeed = 1,
blur = 0
} = options;
const baseColor = d3.hsl(d3.color(color));
baseColor.opacity = opacity;
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext("2d");
const sep = coarseness;
const [deltax, deltay] = [sep * 0.5, sep * 0.5];
const size = sep * 0.5 * baseSize;
const rand = makeRandFunction(randomSeed);
const makeNoise = (amplitude) => {
const scale = -Math.log2(amplitude);
return createSimplexNoise({
xScaleLog: scale,
yScaleLog: scale,
octaves: 1,
random: rand
});
};
const sizeNoise = makeNoise(sizeNoiseAmplitude);
const aspectNoise = makeNoise(aspectNoiseAmplitude);
const angleNoise = makeNoise(angleNoiseAmplitude);
const hueNoise = makeNoise(hueNoiseAmplitude);
const lumNoise = makeNoise(lumNoiseAmplitude);
const draw =
primitive == "circle"
? () => {
ctx.beginPath();
ctx.arc(0, 0, 0.5, 0, Math.PI * 2);
ctx.fill();
}
: primitive == "square"
? () => {
ctx.fillRect(-0.5, -0.5, 1, 1);
}
: primitive == "triangle"
? () => {
ctx.beginPath();
ctx.moveTo(-0.5, -0.5);
ctx.lineTo(0.5, 0.5);
ctx.lineTo(-0.5, 0.5);
ctx.fill();
}
: () => {
ctx.beginPath();
ctx.moveTo(-0.25, -0.75);
ctx.lineTo(-0.75, -0.25);
ctx.lineTo(0.25, 0.75);
ctx.lineTo(0.75, 0.25);
ctx.fill();
};
ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height);
for (let x = -deltax; x - deltax <= width; x += deltax) {
for (let y = -deltay; y - deltay <= height; y += deltay) {
ctx.resetTransform();
ctx.translate(x, y);
const ang = angleModulation * angleNoise(x, y) * Math.PI;
ctx.rotate(ang);
const s = 1 + sizeModulation * sizeNoise(x, y);
const a = aspectModulation * aspectNoise(x, y);
ctx.scale(size * s * (1 + a), size * s * (1 - a));
const hfactor = hueModulation * 180 * hueNoise(x, y);
const lfactor = lumModulation * 0.5 * lumNoise(x, y);
ctx.fillStyle = d3
.hsl(baseColor.h + hfactor, baseColor.s, baseColor.l + lfactor, opacity)
.formatRgb();
draw();
}
}
const canvas2 = DOM.canvas(width, height);
const ctx2 = canvas2.getContext("2d");
ctx2.filter = `blur(${blur}px)`;
ctx2.drawImage(canvas, 0, 0);
return canvas2;
}
Insert cell
Insert cell
Insert cell
presets = FileAttachment("presets@1.json").json()
Insert cell
function collage(images, options = {}) {
const { width = 800, height = 600 } = options;
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext("2d");
ctx.shadowColor = "black";
ctx.shadowBlur = 20;
const delta = width / images.length;
let x = 0;
for (let img of images) {
ctx.drawImage(img, x, 0, width, height);
x += delta;
}
return canvas;
}
Insert cell
collage([samplePaperTexture, sampleFiberTexture, sampleNoiseTexture])
Insert cell
<br>
## Random stuff
Insert cell
rand = makeRandFunction(randomSeed)
Insert cell
function makeRandFunction(randomSeed) {
const baseRandom = d3.randomLcg(randomSeed);
return (a, b) =>
b === undefined
? a === undefined
? baseRandom()
: baseRandom() * a
: baseRandom() * (b - a) + a;
}
Insert cell
mutable randomSeed = Math.random()
Insert cell
Insert cell
Poisson = require("https://bundle.run/poisson-disk-sampling@2.2.2")
Insert cell
import { createSimplexNoise } from "@esperanc/simplex-noise-exerciser"
Insert cell
import { presetMenu } from "@esperanc/input-presets"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more