Public
Edited
Feb 14
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
regenerate;

const width = 640,
height = 640,
image = await (upload ?? FileAttachment("bear.png")).image(),
imageData = getImageDate(image),
color = imageColor(imageData, width, height),
position = cm.randomNoise(3, 8),
rotate = cm.randomNoise(0, Math.PI * 2 * width),
pack = d3.pack().size([width, height]).padding(3),
random = d3.randomUniform(0, 1000),
randomEase = d3.randomInt(0, eases.length),
data = {
name: "0",
children: Array.from({ length: count }, (d) => ({
name: `0-${d}`,
value: random()
}))
},
root = pack(d3.hierarchy(data).sum((d) => d.value)),
circles = root.leaves(),
duration1 = 1500,
duration2 = 1000;

const node = SVG.svg({ width, height }, [
...circles.map((d) => {
const [r, g, b, a] = color(d.x, d.y);
const fill = `rgb(${r}, ${g}, ${b}, ${a})`;
return cm.transition(
{
keyframes: [
{
attr: { r: d.r },
delay: 500,
duration: duration1 - 500,
ease: eases[randomEase()]
}
]
},
[SVG.circle({ cx: d.x, cy: d.y, r: 0, fill })]
);
}),
...circles.map((d) => {
const [r, g, b, a] = color(d.x, d.y);
const gray = Math.round((r + g + b) / 3);
const codePoint = offset + gray;
return cm.transition(
{
keyframes: [
{ attr: { opacity: 1 }, delay: duration1, duration: duration2 }
]
},
[
character({
text: String.fromCodePoint(codePoint),
opacity: 1,
x: d.x,
y: d.y,
fill: "black",
opacity: 0,
fill: fontColor,
height: d.r * 2,
"font-weight": "900",
dy: "0.3em",
dx: "-0.3em",
transform: `rotate(${rotate(d.x / width, d.y / height)})`
})
]
);
})
]);

const sentences = generatePoem(node.textContent);

return HTML.div({ style: "display:flex" }, [
node,
HTML.article(
sentences.map((d, i) => {
const wait = 750;
const delay = duration1 + duration2;
return cm.transition(
{
keyframes: [
{ style: { opacity: 1 }, delay: delay + wait * i, duration: wait }
]
},
[
HTML.p(
{
style: [
"font-weight:bold",
"font-size:20px",
"margin-left: 2.5em",
"opacity: 0"
].join(";")
},
[d]
)
]
);
})
)
]);
}
Insert cell
function generateGroups(array, max = 10) {
let group = [];
const groups = [];
for (const item of array) {
const t = (max - group.length) / max;
if (t > Math.random()) group.push(item);
else {
groups.push(group);
group = [];
}
}
if (group.length) groups.push(group);
return groups;
}
Insert cell
function generatePoem(string) {
const words = generateGroups(string.split("")).map((d) => d.join(""));
const sentences = generateGroups(words, 6).map((d) => d.join(" "));
return sentences;
}
Insert cell
eases = Object.entries(d3)
.filter(([k]) => k.startsWith("ease"))
.map(([, v]) => v)
Insert cell
function character({
text,
x = 0,
y = 0,
width,
height,
transform,
dx,
dy,
...style
}) {
const [w, h] = measureText(text, { fontWeight: style["font-weight"] });
let scaleX = width === undefined ? height / h : width / w;
let scaleY = height === undefined ? width / w : height / h;
if (width === undefined && height === undefined) (scaleX = 1), (scaleY = 1);
return SVG.g(
{
transform: `translate(${x}, ${y}) scale(${scaleX}, ${scaleY})`,
...style
},
[SVG.text({ dy: dy ?? "1em", dx, transform }, [text])]
);
}
Insert cell
function measureText(text, options) {
function createSpan(text) {
const span = document.createElement("span");
span.style.position = "absolute";
span.style.visibility = "hidden";
span.style.whiteSpace = "pre";
for (const [key, value] of Object.entries(options)) {
span.style[key] = value;
}
span.textContent = text;
return span;
}

const span = createSpan(text);
document.body.appendChild(span);
const { width, height } = span.getBoundingClientRect();
document.body.removeChild(span);
return [width, height];
}
Insert cell
function getImageDate(image) {
const { width, height } = image;
const context = DOM.context2d(width, height);
context.drawImage(image, 0, 0, width, height);
const ratio = window.devicePixelRatio;
return context.getImageData(0, 0, width * ratio, height * ratio);
}
Insert cell
function imageColor(imageData, width, height) {
const { data, width: imageWidth, height: imageHeight } = imageData;
const scaleX = d3.scaleLinear([0, width], [0, imageWidth]);
const scaleY = d3.scaleLinear([0, height], [0, imageHeight]);
return (x0, y0) => {
const x = Math.round(scaleX(x0));
const y = Math.round(scaleY(y0));
const i = x + y * imageWidth;
const r = data[i * 4];
const g = data[i * 4 + 1];
const b = data[i * 4 + 2];
const a = data[i * 4 + 3];
return [r, g, b, a];
};
}
Insert cell
HTML = cm.HTML
Insert cell
SVG = cm.SVG
Insert cell
cm = ({ ...(await require("charmingjs@0.0.9")), ...extension })
Insert cell
import { extension } from "@charming-art/charming-extension"
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