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

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