Public
Edited
Jun 2, 2023
5 forks
Importers
27 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
width: dataWidth + marginLeft + marginRight,
marginLeft,
marginRight,
y: { grid: true },
marks: [
Plot.line(randomWalk(500), { x: 't', y: 'v', stroke: 'steelblue' })
]
})
Insert cell
Insert cell
data = randomWalk(1e6)
Insert cell
Insert cell
Insert cell
Insert cell
// Plot.plot({
// width: dataWidth + marginLeft + marginRight,
// marginLeft,
// marginRight,
// y: { grid: true },
// marks: [
// Plot.line(data, { x: 't', y: 'v', stroke: 'steelblue' })
// ]
// })
Insert cell
Insert cell
Insert cell
Plot.plot({
width: dataWidth + marginLeft + marginRight,
marginLeft,
marginRight,
y: { grid: true },
marks: [
Plot.line(minmaxData, { x: 't', y: 'v', stroke: 'steelblue' })
]
})
Insert cell
minmaxData = minmax(data, dataWidth, 't', 'v')
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
width: dataWidth + marginLeft + marginRight,
marginLeft,
marginRight,
y: { grid: true },
marks: [
Plot.line(m4Data, { x: 't', y: 'v', stroke: 'steelblue' })
]
})
Insert cell
m4Data = m4(data, dataWidth, 't', 'v')
Insert cell
Insert cell
/**
* M4 aggregation algorithm.
* Reduce time series data in a visualization-preserving way.
*/
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
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