{"spec_id":"line-yield-curve","library":"echarts","language":"javascript","code":"// anyplot.ai\n// line-yield-curve: Yield Curve (Interest Rate Term Structure)\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 88/100 | Created: 2026-06-10\n\nconst t = window.ANYPLOT_TOKENS;\n\n// U.S. Treasury yield curve snapshots — normal, flattening, and inverted\nconst maturities = [\n  { label: \"1M\",  years: 0.083 },\n  { label: \"3M\",  years: 0.25  },\n  { label: \"6M\",  years: 0.5   },\n  { label: \"1Y\",  years: 1     },\n  { label: \"2Y\",  years: 2     },\n  { label: \"3Y\",  years: 3     },\n  { label: \"5Y\",  years: 5     },\n  { label: \"7Y\",  years: 7     },\n  { label: \"10Y\", years: 10    },\n  { label: \"20Y\", years: 20    },\n  { label: \"30Y\", years: 30    },\n];\n\nconst curves = [\n  {\n    date: \"Jan 2021\",\n    yields: [0.07, 0.07, 0.08, 0.09, 0.15, 0.22, 0.47, 0.77, 1.10, 1.74, 1.86],\n  },\n  {\n    date: \"Jul 2022\",\n    yields: [1.87, 2.35, 2.89, 3.15, 3.17, 3.15, 3.13, 3.14, 3.02, 3.32, 3.15],\n  },\n  {\n    date: \"Mar 2023\",\n    yields: [4.57, 4.82, 5.02, 5.01, 4.60, 4.35, 4.05, 3.97, 3.96, 4.12, 3.96],\n  },\n];\n\n// Build [maturity_years, yield_pct] pairs for each curve\nconst seriesData = curves.map(c =>\n  maturities.map((m, i) => [m.years, c.yields[i]])\n);\n\n// Title sizing — scale down if longer than ~67-char baseline\nconst titleText = \"U.S. Treasury Yield Curves · line-yield-curve · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.max(14, Math.round(22 * 67 / titleText.length));\n\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n\n  title: {\n    text: titleText,\n    left: \"center\",\n    top: 24,\n    textStyle: {\n      color: t.ink,\n      fontSize: titleFontSize,\n      fontWeight: \"bold\",\n    },\n  },\n\n  legend: {\n    data: curves.map(c => c.date),\n    bottom: 32,\n    itemGap: 50,\n    icon: \"roundRect\",\n    textStyle: { color: t.inkSoft, fontSize: 16 },\n  },\n\n  grid: {\n    left: 100,\n    right: 110,\n    top: 90,\n    bottom: 110,\n    borderWidth: 0,\n  },\n\n  tooltip: {\n    trigger: \"axis\",\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    borderWidth: 1,\n    textStyle: { color: t.ink, fontSize: 14 },\n    padding: [10, 14],\n    axisPointer: {\n      type: \"line\",\n      lineStyle: { color: t.inkSoft, width: 1, type: \"dashed\" },\n    },\n    formatter: function(params) {\n      const xVal = params[0].value[0];\n      const mat = maturities.reduce((prev, cur) =>\n        Math.abs(cur.years - xVal) < Math.abs(prev.years - xVal) ? cur : prev\n      );\n      let html = `<div style=\"font-weight:600;margin-bottom:6px\">${mat.label}</div>`;\n      params.forEach(p => {\n        html += `${p.marker}&nbsp;${p.seriesName}:&nbsp;<b>${p.value[1].toFixed(2)}%</b><br/>`;\n      });\n      return html;\n    },\n  },\n\n  // Log scale reflects true time spacing of the term structure\n  xAxis: {\n    type: \"log\",\n    logBase: 10,\n    min: 0.06,\n    max: 35,\n    name: \"Maturity\",\n    nameLocation: \"middle\",\n    nameGap: 42,\n    nameTextStyle: { color: t.inkSoft, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      formatter: function(val) {\n        // Match only the major decade ticks (0.1, 1, 10) via log-distance\n        const lv = Math.log10(val);\n        if (Math.abs(lv + 1) < 0.05) return \"1M\";   // 0.1 yr ≈ 1.2 months\n        if (Math.abs(lv)     < 0.05) return \"1Y\";\n        if (Math.abs(lv - 1) < 0.05) return \"10Y\";\n        if (val >= 25) return \"30Y\";\n        return \"\";\n      },\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n    axisTick: { lineStyle: { color: t.inkSoft } },\n  },\n\n  yAxis: {\n    type: \"value\",\n    name: \"Yield (%)\",\n    nameLocation: \"middle\",\n    nameGap: 55,\n    nameTextStyle: { color: t.inkSoft, fontSize: 16 },\n    min: 0,\n    max: 6,\n    interval: 1,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      formatter: \"{value}%\",\n    },\n    axisLine: { show: true, lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n\n  series: [\n    // Normal upward-sloping curve — Jan 2021 (Imprint palette[0], brand green)\n    {\n      name: curves[0].date,\n      type: \"line\",\n      data: seriesData[0],\n      lineStyle: { width: 3.5, color: t.palette[0] },\n      itemStyle: { color: t.palette[0] },\n      symbol: \"circle\",\n      symbolSize: 10,\n      endLabel: { show: true, formatter: \"{a}\", color: t.palette[0], fontSize: 13, fontWeight: \"bold\" },\n    },\n    // Flattening curve — Jul 2022 (short end rising, long end flat)\n    {\n      name: curves[1].date,\n      type: \"line\",\n      data: seriesData[1],\n      lineStyle: { width: 3.5, color: t.palette[1] },\n      itemStyle: { color: t.palette[1] },\n      symbol: \"circle\",\n      symbolSize: 10,\n      endLabel: { show: true, formatter: \"{a}\", color: t.palette[1], fontSize: 13, fontWeight: \"bold\" },\n    },\n    // Inverted curve — Mar 2023, with 1Y–10Y inversion zone shaded\n    {\n      name: curves[2].date,\n      type: \"line\",\n      data: seriesData[2],\n      lineStyle: { width: 3.5, color: t.palette[2] },\n      itemStyle: { color: t.palette[2] },\n      symbol: \"circle\",\n      symbolSize: 10,\n      endLabel: { show: true, formatter: \"{a}\", color: t.palette[2], fontSize: 13, fontWeight: \"bold\" },\n      markArea: {\n        silent: true,\n        itemStyle: {\n          color: t.palette[2],\n          opacity: 0.14,\n        },\n        data: [[\n          { xAxis: 1, name: \"Inversion Zone\" },\n          { xAxis: 10 },\n        ]],\n        label: {\n          show: true,\n          position: \"insideBottom\",\n          color: t.inkSoft,\n          fontSize: 13,\n          fontStyle: \"italic\",\n          formatter: \"Inversion Zone\",\n        },\n      },\n    },\n  ],\n});\n"}