{"spec_id":"contour-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// contour-basic: Basic Contour Plot\n// Library: d3 7.9.0 | JavaScript 22.23.0\n// Quality: 88/100 | Created: 2026-06-25\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 70, right: 130, bottom: 72, left: 80 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data: \"peaks\" function on [-3, 3] × [-3, 3], produces both positive and negative values ---\nconst nx = 80, ny = 80;\nconst values = new Float64Array(nx * ny);\nfor (let j = 0; j < ny; j++) {\n  for (let i = 0; i < nx; i++) {\n    const x = -3 + 6 * i / (nx - 1);\n    const y = 3 - 6 * j / (ny - 1); // j=0 → y=3 (top), j=ny-1 → y=-3 (bottom)\n    const x2 = x * x, y2 = y * y;\n    values[j * nx + i] =\n      3 * (1 - x) * (1 - x) * Math.exp(-x2 - (y + 1) * (y + 1)) -\n      10 * (x / 5 - x * x2 - y * y2 * y2) * Math.exp(-x2 - y2) -\n      (1 / 3) * Math.exp(-(x + 1) * (x + 1) - y2);\n  }\n}\nconst vmin = d3.min(values);\nconst vmax = d3.max(values);\nconst absMax = Math.max(Math.abs(vmin), Math.abs(vmax));\n\n// Find grid indices of global max and min for focal-point annotations\nlet maxIdx = 0, minIdx = 0;\nfor (let k = 1; k < values.length; k++) {\n  if (values[k] > values[maxIdx]) maxIdx = k;\n  if (values[k] < values[minIdx]) minIdx = k;\n}\nconst peakX = -3 + 6 * (maxIdx % nx) / (nx - 1);\nconst peakY = 3 - 6 * Math.floor(maxIdx / nx) / (ny - 1);\nconst troughX = -3 + 6 * (minIdx % nx) / (nx - 1);\nconst troughY = 3 - 6 * Math.floor(minIdx / nx) / (ny - 1);\n\n// --- SVG mount ---\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst defs = svg.append(\"defs\");\n\ndefs.append(\"clipPath\").attr(\"id\", \"pc\")\n  .append(\"rect\").attr(\"width\", iw).attr(\"height\", ih);\n\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Contour generation ---\nconst numLevels = 18;\nconst thresholds = d3.range(numLevels + 1).map(i => -absMax + 2 * absMax * i / numLevels);\nconst contoursData = d3.contours().size([nx, ny]).thresholds(thresholds)(values);\n\n// Projection: grid space [0..nx] × [0..ny] → plot area [0..iw] × [0..ih]\nconst geoProj = d3.geoTransform({\n  point: function(px, py) { this.stream.point(px * iw / nx, py * ih / ny); }\n});\nconst pathGen = d3.geoPath(geoProj);\n\n// Diverging Imprint colormap: red (negative) → near-bg → blue (positive)\nconst colorScale = d3.scaleSequential(d3.interpolateRgbBasis(t.div)).domain([-absMax, absMax]);\n\n// --- Filled contour bands (clipped to plot area) ---\nconst cg = g.append(\"g\").attr(\"clip-path\", \"url(#pc)\");\n\ncg.selectAll(\".cf\").data(contoursData).join(\"path\")\n  .attr(\"class\", \"cf\").attr(\"d\", pathGen)\n  .attr(\"fill\", d => colorScale(d.value)).attr(\"stroke\", \"none\");\n\n// --- Contour isolines: increased visibility; zero-crossing isoline emphasized ---\nconst step = 2 * absMax / numLevels;\ncg.selectAll(\".cl\").data(contoursData).join(\"path\")\n  .attr(\"class\", \"cl\").attr(\"d\", pathGen)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", d => Math.abs(d.value) < step ? 1.5 : 1.0)\n  .attr(\"stroke-opacity\", d => Math.abs(d.value) < step ? 0.6 : 0.35);\n\n// --- Plot border (all four sides — standard for contour plots) ---\ng.append(\"rect\").attr(\"width\", iw).attr(\"height\", ih)\n  .attr(\"fill\", \"none\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1);\n\n// --- Axes ---\nconst xScale = d3.scaleLinear().domain([-3, 3]).range([0, iw]);\nconst yScale = d3.scaleLinear().domain([3, -3]).range([0, ih]);\n\nconst xAx = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(xScale).ticks(7).tickSize(5).tickPadding(6));\nxAx.select(\".domain\").remove();\nxAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\nxAx.selectAll(\".tick text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n\nconst yAx = g.append(\"g\")\n  .call(d3.axisLeft(yScale).ticks(7).tickSize(5).tickPadding(6));\nyAx.select(\".domain\").remove();\nyAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\nyAx.selectAll(\".tick text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n\n// --- Axis labels ---\ng.append(\"text\")\n  .attr(\"x\", iw / 2).attr(\"y\", ih + 58)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"16px\")\n  .text(\"x\");\n\ng.append(\"text\")\n  .attr(\"transform\", `translate(-56,${ih / 2}) rotate(-90)`)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"16px\")\n  .text(\"y\");\n\n// --- Colorbar ---\nconst cbW = 18, cbX = iw + 32;\nconst cbGrad = defs.append(\"linearGradient\").attr(\"id\", \"cbg\")\n  .attr(\"x1\", \"0%\").attr(\"y1\", \"100%\").attr(\"x2\", \"0%\").attr(\"y2\", \"0%\");\nfor (let s = 0; s <= 30; s++) {\n  const f = s / 30;\n  cbGrad.append(\"stop\")\n    .attr(\"offset\", `${(f * 100).toFixed(1)}%`)\n    .attr(\"stop-color\", colorScale(-absMax + 2 * absMax * f));\n}\n\ng.append(\"rect\")\n  .attr(\"x\", cbX).attr(\"y\", 0).attr(\"width\", cbW).attr(\"height\", ih)\n  .style(\"fill\", \"url(#cbg)\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 0.5);\n\nconst cbScale = d3.scaleLinear().domain([-absMax, absMax]).range([ih, 0]);\nconst cbAx = g.append(\"g\").attr(\"transform\", `translate(${cbX + cbW},0)`)\n  .call(d3.axisRight(cbScale).ticks(6).tickFormat(d3.format(\".1f\")));\ncbAx.selectAll(\".tick text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"12px\");\ncbAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\ncbAx.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\ng.append(\"text\")\n  .attr(\"transform\", `translate(${cbX + cbW + 60},${ih / 2}) rotate(-90)`)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\")\n  .text(\"z (field value)\");\n\n// --- Focal-point annotations: global max and min ---\nconst focalPts = [\n  { x: peakX,   y: peakY,   label: `max ${values[maxIdx].toFixed(1)}` },\n  { x: troughX, y: troughY, label: `min ${values[minIdx].toFixed(1)}` },\n];\ncg.selectAll(\".fp-ring\").data(focalPts).join(\"circle\")\n  .attr(\"class\", \"fp-ring\")\n  .attr(\"cx\", d => xScale(d.x)).attr(\"cy\", d => yScale(d.y))\n  .attr(\"r\", 7).attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.ink).attr(\"stroke-width\", 1.5).attr(\"stroke-dasharray\", \"3,2\");\n\ncg.selectAll(\".fp-lbl\").data(focalPts).join(\"text\")\n  .attr(\"class\", \"fp-lbl\")\n  .attr(\"x\", d => xScale(d.x)).attr(\"y\", d => yScale(d.y) - 11)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"11px\").style(\"font-weight\", \"600\")\n  .text(d => d.label);\n\n// --- Title ---\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 46)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"contour-basic · javascript · d3 · anyplot.ai\");\n"}