diff --git a/examples/mortgage-rates/docs/index.md b/examples/mortgage-rates/docs/index.md index e9ba4e679..e5d0c1d21 100644 --- a/examples/mortgage-rates/docs/index.md +++ b/examples/mortgage-rates/docs/index.md @@ -6,94 +6,135 @@ const pmms = FileAttachment("data/pmms.csv").csv({typed: true}); ```js const color = Plot.scale({color: {domain: ["30Y FRM", "15Y FRM"]}}); -function colorLegend(y) { - return html`${y}-year fixed-rate`; -} +const colorLegend = (y) => html`${y}-year fixed-rate`; ``` ```js const tidy = pmms.flatMap(({date, pmms30, pmms15}) => [{date, rate: pmms30, type: "30Y FRM"}, {date, rate: pmms15, type: "15Y FRM"}]); -const recent = tidy.slice(-53 * 2); ``` ```js function frmCard(y, pmms) { const key = `pmms${y}`; - const fmt = d3.format("+.2"); - const fmt2 = d3.format(".2f"); const diff1 = pmms.at(-1)[key] - pmms.at(-2)[key]; const diffY = pmms.at(-1)[key] - pmms.at(-53)[key]; - const range = d3.extent(pmms.slice(-52), d => d[key]); + const range = d3.extent(pmms.slice(-52), (d) => d[key]); const stroke = color.apply(`${y}Y FRM`); - const rangechart = Plot.plot({ - style: "overflow: visible;", - width: 300, - height: 40, - axis: null, - x: {inset: 40}, - marks: [ - Plot.tickX(pmms.slice(-52), { - x: key, - stroke, - insetTop: 10, - insetBottom: 10, - title: (d) => `${d.date?.toLocaleDateString("en-us")}: ${d[key]}%`, - tip: {anchor: "bottom"} - }), - Plot.tickX(pmms.slice(-1), {x: key, strokeWidth: 2}), - Plot.text([`${range[0]}%`], {frameAnchor: "left"}), - Plot.text([`${range[1]}%`], {frameAnchor: "right"}) - ], - caption: html`52 week range` - }); - return html`
-

${y}Y FRM

-

${pmms.at(-1)[key] ?? "N/A"}%

- -
1-week change ${fmt(diff1)}%${trend(diff1)} -
1-year change ${fmt(diffY)}%${trend(diffY)} -
4-week average ${fmt2(d3.mean(pmms.slice(-4), d => d[key]))}% -
52-week average ${fmt2(d3.mean(pmms.slice(-52), d => d[key]))}% + return html.fragment` +

${y}-year fixed-rate

+

${formatPercent(pmms.at(-1)[key])}

+ + + + + + + + + + + + + + + + + + +
1-week change${formatPercent(diff1, {signDisplay: "always"})}${trend(diff1)}
1-year change${formatPercent(diffY, {signDisplay: "always"})}${trend(diffY)}
4-week average${formatPercent(d3.mean(pmms.slice(-4), (d) => d[key]))}
52-week average${formatPercent(d3.mean(pmms.slice(-52), (d) => d[key]))}
- ${rangechart} - `; + ${resize((width) => + Plot.plot({ + width, + height: 40, + axis: null, + x: {inset: 40}, + marks: [ + Plot.tickX(pmms.slice(-52), { + x: key, + stroke, + insetTop: 10, + insetBottom: 10, + title: (d) => `${d.date?.toLocaleDateString("en-us")}: ${d[key]}%`, + tip: {anchor: "bottom"} + }), + Plot.tickX(pmms.slice(-1), {x: key, strokeWidth: 2}), + Plot.text([`${range[0]}%`], {frameAnchor: "left"}), + Plot.text([`${range[1]}%`], {frameAnchor: "right"}) + ] + }) + )} + 52-week range + `; +} + +function formatPercent(value, format) { + return value == null + ? "N/A" + : (value / 100).toLocaleString("en-US", {minimumFractionDigits: 2, style: "percent", ...format}); } function trend(v) { - if (Math.abs(v) < 0.01) return ""; - return v > 0 ? html`↗︎` : html`↘︎`; + return v >= 0.005 ? html`↗︎` + : v <= -0.005 ? html`↘︎` + : "→"; } ``` - +Each week, [Freddie Mac](https://www.freddiemac.com/pmms/about-pmms.html) surveys lenders on rates and points for their ${colorLegend(30)}, ${colorLegend(15)}, and other mortgage products. Data as of ${pmms.at(-1).date?.toLocaleDateString("en-US", {year: "numeric", month: "long", day: "numeric"})}. -> _Each week since April 1971 [Freddie Mac](https://www.freddiemac.com/pmms/about-pmms.html) surveys lenders on the rates and points for their most popular ${colorLegend(30)}, ${colorLegend(15)} and other mortgage products._ + -

${Plot.plot({ - title: "Past year", - height: 250, - y: {nice: 5, grid: true, label: "rate (%)"}, - color, - marks: [ - Plot.lineY(recent, {x: "date", y: "rate", stroke: "type", curve: "step", tip: true, markerEnd: true}) - ] -})}

+
+
${frmCard(30, pmms)}
+
${frmCard(15, pmms)}
+
+

Rates over the past year

+ ${resize((width, height) => + Plot.plot({ + width, + height, + y: {grid: true, label: "rate (%)"}, + color, + marks: [ + Plot.lineY(tidy.slice(-53 * 2), {x: "date", y: "rate", stroke: "type", curve: "step", tip: true, markerEnd: true}) + ] + }) + )} +
+
-

${ -Plot.plot({ - title: "Historical data", - x: {nice: 20}, - y: {grid: true, label: "rate (%)"}, - color, - marks: [ - Plot.ruleY([0]), - Plot.lineY(tidy, {x: "date", y: "rate", stroke: "type", tip: true}) - ] -})}

+
+
+

Rates over all time (${d3.extent(pmms, (d) => d.date.getUTCFullYear()).join("–")})

+ ${resize((width) => + Plot.plot({ + width, + y: {grid: true, label: "rate (%)"}, + color, + marks: [ + Plot.ruleY([0]), + Plot.lineY(tidy, {x: "date", y: "rate", stroke: "type", tip: true}) + ] + }) + )} +
+
diff --git a/src/client/stdlib/resize.ts b/src/client/stdlib/resize.ts index 8f241f255..dd5c86587 100644 --- a/src/client/stdlib/resize.ts +++ b/src/client/stdlib/resize.ts @@ -1,7 +1,8 @@ // TODO Automatically disconnect the observer when the returned DIV is detached. export function resize(run: (width: number, height: number) => Node, invalidation?: Promise): Node { const div = document.createElement("div"); - div.style.cssText = "position: relative; height: 100%;"; + div.style.position = "relative"; + if (run.length !== 1) div.style.height = "100%"; const observer = new ResizeObserver(([entry]) => { const {width, height} = entry.contentRect; while (div.lastChild) div.lastChild.remove(); diff --git a/src/style/grid.css b/src/style/grid.css index a0525c4c7..e71561efc 100644 --- a/src/style/grid.css +++ b/src/style/grid.css @@ -30,9 +30,12 @@ .grid-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .grid-colspan-2, - .grid-colspan-3, - .grid-colspan-4 { + .grid-cols-2 .grid-colspan-2, + .grid-cols-2 .grid-colspan-3, + .grid-cols-2 .grid-colspan-4, + .grid-cols-4 .grid-colspan-2, + .grid-cols-4 .grid-colspan-3, + .grid-cols-4 .grid-colspan-4 { grid-column: span 2; } } @@ -41,6 +44,9 @@ .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .grid-cols-3 .grid-colspan-2 { + grid-column: span 2; + } .grid-cols-3 .grid-colspan-3 { grid-column: span 3; }