{"spec_id":"area-elevation-profile","library":"d3","language":"javascript","code":"// anyplot.ai\n// area-elevation-profile: Terrain Elevation Profile Along Transect\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-10\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 100, right: 70, bottom: 80, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data -------------------------------------------------------------------\n// Deterministic alpine trail elevation profile (120 km transect, 481 sample points)\nfunction elevAt(dist) {\n  const base = 820;\n  const p1 = 1180 * Math.exp(-Math.pow((dist - 24) / 11, 2));\n  const v1 = -320  * Math.exp(-Math.pow((dist - 40) /  7, 2));\n  const p2 = 1480 * Math.exp(-Math.pow((dist - 62) / 13, 2));\n  const v2 = -380  * Math.exp(-Math.pow((dist - 80) /  6, 2));\n  const p3 = 1060 * Math.exp(-Math.pow((dist - 98) /  9, 2));\n  const ripple = 40 * Math.sin(dist * 1.9) + 25 * Math.sin(dist * 4.7) + 12 * Math.sin(dist * 9.3);\n  return Math.max(640, base + p1 + v1 + p2 + v2 + p3 + ripple);\n}\n\nconst TOTAL_KM = 120;\nconst N_PTS    = 480;\nconst profile  = Array.from({ length: N_PTS + 1 }, (_, i) => {\n  const dist = (i / N_PTS) * TOTAL_KM;\n  return { dist, elev: elevAt(dist) };\n});\n\n// Landmarks — elevation resolved from profile array\nconst LANDMARKS = [\n  { name: \"Trailhead\",       dist: 0   },\n  { name: \"Pine Ridge Pass\", dist: 24  },\n  { name: \"Deer Meadow\",     dist: 40  },\n  { name: \"Eagle Summit\",    dist: 62  },\n  { name: \"Stone Hut\",       dist: 80  },\n  { name: \"North Peak\",      dist: 98  },\n  { name: \"Valley End\",      dist: 120 },\n];\nLANDMARKS.forEach(lm => {\n  const idx = Math.min(Math.round((lm.dist / TOTAL_KM) * N_PTS), profile.length - 1);\n  lm.elev = profile[idx].elev;\n});\n\n// --- SVG mount --------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g   = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales -----------------------------------------------------------------\nconst xScale = d3.scaleLinear().domain([0, TOTAL_KM]).range([0, iw]);\nconst [eMin, eMax] = d3.extent(profile, d => d.elev);\nconst yScale = d3.scaleLinear()\n  .domain([Math.max(0, eMin - 120), eMax + 280])\n  .nice()\n  .range([ih, 0]);\n\n// Compute actual vertical exaggeration from final scale domains\nconst [yDomMin, yDomMax] = yScale.domain();\nconst veActual = Math.round((ih * TOTAL_KM * 1000) / ((yDomMax - yDomMin) * iw));\n\n// --- Gradient fill definition -----------------------------------------------\nconst defs = svg.append(\"defs\");\nconst grad = defs.append(\"linearGradient\")\n  .attr(\"id\", \"elevFill\")\n  .attr(\"x1\", \"0%\").attr(\"y1\", \"0%\")\n  .attr(\"x2\", \"0%\").attr(\"y2\", \"100%\");\ngrad.append(\"stop\").attr(\"offset\", \"0%\")\n  .attr(\"stop-color\", t.palette[0]).attr(\"stop-opacity\", 0.6);\ngrad.append(\"stop\").attr(\"offset\", \"100%\")\n  .attr(\"stop-color\", t.palette[0]).attr(\"stop-opacity\", 0.05);\n\n// --- Y-axis gridlines -------------------------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"y-grid\")\n  .call(d3.axisLeft(yScale).tickSize(-iw).tickFormat(\"\").ticks(6))\n  .call(grd => {\n    grd.selectAll(\".tick line\").attr(\"stroke\", t.grid);\n    grd.select(\".domain\").remove();\n  });\n\n// --- Area fill --------------------------------------------------------------\ng.append(\"path\")\n  .datum(profile)\n  .attr(\"fill\", \"url(#elevFill)\")\n  .attr(\"d\", d3.area()\n    .x(d => xScale(d.dist))\n    .y0(ih)\n    .y1(d => yScale(d.elev))\n    .curve(d3.curveCatmullRom.alpha(0.5)));\n\n// --- Elevation profile line -------------------------------------------------\ng.append(\"path\")\n  .datum(profile)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 2.5)\n  .attr(\"d\", d3.line()\n    .x(d => xScale(d.dist))\n    .y(d => yScale(d.elev))\n    .curve(d3.curveCatmullRom.alpha(0.5)));\n\n// --- Landmark annotations ---------------------------------------------------\nLANDMARKS.forEach(lm => {\n  const xPos   = xScale(lm.dist);\n  const yTerr  = yScale(lm.elev);\n  const labelY = Math.max(14, yTerr - 60);\n  const anchor = lm.dist < 8 ? \"start\" : lm.dist > 112 ? \"end\" : \"middle\";\n\n  // Dashed vertical line from terrain down to x-axis baseline\n  g.append(\"line\")\n    .attr(\"x1\", xPos).attr(\"y1\", yTerr)\n    .attr(\"x2\", xPos).attr(\"y2\", ih)\n    .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1)\n    .attr(\"stroke-dasharray\", \"4,3\").attr(\"stroke-opacity\", 0.45);\n\n  // Dot at terrain surface\n  g.append(\"circle\")\n    .attr(\"cx\", xPos).attr(\"cy\", yTerr).attr(\"r\", 4.5)\n    .attr(\"fill\", t.ink)\n    .attr(\"stroke\", t.pageBg).attr(\"stroke-width\", 2);\n\n  // Thin connector from dot up to label\n  g.append(\"line\")\n    .attr(\"x1\", xPos).attr(\"y1\", yTerr - 7)\n    .attr(\"x2\", xPos).attr(\"y2\", labelY + 18)\n    .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 0.8).attr(\"stroke-opacity\", 0.4);\n\n  // Landmark name\n  g.append(\"text\")\n    .attr(\"x\", xPos).attr(\"y\", labelY)\n    .attr(\"text-anchor\", anchor).attr(\"fill\", t.ink)\n    .style(\"font-size\", \"12px\").style(\"font-weight\", \"600\")\n    .text(lm.name);\n\n  // Elevation value\n  g.append(\"text\")\n    .attr(\"x\", xPos).attr(\"y\", labelY + 15)\n    .attr(\"text-anchor\", anchor).attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"11px\")\n    .text(`${Math.round(lm.elev)} m`);\n});\n\n// --- Axes -------------------------------------------------------------------\nconst xAxisG = g.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(xScale).ticks(10).tickSizeOuter(0)\n    .tickFormat(d => `${d} km`));\n\nconst yAxisG = g.append(\"g\")\n  .call(d3.axisLeft(yScale).ticks(6).tickSizeOuter(0)\n    .tickFormat(d => `${d3.format(\",d\")(d)} m`));\n\nfor (const ax of [xAxisG, yAxisG]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\".tick line\").attr(\"stroke\", t.grid);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// --- Axis labels ------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw / 2).attr(\"y\", height - 20)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"15px\")\n  .text(\"Distance (km)\");\n\nsvg.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -(margin.top + ih / 2)).attr(\"y\", 26)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"15px\")\n  .text(\"Elevation (m)\");\n\n// --- Vertical exaggeration note ---------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw - 8).attr(\"y\", margin.top - 16)\n  .attr(\"text-anchor\", \"end\").attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"12px\").style(\"font-style\", \"italic\")\n  .text(`Vertical exaggeration: ${veActual}×`);\n\n// --- Title ------------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 50)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"area-elevation-profile · javascript · d3 · anyplot.ai\");\n"}