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;
}
|