Published
Edited
Sep 23, 2022
1 fork
12 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { data, advancedDate, industryDateMap } from "@observablehq/monthly-retail-trade-report-data"
Insert cell
// we define the limits of our data to be 2019 to the date of the latest report
dateExtent = [d3.utcParse("%Y-%m-%d")("1999-01-01"), d3.utcMonth.offset(advancedDate, 2)]
Insert cell
Insert cell
dataIndustries = data.filter(d => d.naics.indexOf("*") < 0 && d.date >= dateExtent[0] && d.date <= dateExtent[1])
Insert cell
advancedIndustries = Array.from(industryDateMap.keys()).filter(d => d.indexOf("*") < 0 && industryDateMap.get(d).get(advancedDate))
Insert cell
allIndustries = Array.from(industryDateMap.keys()).filter(d => d.indexOf("*") < 0)
Insert cell
dates = Array.from(new Set(dataIndustries.map(d => d.date)))
Insert cell
function pointIndex(index) {
return function (series) {
return series.map((d, i) => {
return (d / series[index] - 1);
});
};
}
Insert cell
percent = d3.format("+0.2%")
Insert cell
percentColor = (n) => textcolor(percent(n), { color: n > 0 ? 'darkgreen' : d3.schemeTableau10[2] })
Insert cell
dateFormat = d3.utcFormat("%b. %d, %Y")
Insert cell
monthFormat = d3.utcFormat("%B %Y")
Insert cell
recessionMarks = (max) => [
// recession annotations from https://www.nber.org/research/data/us-business-cycle-expansions-and-contractions
Plot.rect([{}], {
x1: d3.utcParse("%Y-%m-%d")("2020-02-01"),
x2: d3.utcParse("%Y-%m-%d")("2020-06-01"),
y1: 0,
y2: max,
stroke: "lightgray",
fillOpacity: 0.5,
clip: true
}),
Plot.text([{}], { text: d => "COVID-19 Recession", x: d3.utcParse("%Y-%m-%d")("2020-02-01"), dy: -15, frameAnchor: "top", textAnchor: "start", clip: true}),
Plot.rect([{}], {
x1: d3.utcParse("%Y-%m-%d")("2007-12-01"),
x2: d3.utcParse("%Y-%m-%d")("2009-06-01"),
y1: 0,
y2: max,
stroke: "lightgray",
fillOpacity: 0.5,
clip: true
}),
Plot.text([{}], { text: d => "2008 Recession", x: new Date("2007-12-01"), dy: -15, frameAnchor: "top", textAnchor: "start", clip: true}),
]
Insert cell
industryCategories = ({
"All other gen. merchandise stores": "General",
"Automobile and other motor vehicle dealers": "Auto",
"Automotive parts, acc., and tire stores": "Auto",
"Beer, wine and liquor stores": "Food",
"Building mat. and garden equip. and supplies dealers": "Home",
"Building mat. and supplies dealers": "Home",
"Clothing and clothing access. stores": "Fashion",
"Clothing stores": "Fashion",
"Department stores": "General",
"Electronic shopping and mail order houses": "Other",
"Electronics and appliance stores": "Home",
"Food and beverage stores": "Food",
"Food services and drinking places": "Food",
"Fuel dealers": "Auto",
"Furniture and home furnishings stores": "Home",
"Furniture, home furn, electronics, and appliance stores": "Home",
"Gasoline stations": "Auto",
"General merchandise stores": "General",
"Grocery stores": "Food",
"Health and personal care stores": "Other",
"Jewelry stores": "Fashion",
"Men's clothing stores": "Fashion",
"Miscellaneous stores retailers": "General",
"Motor vehicle and parts dealers": "Auto",
"Nonstore retailers": "Other",
"Other general merchandise stores": "General",
"Pharmacies and drug stores": "Other",
"Shoe stores": "Fashion",
"Sporting goods, hobby, musical instrument, and book stores": "Other",
"Warehouse clubs and superstores": "General",
"Women's clothing stores": "Fashion",
})
Insert cell
import { Plot } from "@mkfreeman/plot-tooltip"
Insert cell
import {textcolor} from "@observablehq/text-color-annotations-in-markdown"
Insert cell
// import {Scrubber} from "@mbostock/scrubber"
Insert cell
function Scrubber(values, {
format = value => value,
initial = 0,
delay = null,
autoplay = true,
loop = true,
loopDelay = null,
alternate = false
} = {}) {
values = Array.from(values);
const form = html`<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">
<button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>
<label style="display: flex; align-items: center;">
<input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 450px;">
<output name=o style="margin-left: 0.4em;"></output>
</label>
</form>`;
let frame = null;
let timer = null;
let interval = null;
let direction = 1;
function start() {
form.b.textContent = "Pause";
if (delay === null) frame = requestAnimationFrame(tick);
else interval = setInterval(tick, delay);
}
function stop() {
form.b.textContent = "Play";
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (timer !== null) clearTimeout(timer), timer = null;
if (interval !== null) clearInterval(interval), interval = null;
}
function running() {
return frame !== null || timer !== null || interval !== null;
}
function tick() {
if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
if (!loop) return stop();
if (alternate) direction = -direction;
if (loopDelay !== null) {
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (interval !== null) clearInterval(interval), interval = null;
timer = setTimeout(() => (step(), start()), loopDelay);
return;
}
}
if (delay === null) frame = requestAnimationFrame(tick);
step();
}
function step() {
form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
}
form.i.oninput = event => {
if (event && event.isTrusted && running()) stop();
form.value = values[form.i.valueAsNumber];
form.o.value = format(form.value, form.i.valueAsNumber, values);
};
form.b.onclick = () => {
if (running()) return stop();
direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
start();
};
form.i.oninput();
if (autoplay) start();
else stop();
Inputs.disposal(form).then(stop);
return form;
}
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