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

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more