{"spec_id":"line-yield-curve","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// line-yield-curve: Yield Curve (Interest Rate Term Structure)\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 92/100 | Created: 2026-06-10\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Maturity schedule\nconst maturities = [\"1M\", \"3M\", \"6M\", \"1Y\", \"2Y\", \"3Y\", \"5Y\", \"7Y\", \"10Y\", \"20Y\", \"30Y\"];\nconst maturityYears = [0.083, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30];\n\n// U.S. Treasury yield snapshots (annualised %)\n// Jan 2021 — normal, upward-sloping (ZIRP era, early pandemic recovery)\nconst yields2021 = [0.09, 0.08, 0.09, 0.10, 0.17, 0.22, 0.46, 0.74, 1.09, 1.78, 1.83];\n// Jan 2022 — flattening as Fed signals rate hike cycle\nconst yields2022 = [0.06, 0.07, 0.18, 0.40, 0.93, 1.35, 1.69, 1.88, 1.89, 2.27, 2.23];\n// Nov 2022 — deeply inverted (aggressive tightening, 2Y > 10Y spread)\nconst yieldsNov2022 = [3.84, 4.38, 4.55, 4.59, 4.46, 4.31, 4.11, 4.00, 3.86, 4.16, 3.93];\n\nconst toPoints = (ys) => maturityYears.map((x, i) => ({ x, y: ys[i] }));\n\n// Robust tick label: nearest-neighbour match to handle floating-point drift\nconst matLabel = (val) => {\n  let best = maturityYears[0], bestDist = Infinity;\n  maturityYears.forEach((yr, i) => {\n    const d = Math.abs(yr - val);\n    if (d < bestDist) { bestDist = d; best = i; }\n  });\n  return bestDist < 0.01 ? maturities[best] : \"\";\n};\n\n// Plugin: shade the 3M–10Y inversion zone (where Nov-2022 is inverted)\nconst inversionZonePlugin = {\n  id: \"inversionZone\",\n  beforeDraw({ ctx, scales, chartArea }) {\n    if (!scales.x) return;\n    const x1 = scales.x.getPixelForValue(0.25);\n    const x2 = scales.x.getPixelForValue(10);\n    ctx.save();\n    ctx.fillStyle = window.ANYPLOT_THEME === \"dark\"\n      ? \"rgba(174,48,48,0.16)\"\n      : \"rgba(174,48,48,0.07)\";\n    ctx.fillRect(x1, chartArea.top, x2 - x1, chartArea.bottom - chartArea.top);\n    ctx.restore();\n  },\n};\n\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\nnew Chart(canvas, {\n  type: \"line\",\n  plugins: [inversionZonePlugin],\n  data: {\n    datasets: [\n      {\n        label: \"Jan 2021 — Normal\",\n        data: toPoints(yields2021),\n        borderColor: t.palette[0],          // Imprint palette pos 1 — brand green\n        pointBackgroundColor: t.palette[0],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n        pointRadius: 6,\n        borderWidth: 3,\n        tension: 0.3,\n        fill: false,\n      },\n      {\n        label: \"Jan 2022 — Flattening\",\n        data: toPoints(yields2022),\n        borderColor: t.palette[1],          // Imprint palette pos 2 — lavender\n        pointBackgroundColor: t.palette[1],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n        pointRadius: 6,\n        borderWidth: 3,\n        tension: 0.3,\n        fill: false,\n      },\n      {\n        label: \"Nov 2022 — Inverted\",\n        data: toPoints(yieldsNov2022),\n        borderColor: t.palette[4],          // Imprint matte red — semantic: recessionary signal\n        pointBackgroundColor: t.palette[4],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n        pointRadius: 6,\n        borderWidth: 3.5,\n        tension: 0.3,\n        fill: false,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"line-yield-curve · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 12, bottom: 6 },\n      },\n      subtitle: {\n        display: true,\n        text: \"U.S. Treasury yields — shaded band marks the 3M–10Y inversion zone (Nov 2022)\",\n        color: t.inkSoft,\n        font: { size: 14, style: \"italic\" },\n        padding: { bottom: 14 },\n      },\n      legend: {\n        position: \"top\",\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          padding: 24,\n          usePointStyle: true,\n          pointStyleWidth: 40,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"logarithmic\",\n        min: 0.06,\n        max: 35,\n        title: {\n          display: true,\n          text: \"Maturity\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n          padding: { top: 8 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: matLabel,\n          maxRotation: 0,\n          autoSkip: false,\n        },\n        afterBuildTicks(axis) {\n          axis.ticks = maturityYears.map((v) => ({ value: v }));\n        },\n        grid: {\n          color: t.grid,\n        },\n      },\n      y: {\n        title: {\n          display: true,\n          text: \"Yield (%)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n          padding: { bottom: 8 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: (val) => val.toFixed(2) + \"%\",\n        },\n        grid: {\n          color: t.grid,\n        },\n      },\n    },\n  },\n});\n"}