Published
Edited
Feb 4, 2022
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
lineProb = 2/3
Insert cell
Insert cell
lineSizeRatio = 1/128
Insert cell
function createLineLengths ([start, end]) {
// parameters: List of integer.
// start: start x value
// end: end x value
// returns: a list of pairs [[start, end]] of each segment from start to end, probabalistically
// drawn from a bernoulli distribution.
let minLineSize = lineSizeRatio * (end - start);
let x = start;
let rLine = d3.randomBernoulli(lineProb)
let lines = [];
while (x < end) {
if (rLine() > 0) {
lines.push([x, x + minLineSize]);
}
x = x + minLineSize;
}
return lines
}
Insert cell
Insert cell
Insert cell
cols = _.range(0,121,1)
Insert cell
size = [750,750]
Insert cell
Insert cell
p5(sketch => {
sketch.setup = function () {
sketch.createCanvas(size[0],size[1]);
sketch.noLoop()
}

sketch.draw = function () {
// split into columns
sketch.stroke('#000000')
sketch.strokeWeight(size[0] / (121*2));
_.forEach(cols, (c) => {
let lineLengths = createLineLengths([0, size[1]])
lineLengths.forEach(l => {
sketch.line(xScale(c), l[0], xScale(c), l[1])
})
})
}
})
Insert cell
Insert cell
p5(sketch => {
sketch.setup = function () {
sketch.createCanvas(size[0],size[1]);
sketch.noLoop()
}

sketch.draw = function () {
// split into columns
sketch.stroke('#000000')
sketch.strokeWeight(size[0] / (121*2));
// Noise. First, create the array of noise:
const noises = createNoises(size[1])
// Then generate an index array of all our y values
const yVals = _.range(0,size[1], lineSizeRatio * size[1])
// looks up noise for each y value
function getNoise(y) { return noises[_.indexOf(yVals, y)] }
_.forEach(cols, (c) => {
let lineLengths = createLineLengths([0, size[1]])
lineLengths.forEach((l,i) => {
// looks up our noise.
let n = getNoise(l[0])
sketch.line(xScale(c) + n, l[0], xScale(c) + n, l[1])
})
})
}
})
Insert cell
createNoises = {
const NoiseMean = 0
const NoiseStd = 0.3 // std of normal dist for amount of noise
const NoiseProb = 1/2 // probabilty of applying at any y-axis interval
return function createNoises(s) {
// for a given size, creates an array of noise values for each pixel based on
// lineSizeRatio.
let noises = []
let noise = d3.randomNormal(NoiseMean, NoiseStd)
// create noise with a 1/5 chance as we go down the y-axis
let willChange = d3.randomBernoulli(NoiseProb)
let currentNoise = noise()
_.range(0,s, lineSizeRatio * s).forEach(
(v,i,c) => {
if (willChange()) {
currentNoise = noise()
}
noises.push(currentNoise)
})
return noises;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
let d = tri.data
let x = 375;//d3.randomUniform(750)();
let y = 230;//d3.randomUniform(750)();
let idx = ((750 * 4) * y) + (x * 4)
d[idx] = 255
d[idx + 1] = 255
d[idx + 2] = 255
let data = new ImageData(d, tri.width, tri.height)
let ctx = DOM.canvas(data.width, data.height).getContext("2d");
ctx.canvas.style.width = `${data.width}px`;
ctx.putImageData(data, 0, 0);
return ctx.canvas;
}
Insert cell
Insert cell
function getValue([x, y], d) {
// Returns the average value across all R,G,B values for a pixel
// at x,y in data array d.
let idx = ((750 * 4) * y) + (x * 4)
return ( d[idx] + d[idx + 1] + d[idx + 2]) / 3;
}
Insert cell
Insert cell
function getYNeighborMean([x, y], d, range) {
// returns the average values of pixels `range/2` above and `range/2` below
// the pixel at `x,y` in `d`.
let neighbors = _.range(y - _.ceil(range/2), y + _.ceil(range/2));
return _.sum(neighbors.map(y2 => getValue([x, y2], d))) / neighbors.length;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// let range = 20
return p5(sketch => {
sketch.setup = function () {
sketch.createCanvas(size[0],size[1]);
sketch.noLoop()
}
sketch.draw = function () {
sketch.background('#ffffff');
// split into columns
sketch.stroke('#000000')
sketch.strokeWeight(size[0] / (121*2));
// Noise. First, create the array of noise:
const noises = createNoises(size[1])
// Then generate an index array of all our y values
const yVals = _.range(0,size[1], lineSizeRatio * size[1])
// looks up noise for each y value
function getNoise(y) { return noises[_.indexOf(yVals, y)] }
_.forEach(cols, (c) => {
let lineLengths = createLinesWithShape(c, size[1], tri.data, range)
lineLengths.forEach((l,i) => {
// looks up our noise.
let n = getNoise(l[0])
sketch.line(xScale(c) + n, l[0], xScale(c) + n, l[1])
})
})
}
})
}
Insert cell
// maps value of a pixel (255-0) to a probability range
lineScale = d3.scaleLinear()
.domain([255,0])
.range([0, drawProb/100])

Insert cell
function createLinesWithShape (col, size, d, range) {
// parameters: List of integer.
// x: x val to create line on
// size: length of lines in total
// returns: a list of pairs [[start, end]] of each segment from start to end, probabalistically
// drawn from a distribution ....
let minLineSize = lineSizeRatio * size;
let x = 0;
let rLine = d3.randomBernoulli(lineProb)
let lines = [];
while (x < size) {
let colX = xScale(col)
let drawLine = d3.randomBernoulli(lineScale(getYNeighborMean([_.floor(colX), _.floor(x)], d, range)))
if ((drawLine() > 0) | (rLine() > 0)) {
lines.push([x, x + minLineSize]);
}
x = x + minLineSize;
}
return lines
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
# Animating
One fun thing about this work is that it allows us to build on top of her original piece. here is a fun little animation that iterates through different range values. I use perlin noise so it sort of 'walks' through different ranges rather than jumps randomly around.
Insert cell
Insert cell
{
let randRange = d3.randomNormal(5, 75);
let MAX_RANGE = 75
let range = 5
let ix = 0;
return p5(sketch => {
sketch.setup = function () {
if (!isLooping)
sketch.noLoop()
else
sketch.loop()
sketch.createCanvas(size[0],size[1]);
let SCALE = 0.01
let r = perlin2(ix * SCALE, 1)
range = Math.ceil((r + 1) / 2 * MAX_RANGE)
ix += 1
}
sketch.draw = function () {
sketch.background('#ffffff');
// split into columns
sketch.stroke('#000000')
sketch.strokeWeight(size[0] / (121*2));
// Noise. First, create the array of noise:
const noises = createNoises(size[1])
// Then generate an index array of all our y values
const yVals = _.range(0,size[1], lineSizeRatio * size[1])
// looks up noise for each y value
function getNoise(y) { return noises[_.indexOf(yVals, y)] }
_.forEach(cols, (c) => {
let lineLengths = createLinesWithShape(c, size[1], tri.data, range)
lineLengths.forEach((l,i) => {
// looks up our noise.
let n = getNoise(l[0])
sketch.line(xScale(c) + n, l[0], xScale(c) + n, l[1])
})
})
}
})
}
Insert cell
import {perlin2, perlin3} from "@mbostock/perlin-noise"
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