Published
Edited
Nov 22, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
y: {
grid: true,
label: "↑ Daily temperature range (°F)"
},
marks: [
Plot.areaY(temp, {
x: "date",
y1: "low",
y2: "high",
curve: "step",
fill: "#ccc"
}),
Plot.line(
temp,
Plot.windowY({
k,
anchor,
x: "date",
y: "low",
curve: "step",
stroke: "blue"
})
),
Plot.line(
temp,
Plot.windowY({
k,
anchor,
x: "date",
y: "high",
curve: "step",
stroke: "red"
})
)
]
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
htl.html`${["min", "median", "max", "difference"].map((reduce) =>
Plot.plot({
caption: `Reducer: ${reduce}`,
y: { grid: true },
marks: [
Plot.line(
temp,
Plot.windowY({
anchor,
reduce,
k,
x: "date",
y: "middle"
})
)
],
height: 250
})
)}`
Insert cell
Insert cell
{
const reduce = "mode";
const median = d3.median(temp, (d) => d.middle);
return Plot.plot({
caption: `Reducer: ${reduce}`,
marks: [
Plot.tickX(
temp,
Plot.windowY({
anchor,
reduce,
k,
x: "date",
y: (d) => Math.sign(d.middle - median)
})
)
],
height: 100,
y: { domain: [-1, 1], tickFormat: (d) => (d < 0 ? "lower" : "higher") }
});
}
Insert cell
Insert cell
{
const categoricalWindowY = ({ anchor, k, reduce = d3.mean, ...options }) =>
Plot.map(
{
y: (v) => {
const R = Array.from(v, () => null);
const a =
anchor === "start" ? 0 : anchor === "end" ? k - 1 : (k - 1) >> 1;
for (let i = 0; i + k < v.length; i++) {
R[a + i] = reduce(v.slice(i, i + k + 1));
}
return R;
}
},
options
);

const median = d3.median(temp, (d) => d.middle);

return Plot.plot({
caption: `Custom map`,
marks: [
Plot.tickX(
temp,
categoricalWindowY({
anchor: "start",
reduce: d3.mode,
k,
x: "date",
y: (d) => (d.middle < median ? "lower" : "higher")
})
)
],
// Here I purposefully didn't remove NaN from the y domain, a situation which happens
// at the beginning and end of the x domain; uncomment the following line to see the difference
// y: { domain: ["lower", "higher"] },
height: 100
});
}
Insert cell
Insert cell
Plot.plot({
caption: `Reducer: deviation`,
marks: [
Plot.areaX(
temp,
Plot.stackX(
Plot.windowX({
anchor,
reduce: "deviation",
k: 20,
y: "date",
x: "middle",
offset: "center",
fill: "brown"
})
)
)
],
marginLeft: 150,
height: 250,
x: { axis: null },
y: { ticks: 12, tickFormat: d => Plot.formatMonth()(d.getMonth()) }
})
Insert cell
Insert cell
bollinger = (K) => (values) => d3.mean(values) + K * d3.deviation(values)
Insert cell
Plot.plot({
y: {
nice: true,
grid: true,
domain: d3.extent(temp, (d) => d.middle)
},
marks: [
Plot.lineY(
temp,
Plot.windowY({
k: days,
reduce: bollinger(-K),
x: "date",
y: "middle",
curve: "basis",
stroke: "#ccc"
})
),
Plot.lineY(
temp,
Plot.windowY({
k: days,
reduce: bollinger(K),
x: "date",
y: "middle",
curve: "basis",
stroke: "#ccc"
})
),
Plot.dot(temp, {
x: "date",
y: "middle",
fill: "black",
r: 1.5
})
]
})
Insert cell
Insert cell
Insert cell
Insert cell
Plot.plot({
y: {
nice: true,
grid: true,
domain: d3.extent(temp, (d) => d.middle)
},
marks: [
Plot.areaY(
temp,
Plot.map(
{
y1: Plot.window({ k: days, reduce: bollinger(-K) }),
y2: Plot.window({ k: days, reduce: bollinger(K) })
},
{
x: "date",
y: "middle",
curve: "basis",
fill: "#ccc"
}
)
),
Plot.dot(temp, {
x: "date",
y: "middle",
fill: "black",
r: 1.5
})
]
})
Insert cell
Insert cell
{
// the reference point inside a window depends on the anchor
const ref =
anchor === "start" ? 0 : anchor === "end" ? days - 1 : (days - 1) >> 1;

return Plot.plot({
y: {
nice: true,
grid: true,
domain: d3.extent(temp, (d) => d.middle)
},
color: {
domain: [false],
range: ["orange"]
},
marks: [
Plot.areaY(
temp,
Plot.map(
{
y1: Plot.window({
k: days,
anchor,
reduce: bollinger(-K)
}),
y2: Plot.window({
anchor,
k: days,
reduce: bollinger(K)
})
},
{
x: "date",
y: "middle",
fill: "#eef9ff",
curve: "basis"
}
)
),

Plot.dot(
temp,
// color dots according to their being an outlier of the bollinger band
Plot.map(
{
fill: Plot.window({
anchor,
k: days,
reduce: (V) =>
Math.abs(V[ref] - d3.mean(V)) <= K * d3.deviation(V)
})
},
{
x: "date",
y: "middle",
z: null, // avoids grouping on fill
fill: "middle"
}
)
),
Plot.line(temp, {
x: "date",
y: "middle",
strokeWidth: 0.5
})
],
width
});
}
Insert cell
Insert cell
{
// add fake data gaps for demo purposes
const tempWithGaps = temp.slice(0,100)
.map((d, i) => ({...d, valueOrGap: [22, 53, 60, 87].includes(i) ? NaN : d.middle}));
const customWindowY = ({ anchor, k, reduce = d3.mean, ...options }) =>
Plot.map(
{
y: (v) => {
const R = Array.from(v, () => null);
const a =
anchor === "start" ? 0 : anchor === "end" ? k - 1 : (k - 1) >> 1;
for (let i = -a; i + k -a < v.length; i++) {
R[a + i] = reduce(v.slice(Math.max(0, i), i + k + 1));
}
return R;
}
},
options
);

return Plot.plot({
caption: `Custom map, ignoring gaps`,
marks: [
Plot.dot(
tempWithGaps,
{
x: "date",
y: d => d.valueOrGap || 43.5,
fill: d => !!d.valueOrGap,
r: 2.5
}
),

Plot.line(
tempWithGaps,
customWindowY({
k : 5,
x: "date",
y: "valueOrGap",
stroke: "orange",
strokeWidth: 4
})
),
Plot.line(
tempWithGaps,
Plot.windowY({
k : 5,
x: "date",
y: "valueOrGap",
stroke: "brown",
strokeWidth: 3
})
),

],
color: { domain: [false, true], range: ["brown", "grey"] },
height: 250
});
}
Insert cell
Insert cell
Insert cell
{
let options = {x: "date", y: "middle", k: 28};
if (shuffle_sort) options = Plot.sort("date", options);
return Plot.lineY(shuffled, Plot.windowY(options)).plot({height: 250});
}
Insert cell
shuffled = d3.shuffle(temp.slice())
Insert cell
temp = FileAttachment("temp.csv").csv({typed: true})
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