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

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