Unlisted
Edited
Oct 7, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const app = cm.app({
width: 600,
height: 200
});

// Clears background, and
// draws a circle at the mouse position,
// defaults to center of the canvas.
app
.on("update", () => {
app.append(cm.clear, { fill: "white" });
})
.on("update", () => {
app.append(cm.circle, {
x: app.prop("mouseX") || app.prop("width") / 2,
y: app.prop("mouseY") || app.prop("height") / 2,
r: 30,
fill: "black"
});
});

// Diposes app when rerunning cell.
function dispose(app) {
invalidation.then(() => app.dispose());
}

return app.start().call(dispose).call(frame).node();
}
Insert cell
Insert cell
{
const width = 600,
height = 200,
particles = [];

function update(app) {
// Appends clear shape to clear background.
app.append(cm.clear, { fill: cm.rgb(255) });

app
.data(particles) // Creates flows and places data.
.process(cm.push, create) // Updates particles' state.
.process(cm.eachRight, remove)
.process(cm.each, age)
.process(cm.each, move)
.append(cm.circle, {
// Binds particles with circles.
x: (d) => d.location.x,
y: (d) => d.location.y,
r: 5,
fill: cm.rgb(0),
stroke: cm.rgb(0),
fillOpacity: (d) => d.lifespan,
strokeOpacity: (d) => d.lifespan
})
.transform(cm.mapAttrs, {
// Map abstract values to visual values by scales.
fillOpacity: { domain: [0, 255], range: [0, 0.6] },
strokeOpacity: { domain: [0, 255], range: [0, 1] }
});
}

function create(d, i, data, flow) {
const app = flow.app();
return {
location: cm.vec(app.prop("width") / 2, 50),
velocity: cm.vec(cm.random(-1, 1), cm.random(-2, 0)),
acceleration: cm.vec(0, 0.05),
lifespan: 255
};
}

function remove(d, i, array) {
d.lifespan < 0 && array.splice(i, 1);
}

function age(d) {
d.lifespan -= 2;
}

function move(d) {
d.velocity.add(d.acceleration);
d.location.add(d.velocity);
}

// Diposes app when rerunning cell.
function dispose(app) {
invalidation.then(() => app.dispose());
}

return cm
.app({ width, height })
.on("update", update)
.call(dispose)
.call(frame)
.start()
.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 700,
height = 700,
scale = 300,
theta = cm.range(count, 0, cm.TWO_PI);

function update(app) {
const time = app.prop("frameCount") / 50;

app.append(cm.clear, { fill: "black" });

app
.data(theta) // Bind Data.
.append(cm.circle, {
// Define some glsl attributes and interpolate some values.
position: cm.glsl`vec2 position(float theta) {
vec2 xy = vec2(
cos(theta),
sin(theta)) * (0.6 + 0.2 * cos(theta * 6.0 + cos(theta * 8.0 + ${time}))
);
return xy * ${scale} + vec2(${width / 2}, ${height / 2});
}`,
r: cm.glsl`float r(float theta) {
float d = 0.2 + 0.12 * cos(theta * 9.0 - ${time} * 2.0);
return d * ${scale};
}`,
stroke: cm.glsl`vec4 stroke(float theta) {
float th = 8.0 * theta + ${time} * 2.0;
vec3 rgb = 0.6 + 0.4 * vec3(
cos(th - ${Math.PI} * 2.0 / 3.0),
cos(th),
cos(th - ${Math.PI} * 5.0 / 3.0)
);
return vec4(rgb, 0.0);
}`,
strokeOpacity: cm.glsl`float strokeOpacity(float theta) {
return 0.15 * 2000.0 / ${count};
}`
});
}

// Diposes app when rerunning cell.
function dispose(app) {
invalidation.then(() => app.dispose());
}

return cm
.app({
width,
height,
renderer: cm.webgl() // Uses WebGL renderer.
})
.on("update", update)
.call(stats)
.call(dispose)
.start()
.node();
}
Insert cell
Insert cell
{
const width = 640;
const height = 360;
const palette = cm.glsl`vec3 palette(float t) {
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.263, 0.416, 0.557);
return a + b * cos(3.1415926 * 2.0 * (c * t + d));
}`;

function update(app) {
const time = app.prop("frameCount") / 50;
const fill = cm.glsl`vec4 fill(vec2 coord, vec4 color) {
vec2 uv = (coord - vec2(${width}, ${height})) / ${height};
vec2 uv0 = uv;
vec3 rgb = vec3(0.0);
for (float i = 0.0; i < 4.0; i++) {
uv = fract(uv * 1.5) - 0.5;
float d = length(uv) * exp(-length(uv0));
vec3 col = ${palette}(length(uv0) + i * 0.4 + ${time} * 0.4);
d = sin(d * 8.0 + ${time}) / 8.0;
d = abs(d);
d = pow(0.01 / d, 1.2);
rgb += col * d;
}
return vec4(rgb, 1.0);
}`;
app.append(cm.rect, { x: 0, y: 0, width, height, fill });
}

// Diposes app when rerunning cell.
function dispose(app) {
invalidation.then(() => app.dispose());
}

return cm
.app({
renderer: cm.webgl(), // Uses WebGL renderer.
width,
height
})
.on("update", update)
.call(dispose)
.start()
.node();
}
Insert cell
Insert cell
{
let strings = null;

function update(app) {
const width = app.prop("width");
const height = app.prop("height");
if (!strings) strings = cm.range(width).map(() => createString(height));

app.append(cm.clear, { fill: "black" });

app
.data(strings)
.process(cm.eachRight, updateString)
.append(cm.group, {
x: (_, i) => i,
y: (d) => d.y
})
.data((d) => d.chars)
.append(cm.point, {
x: 0,
y: (_, i) => i,
stroke: (d) => cm.cfb(d, "#6EBD41")
});
}

function createString(height) {
const lifespan = cm.randomInt(height);
const length = cm.randomInt(lifespan);
const chars = cm.range(length).map(cm.randomChar);
const y = cm.randomInt(0, 15);
return { lifespan, length, chars, y };
}

function updateString(d, i, array, flow) {
const app = flow.app();
const height = app.prop("height");
const { chars, lifespan, length } = array[i];
const curLength = chars.length;

// Create a new string if the current one is dead.
// Fade out the string if lifespan is less than the current length.
// Fade in the string if lifespan is greater than the current length.
if (lifespan < 0) array[i] = createString(height);
else if (lifespan <= curLength) chars[curLength - lifespan] = "";
else if (lifespan > curLength) {
for (let i = length - 1; i < curLength; i++) chars[i] = cm.randomChar();
chars.push(cm.randomChar());
}

d.lifespan--;
}

// Diposes app when rerunning cell.
function dispose(app) {
invalidation.then(() => app.dispose());
}

const app = cm.app({
renderer: await cm.terminal(), // Uses terminal renderer.
frameRate: 15,
fontWeight: "bold"
});

return app.on("update", update).call(dispose).start().node();
}
Insert cell
Insert cell
function arrow(flow, { length, x, y, rotate, angle, ...options }) {
const group = flow.append(cm.group, { x, y, rotate });
const l1 = length.map((d) => d / 2);
const l2 = length.map((d) => -d / 2);
group.append(cm.link, { x: l2, y: 0, x1: l1, y1: 0, ...options });
group.append(cm.link, {
x: 0,
y: 0,
x1: l1,
y1: 0,
rotate: angle,
transformOrigin: "end",
...options
});
group.append(cm.link, {
x: 0,
y: 0,
x1: l1,
y1: 0,
rotate: -angle,
transformOrigin: "end",
...options
});
}
Insert cell
Insert cell
{
const width = 640,
height = 240,
size = 16,
cols = width / size,
rows = height / size,
noise = cm.randomNoise(),
fields = cm
.cross(cm.range(cols), cm.range(rows))
.map(([x, y]) => ({ x, y, value: noise(y * 0.1, x * 0.1) }));

const app = cm.app({ width, height });

app
.data(fields)
.append(arrow, {
x: (d) => d.x * size + size / 2,
y: (d) => d.y * size + size / 2,
rotate: (d) => d.value,
angle: cm.constant(Math.PI / 6),
rotate: (d) => d.value,
stroke: (d) => d.value,
length: (d) => d.value
})
.transform(cm.mapAttrs, {
rotate: { range: [0, cm.TWO_PI] },
length: { range: [size * 0.3, size * 0.9] },
stroke: { interpolate: d3.interpolateViridis }
});

return app.render().node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const data = [1.9, 1.9, 1.6, 1.0, 0.4, 0.1];

// Dimensions.
const step = 3;
const marginX = 3;
const marginY = 3;
const plotWidth = 60;
const plotHeight = step * data.length;
const height = plotHeight + marginY * 2;
const width = plotWidth + marginX * 2;

const app = cm.app({
renderer: await cm.terminal(),
cols: width,
rows: height
});

// Bar Chart.
app
.append(cm.group, { x: marginX, y: marginY })
.data(data)
.append(barX, {
x: (d) => d,
y: (_, i) => i,
step: cm.constant(step),
width: cm.constant(plotWidth),
height: cm.constant(plotHeight),
title: cm.constant("Terminal Bar Chart")
});

// Annotation.
app.append(cm.text, {
x: marginX + plotWidth,
y: marginY + plotHeight,
fontSize: "large",
text: cm.figlet("2023"),
textBaseline: "bottom",
textAlign: "left",
fill: cm.gradientSineBowX()
});

return app.render().node();
}
Insert cell
Insert cell
{
const app = cm.app({ width: 928, height: 500 });

app.data(alphabet).append(barY, {
x: (d) => d.letter,
y: (d) => d.frequency
});

return app.render().node();
}
Insert cell
Insert cell
Insert cell
{
const app = cm.app();
for (let i = 0; i < 500; i++) {
const x = cm.random(app.prop("width"));
const y = cm.random(app.prop("height"));
const radius = cm.randomInt(30);
const r = cm.randomInt(255);
const g = cm.randomInt(255);
const b = cm.randomInt(255);
app.append(cm.circle, {
x,
y,
r: radius,
fill: `rgb(${r}, ${g}, ${b})`
});
}
return app.render().node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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