Skip to content

Instantly share code, notes, and snippets.

@tophtucker
Last active March 25, 2021 09:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tophtucker/75552051226e558e0533d6493526ae19 to your computer and use it in GitHub Desktop.
Save tophtucker/75552051226e558e0533d6493526ae19 to your computer and use it in GitHub Desktop.
Wordwrap zigzag
<!DOCTYPE html>
<meta charset="utf-8">
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
span {
display: inline-block;
}
article > div.line {
white-space: nowrap;
transform-origin: left;
position: absolute;
top: 0;
left: 0;
}
svg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
</style>
<body>
<svg></svg>
<article></article>
</body>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
const seed = Date.now()
// const text = `Look not too long in the face of the fire, O man! Never dream with thy hand on the helm! Turn not thy back to the compass; accept the first hint of the hitching tiller`;
const text = `Look not too long in the face of the fire, O man! Never dream with thy hand on the helm! Turn not thy back to the compass; accept the first hint of the hitching tiller; believe not the artificial fire, when its redness makes all things look ghastly. Tomorrow, in the natural sun, the skies will be bright; those who glared like devils in the forking flames, the morn will show in far other, at least gentler, relief; the glorious, golden, glad sun, the only true lamp—all others but liars!`;
// const text = `Look not too long in the face of the fire, O man! Never dream with thy hand on the helm! Turn not thy back to the compass; accept the first hint of the hitching tiller; believe not the artificial fire, when its redness makes all things look ghastly. Tomorrow, in the natural sun, the skies will be bright; those who glared like devils in the forking flames, the morn will show in far other, at least gentler, relief; the glorious, golden, glad sun, the only true lamp—all others but liars! Nevertheless the sun hides not Virginia’s Dismal Swamp, nor Rome’s accursed Campagna, nor wide Sahara, nor all the millions of miles of deserts and of griefs beneath the moon. The sun hides not the ocean, which is the dark side of this earth, and which is two thirds of this earth. So, therefore, that mortal man who hath more of joy than sorrow in him, that mortal man cannot be true—not true, or undeveloped. With books the same. The truest of all men was the Man of Sorrows, and the truest of all books is Solomon’s, and Ecclesiastes is the fine hammered steel of woe. “All is vanity”. ALL. This wilful world hath not got hold of unchristian Solomon’s wisdom yet. But he who dodges hospitals and jails, and walks fast crossing grave-yards, and would rather talk of operas than hell; calls Cowper, Young, Pascal, Rousseau, poor devils all of sick men; and throughout a care-free lifetime swears by Rabelais as passing wise, and therefore jolly;—not that man is fitted to sit down on tombstones, and break the green damp mould with unfathomably wondrous Solomon. But even Solomon, he says, “the man that wandereth out of the way of understanding shall remain” (i.e. even while living) “in the congregation of the dead”. Give not thyself up, then, to fire, lest it invert thee, deaden thee; as for the time it did me. There is a wisdom that is woe; but there is a woe that is madness. And there is a Catskill eagle in some souls that can alike dive down into the blackest gorges, and soar out of them again and become invisible in the sunny spaces. And even if he for ever flies within the gorge, that gorge is in the mountains; so that even in his lowest swoop the mountain eagle is still higher than other birds upon the plain, even though they soar.`;
const data = text.split(" ").map(text => ({
text: `${text}&nbsp;`
}));
const article = d3.select("article");
const svg = d3.select("svg").append("g");
const sel = article
.selectAll("span")
.data(data)
.join("span")
.html(d => d.text)
.each(function(d) {
d.width = this.offsetWidth;
})
.remove();
// .style("position", "absolute")
// .style("white-space", "nowrap");
const cumsum = [0, ...d3.cumsum(data, d => d.width).slice(0, -1)];
for (let [i, d] of Object.entries(data)) {
d.i = i;
d.cumx = cumsum[i];
}
function render() {
const rand = d3.randomLcg(seed);
const w = innerWidth / 2;
const h = innerHeight / 2;
const cornerAngles = [
[-w, -h],
[w, -h],
[w, h],
[-w, h]
].map(([x, y]) => Math.atan2(y, x))
function getRandomAngle() {
return (rand() - 0.5) * Math.PI * 2;
}
function getRandomPerimenterPoint() {
const angle = getRandomAngle();
if (angle < cornerAngles[0] || angle >= cornerAngles[3]) {
return [-w, -w * Math.tan(angle)];
} else if (angle < cornerAngles[1]) {
return [-h / Math.tan(angle), -h];
} else if (angle < cornerAngles[2]) {
return [w, w * Math.tan(angle)];
} else if (angle < cornerAngles[3]) {
return [h / Math.tan(angle), h];
}
}
function getRandomLine() {
const a = getRandomPerimenterPoint();
const b = getRandomPerimenterPoint();
const dist = Math.sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2);
const angle = Math.atan2(b[1] - a[1], b[0] - a[0]);
if (angle === 0 || angle === Math.PI) return getRandomLine();
return {a, b, dist, angle};
}
article
.style("transform", `translate(${w}px, ${h}px)`)
.selectAll("div.line")
.remove();
svg
.attr("transform", `translate(${w}, ${h})`)
.selectAll("path")
.remove();
let dist = 0;
let line;
while (dist < d3.max(data, d => d.cumx)) {
line = getRandomLine(rand);
const lineData = data.filter(d => d.cumx >= dist && d.cumx <= dist + line.dist);
// include the preceding word and offset the line accordingly
let dx, dy;
if (+lineData[0].i) {
lineData.unshift(data[lineData[0].i - 1]);
const offset = lineData[0].cumx - dist;
dx = offset * Math.cos(line.angle);
dy = offset * Math.sin(line.angle);
} else {
dx = 0;
dy = 0;
}
article.append("div")
.attr("class", "line")
.style("transform", `translate(${line.a[0] + dx}px, ${line.a[1] + dy}px) rotate(${line.angle}rad)`)
// .style("padding-left", `${lineData[0].cumx - dist}px`) // alt to including preceding word
.selectAll("span")
.data(lineData)
.join("span")
.html(d => d.text);
// svg.append("path")
// .attr("stroke", "gray")
// .attr("d", `M ${line.a} ${line.b}`)
dist += line.dist;
}
}
render();
window.addEventListener("resize", render)
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment