Public
Edited
Sep 9, 2023
Importers
Insert cell
Insert cell
function createVideo({ width = 500, height = 375, scaleX = 1, scaleY = 1, id = "", src = "" } = { }) {
return html`
<video
id="${id}"
src="${src}"
autoplay="true"
style="width: ${width}px; height: ${height}px; background-color: #666; transform: scale(${scaleX}, ${scaleY});"
>
</video>`
}
Insert cell
function attachWebcam(videoElement) {
if (navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
videoElement.srcObject = stream;
})
.then(() => "Webcam stream attached")
}

return Promise.reject("Navigator does not support mediaDevices");
}
Insert cell
function createImage({ src, id = "" } = {}) {
return html`<img id="${id}" src="${src}" />`
}
Insert cell
createChart = require('@observablehq/vega-lite')
Insert cell
function createButton({ text = "" } = {}) {
const button = html`<button></button>`;
button.style.width = "150px";
button.style.height = "40px";
button.style.backgroundImage = "linear-gradient(#f3f3f3, #efefef)";
button.style.borderRadius = "4px";
button.style.marginBottom = "8px";
button.innerHTML = `<span style="font-size:14px">${text}</span>`;
return button;
}
Insert cell
function createML5(version = "0.12.2") {
return require(`https://unpkg.com/ml5@${version}/dist/ml5.min.js`)
}
Insert cell
import { createP5 } from "@bberak/p5"
Insert cell
p5 = createP5()
Insert cell
function observe(element, eventName, transformer, options) {
return Generators.observe(notify => {
const handler = e => {
if (transformer) notify(transformer(e));
else notify(e);
};

element.addEventListener(eventName, handler, options);

return () => element.removeEventListener(eventName, handler);
})
}
Insert cell
function observeMouseover(element, transformer, options) {
return observe(element, 'mouseover', transformer, options);
}
Insert cell
function observeMousemove(element, transformer, options) {
return observe(element, 'mousemove', transformer, options);
}
Insert cell
function observeMousedown(element, transformer, options) {
return observe(element, 'mousedown', transformer, options);
}
Insert cell
function observeClick(element, transformer, options) {
return observe(element, 'click', transformer, options);
}
Insert cell
function observeKeydown(element, transformer, options) {
return observe(element, 'keydown', transformer, options);
}
Insert cell
function observeKeyup(element, transformer, options) {
return observe(element, 'keyup', transformer, options);
}
Insert cell
function observeKeypress(element, transformer, options) {
return observe(element, 'keypress', transformer, options);
}
Insert cell
function listen(element, eventName, handler, options) {
element.addEventListener(eventName, handler, options);

const unsubscribe = () => {
element.removeEventListener(eventName, handler);
};

invalidation.then(unsubscribe);
return unsubscribe;
}
Insert cell
function listenMouseover(element, handler, options) {
return listen(element, 'mouseover', handler, options);
}
Insert cell
function listenMousemove(element, handler, options) {
return listen(element, 'mousemove', handler, options);
}
Insert cell
function listenMousedown(element, handler, options) {
return listen(element, 'mousedown', handler, options);
}
Insert cell
function listenClick(element, handler, options) {
return listen(element, 'click', handler, options);
}
Insert cell
function listenKeydown(element, handler, options) {
return listen(element, 'keydown', handler, options);
}
Insert cell
function listenKeyup(element, handler, options) {
return listen(element, 'keyup', handler, options);
}
Insert cell
function listenKeypress(element, handler, options) {
return listen(element, 'keypress', handler, options);
}
Insert cell
/**
* M4 aggregation algorithm.
* Reduce time series data in a visualization-preserving way.
* Source: https://observablehq.com/@uwdata/m4-scalable-time-series-visualization
*/
function m4(
data, // input data
width, // chart width in pixels
time = d => d[0], // time accessor, defaults to [0]
value = d => d[1] // value accessor, defaults to [1]
) {
// update accessors as needed
if (typeof time === 'string') {
const tfield = time;
time = d => d[tfield];
}
if (typeof value === 'string') {
const vfield = value;
value = d => d[vfield];
}

// ensure data is a proper array
data = Array.isArray(data) ? data : Array.from(data);
const n = data.length;
// determine time range
let tmin = time(data[0]);
let tmax = tmin;
for (let i = 0; i < n; ++i) {
const t = time(data[i]);
if (t < tmin) tmin = t;
if (t > tmax) tmax = t;
}
const delta = tmax - tmin;

// map a timestamp to an integer pixel coordinate
const key = t => Math.round(width * (t - tmin) / delta);

// compute min/max time and min/max value (M4!) per pixel column
const agg = Array(width);
for (let i = 0; i < n; ++i) {
const d = data[i];
const t = time(d);
const v = value(d);
const k = key(t);
if (agg[k] == null) {
agg[k] = {
tmin: t, tmin_i: i,
tmax: t, tmax_i: i,
vmin: v, vmin_i: i,
vmax: v, vmax_i: i,
};
} else {
const a = agg[k];
if (t < a.tmin) { a.tmin = t; a.tmin_i = i; }
if (t > a.tmax) { a.tmax = t; a.tmax_i = i; }
if (v < a.vmin) { a.vmin = v; a.vmin_i = i; }
if (v > a.vmax) { a.vmax = v; a.vmax_i = i; }
}
}

// filter original data to extremal (M4) values only
return data.filter((d, i) => {
const t = time(d);
const k = key(t);
const a = agg[k];
return i === a.tmin_i || i === a.tmax_i || i === a.vmin_i || i === a.vmax_i;
});
}
Insert cell
function timeSeriesChart({
lines = [],
page = 0,
limit = 1000,
width = innerWidth,
height = undefined
}) {
const transformedData = lines.map(l => {
let start = Math.trunc(page * limit);
let end = start + limit;

if (start > l.data.length || end > l.data.length) {
start = l.data.length - limit;
end = l.data.length;
}
return m4(l.data.slice(start, end), width)
});
const defaultColor = 'black';
return Plot.plot({
width,
height,
x: {
label: "Time",
grid: true
},
y: {
axis: "right",
grid: true,
nice: true
},
marks: [
...transformedData.map((data, idx) => Plot.line(data, { stroke: lines[idx].color })),
...transformedData.map((data, idx) => Plot.dot(data, Plot.pointerX({
fill: lines[idx].color || defaultColor,
stroke: lines[idx].color || defaultColor
}))),
...transformedData.map((data, idx) => Plot.text(data, Plot.pointerX({
px: "0",
py: "1",
dy: -17,
dx: 120 * idx,
frameAnchor: "top-left",
fontVariant: "tabular-nums",
fontWeight: 'bold',
text: d => `${lines[idx].label || ''}${d[1].toFixed(2)}`,
fill: lines[idx].color || defaultColor
})))
]
})
}
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