Published
Edited
Mar 9, 2020
2 forks
16 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
let height = 300;
let ctx = DOM.context2d(width, height);
const heightAmplifier = 100;

const xDelta = 1;
const yDelta = 2;
// draw from bottom to top
for (let y = height; y >= 0; y -= yDelta) {
ctx.beginPath();
ctx.moveTo(0, y);
// from left to right
for (let x = 0; x < width; x += xDelta) {
const heightAtX = heightAt(x, y) * heightAmplifier;
// by default we'll draw this line segment
let draw = true;
/*
Check the `n` lines below this one. On the Y axis, this line is at `y`.
The line below is therefore at `y + yDelta`.
*/
for (let n = 1; n <= 20; n++) {
const yBelowOffset = n * yDelta;
if (y + yBelowOffset > height) {
break; // 'y' is greater than the canvas height, it would be off canvas
}
// Get the height of the Nth line at the same `x`
const belowLineHeight = heightAt(x, y + yBelowOffset) * heightAmplifier;
/*
Now for the tricky conditional. Remember that a higher Y means lower on the Y axis:
y
x 0 1 2 3 4 5 …
1
2
3
Since we draw the hills by *adding* to `y`, we check that the height we are currently
considering drawing is *greater* than the height of a line below. If it is, it would
be "behind" (below) some other line ; we don't want it so we exit.
To put it differently, if we were drawing horizontal straight lines the current point
would be (x,y). To create the 'hills' we do (x,y+height). Since height > 0 and because
a greater y means 'closer to the bottom of the canvas', y+height is 'lower' than y.
We avoid drawing a 'lower' point behind a 'higher' point.
*/
if (heightAtX > yBelowOffset + belowLineHeight) {
draw = false;
break;
}
}
if (draw) {
// Well, draw!
ctx.lineTo(x, y + heightAtX);
} else {
// Move to where we would have drawn, we'll pick up from there at the next iteration
// on the x axis.
ctx.moveTo(x, y + heightAtX);
}
}
ctx.stroke();
}
return ctx.canvas;
}
Insert cell
Insert cell
Insert cell
{
let height = 300;
let ctx = DOM.context2d(width, height);
const heightAmplifier = 100;
const segments = [];
const coils = 200;
const rotation = 0;
const thetaMax = coils * 2 * Math.PI;
const chord = 1;
const center = [width / 2, height * 1.5];
let radius = 400;
const step = radius / thetaMax;
let lastVertice;

for (let i = 0, theta = chord / step; theta <= thetaMax; i++) {
const away = step * theta;
const around = theta + rotation;
radius += 0.05;
let x = center[0] + Math.cos(around) * away;
let y = center[1] + Math.sin(around) * away;

let h = heightAt(x, y) * heightAmplifier;
x += Math.cos(around);
y += h + Math.sin(around);
if (i > 0 && y < height + 5) {
segments.push({
x0: lastVertice.x,
y0: lastVertice.y,
x: x,
y: y
});
}
lastVertice = { x, y, h };
theta += chord / away;
}

const minX = segments.reduce(
(acc, segment) => Math.min(acc, segment.x0),
Infinity
);
const maxX = segments.reduce(
(acc, segment) => Math.max(acc, segment.x0),
-Infinity
);
const minY = segments.reduce(
(acc, segment) => Math.min(acc, segment.y0),
Infinity
);
const maxY = segments.reduce(
(acc, segment) => Math.max(acc, segment.y0),
-Infinity
);

const layersX = [];
const range = (maxX - minX) * (_bucketSize / 10000);
console.log({
range,
maxX,
minX,
maxY,
minY
});

// let's draw a single bucket around top/left
ctx.fillStyle = 'blue';
ctx.fillRect(minX, minY, range, range);
ctx.stroke();

// file segments into layers/buckets
segments.forEach(segment => {
const midX = (segment.x0 + segment.x) / 2;
const indexX = Math.round((midX - minX) * range);
if (!(indexX in layersX)) {
layersX[indexX] = [];
}
const midY = (segment.y0 + segment.y) / 2;
const indexY = Math.round((midY - minY) * range);
if (!(indexY in layersX[indexX])) {
layersX[indexX][indexY] = [];
}
layersX[indexX][indexY].push(segment);
});

layersX.forEach((layerX, x) => {
layerX.forEach((bucketXY, y) => {
// let's find a few buckets below this one
const bucketsBelow = [...Array(10).keys()]
.map(i => layerX[y + i + 1])
.filter(Boolean);
if (bucketsBelow.length > 2) {
ctx.strokeStyle = 'green';
} else {
ctx.strokeStyle = 'black';
}
ctx.beginPath();
for (const segment of bucketXY) {
ctx.lineTo(segment.x0, segment.y0);
ctx.lineTo(segment.x, segment.y);
}
ctx.stroke();
});
});

return ctx.canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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