{"spec_id":"contour-filled","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// contour-filled: Filled Contour Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-04\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: synthetic mountain-range elevation field on a regular grid ------\n// The core Highcharts bundle has no heatmap/colorAxis module loaded, so the\n// filled isobands are computed by hand (marching-squares style quad clipping)\n// and drawn with the SVG renderer, same technique anyplot's other\n// module-less Highcharts grids use.\nconst NX = 40;\nconst NY = 40;\nconst X_MIN = -10;\nconst X_MAX = 10;\nconst Y_MIN = -10;\nconst Y_MAX = 10;\nconst LEVELS = 14;\n\nconst xs = Array.from({ length: NX }, (_, i) => X_MIN + (i * (X_MAX - X_MIN)) / (NX - 1));\nconst ys = Array.from({ length: NY }, (_, j) => Y_MIN + (j * (Y_MAX - Y_MIN)) / (NY - 1));\n\nfunction peak(x, y, amp, cx, cy, sx, sy) {\n  return amp * Math.exp(-(((x - cx) ** 2) / (2 * sx * sx) + ((y - cy) ** 2) / (2 * sy * sy)));\n}\n\nfunction elevation(x, y) {\n  return (\n    peak(x, y, 1150, 2.5, 2.5, 3.2, 3.6) +\n    peak(x, y, 850, -4.5, -3, 3.8, 3) +\n    peak(x, y, 500, 1, -5.5, 2.4, 2.8)\n  );\n}\n\n// Z[row][col] — row indexes ys, col indexes xs.\nconst Z = ys.map((y) => xs.map((x) => elevation(x, y)));\n\nlet zMin = Infinity;\nlet zMax = -Infinity;\nfor (const row of Z) {\n  for (const v of row) {\n    if (v < zMin) zMin = v;\n    if (v > zMax) zMax = v;\n  }\n}\nconst levelBounds = Array.from({ length: LEVELS + 1 }, (_, i) => zMin + (i * (zMax - zMin)) / LEVELS);\n\n// --- Color: imprint_seq — brand green (low) to blue (high) elevation -------\nfunction hexToRgb(hex) {\n  return [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];\n}\nconst SEQ_LOW = hexToRgb(t.seq[0]);\nconst SEQ_HIGH = hexToRgb(t.seq[1]);\n\nfunction bandColor(bandIndex) {\n  const frac = (bandIndex + 0.5) / LEVELS;\n  const lerp = (a, b) => Math.round(a + (b - a) * frac);\n  return `rgb(${lerp(SEQ_LOW[0], SEQ_HIGH[0])},${lerp(SEQ_LOW[1], SEQ_HIGH[1])},${lerp(SEQ_LOW[2], SEQ_HIGH[2])})`;\n}\n\n// --- Isoband extraction: clip each grid quad against a value band ----------\n// Sutherland-Hodgman clipping against z >= lo, then z <= hi, interpolating\n// along the quad's edges — the standard \"conrec\"-style shortcut for turning\n// a scalar grid into smooth filled contour polygons per cell.\nfunction clipHalfPlane(poly, inside, crossing) {\n  if (poly.length === 0) return poly;\n  const out = [];\n  for (let i = 0; i < poly.length; i++) {\n    const curr = poly[i];\n    const prev = poly[(i + poly.length - 1) % poly.length];\n    const currIn = inside(curr);\n    const prevIn = inside(prev);\n    if (currIn) {\n      if (!prevIn) out.push(crossing(prev, curr));\n      out.push(curr);\n    } else if (prevIn) {\n      out.push(crossing(prev, curr));\n    }\n  }\n  return out;\n}\n\nfunction crossAt(p1, p2, level) {\n  const f = (level - p1.z) / (p2.z - p1.z);\n  return { x: p1.x + f * (p2.x - p1.x), y: p1.y + f * (p2.y - p1.y), z: level };\n}\n\nfunction bandPolygon(quad, lo, hi) {\n  let poly = quad;\n  if (lo > -Infinity) poly = clipHalfPlane(poly, (p) => p.z >= lo, (a, b) => crossAt(a, b, lo));\n  if (poly.length >= 3 && hi < Infinity) poly = clipHalfPlane(poly, (p) => p.z <= hi, (a, b) => crossAt(a, b, hi));\n  return poly;\n}\n\n// --- Isolines: crisp per-level boundary lines overlaid on the bands --------\n// A cell edge crosses `level` when its two endpoints straddle it; exactly two\n// (or, at an ambiguous saddle, four) of the quad's four edges cross for any\n// given level, and connecting the crossing points traces the isoline through\n// that cell — the classic marching-squares result without needing the full\n// 16-case lookup table.\nfunction edgeCrossing(p1, p2, level) {\n  const f = (level - p1.z) / (p2.z - p1.z);\n  return { x: p1.x + f * (p2.x - p1.x), y: p1.y + f * (p2.y - p1.y) };\n}\n\nfunction cellContourSegments(a, b, c, d, level) {\n  const crosses = (p1, p2) => p1.z >= level !== p2.z >= level;\n  const top = crosses(a, b) ? edgeCrossing(a, b, level) : null;\n  const right = crosses(b, c) ? edgeCrossing(b, c, level) : null;\n  const bottom = crosses(d, c) ? edgeCrossing(d, c, level) : null;\n  const left = crosses(a, d) ? edgeCrossing(a, d, level) : null;\n  const present = [top, right, bottom, left].filter(Boolean);\n  if (present.length === 2) return [[present[0], present[1]]];\n  if (present.length === 4) {\n    // Saddle cell (diagonal corners on the same side of the level): resolve\n    // the ambiguity from corner a so pairing stays consistent cell-to-cell.\n    return a.z >= level\n      ? [\n          [top, left],\n          [right, bottom],\n        ]\n      : [\n          [top, right],\n          [left, bottom],\n        ];\n  }\n  return [];\n}\n\n// --- Title (fontsize scaled off the 67-char baseline) -----------------------\nconst TITLE_TEXT = 'Synthetic Mountain Range Elevation · contour-filled · javascript · highcharts · anyplot.ai';\nconst TITLE_FS = Math.max(Math.round(22 * Math.min(1, 67 / TITLE_TEXT.length)), 14);\n\n// Fixed, theme-independent isoline stroke (not derived from t.ink): the data\n// colors are already identical between light/dark, so the boundary lines\n// must be too, or they read faint on light-theme bands and bright on\n// dark-theme bands (a prior review flagged exactly this).\nconst ISOLINE_STROKE = 'rgba(26, 26, 23, 0.45)';\n// Elevation levels get labeled with a \"nice\" round number rather than the\n// raw computed boundary, and only a few are called out directly on the map.\nconst roundNice = (v) => Math.round(v / 50) * 50;\nconst LABELED_LEVEL_INDICES = new Set([\n  Math.round(LEVELS * 0.25),\n  Math.round(LEVELS * 0.5),\n  Math.round(LEVELS * 0.75),\n]);\n\nconst drawn = [];\nfunction clearDrawn() {\n  drawn.forEach((el) => {\n    try {\n      el.destroy();\n    } catch (_err) {\n      // already removed\n    }\n  });\n  drawn.length = 0;\n}\n\nfunction drawAll() {\n  const chart = this;\n  clearDrawn();\n  const r = chart.renderer;\n  const xAxis = chart.xAxis[0];\n  const yAxis = chart.yAxis[0];\n\n  // Filled isobands, cell by cell.\n  for (let j = 0; j < NY - 1; j++) {\n    for (let i = 0; i < NX - 1; i++) {\n      const quad = [\n        { x: xs[i], y: ys[j], z: Z[j][i] },\n        { x: xs[i + 1], y: ys[j], z: Z[j][i + 1] },\n        { x: xs[i + 1], y: ys[j + 1], z: Z[j + 1][i + 1] },\n        { x: xs[i], y: ys[j + 1], z: Z[j + 1][i] },\n      ];\n      const cellMin = Math.min(quad[0].z, quad[1].z, quad[2].z, quad[3].z);\n      const cellMax = Math.max(quad[0].z, quad[1].z, quad[2].z, quad[3].z);\n\n      for (let b = 0; b < LEVELS; b++) {\n        const lo = b === 0 ? -Infinity : levelBounds[b];\n        const hi = b === LEVELS - 1 ? Infinity : levelBounds[b + 1];\n        if (hi < cellMin || lo > cellMax) continue;\n\n        const poly = bandPolygon(quad, lo, hi);\n        if (poly.length < 3) continue;\n\n        const fill = bandColor(b);\n        const pathArr = [];\n        poly.forEach((p, idx) => {\n          pathArr.push(idx === 0 ? 'M' : 'L', xAxis.toPixels(p.x), yAxis.toPixels(p.y));\n        });\n        pathArr.push('Z');\n        drawn.push(\n          r\n            .path(pathArr)\n            .attr({ fill, stroke: fill, 'stroke-width': 0.6, zIndex: 2 })\n            .add()\n        );\n      }\n    }\n  }\n\n  // Isolines at each interior level boundary — precise level identification\n  // on top of the color bands (spec: \"consider overlaying contour lines\").\n  for (let lvlIdx = 1; lvlIdx < LEVELS; lvlIdx++) {\n    const level = levelBounds[lvlIdx];\n    const pathArr = [];\n    const segments = [];\n    for (let j = 0; j < NY - 1; j++) {\n      for (let i = 0; i < NX - 1; i++) {\n        const a = { x: xs[i], y: ys[j], z: Z[j][i] };\n        const b = { x: xs[i + 1], y: ys[j], z: Z[j][i + 1] };\n        const c = { x: xs[i + 1], y: ys[j + 1], z: Z[j + 1][i + 1] };\n        const d = { x: xs[i], y: ys[j + 1], z: Z[j + 1][i] };\n        for (const [p1, p2] of cellContourSegments(a, b, c, d, level)) {\n          pathArr.push('M', xAxis.toPixels(p1.x), yAxis.toPixels(p1.y), 'L', xAxis.toPixels(p2.x), yAxis.toPixels(p2.y));\n          segments.push([p1, p2]);\n        }\n      }\n    }\n    if (!pathArr.length) continue;\n    drawn.push(\n      r\n        .path(pathArr)\n        .attr({ fill: 'none', stroke: ISOLINE_STROKE, 'stroke-width': 1.1, zIndex: 3 })\n        .add()\n    );\n\n    // Direct elevation callout on a few isolines — picks the longest segment\n    // for that level as a stable, uncluttered anchor point for the label.\n    if (LABELED_LEVEL_INDICES.has(lvlIdx)) {\n      let longest = segments[0];\n      let longestLenSq = -1;\n      for (const seg of segments) {\n        const dx = seg[1].x - seg[0].x;\n        const dy = seg[1].y - seg[0].y;\n        const lenSq = dx * dx + dy * dy;\n        if (lenSq > longestLenSq) {\n          longestLenSq = lenSq;\n          longest = seg;\n        }\n      }\n      const midX = xAxis.toPixels((longest[0].x + longest[1].x) / 2);\n      const midY = yAxis.toPixels((longest[0].y + longest[1].y) / 2);\n      drawn.push(\n        r\n          .label(`${roundNice(level)} m`, midX, midY, 'rect')\n          .attr({ fill: t.elevatedBg, stroke: t.inkSoft, 'stroke-width': 1, r: 4, padding: 3, zIndex: 4 })\n          .css({ color: t.ink, fontSize: '11px', fontWeight: '600' })\n          .add()\n      );\n    }\n  }\n\n  // Discrete colorbar (right of the plot area) — one swatch per band.\n  const barLeft = chart.plotLeft + chart.plotWidth + 60;\n  const barWidth = 30;\n  const barTop = chart.plotTop + 10;\n  const barHeight = chart.plotHeight - 20;\n  const swatchH = barHeight / LEVELS;\n\n  for (let b = LEVELS - 1; b >= 0; b--) {\n    const yTop = barTop + (LEVELS - 1 - b) * swatchH;\n    drawn.push(\n      r\n        .rect(barLeft, yTop, barWidth, swatchH + 0.5)\n        .attr({ fill: bandColor(b), zIndex: 2 })\n        .add()\n    );\n  }\n  drawn.push(\n    r\n      .rect(barLeft, barTop, barWidth, barHeight)\n      .attr({ fill: 'none', stroke: t.inkSoft, 'stroke-width': 1, zIndex: 3 })\n      .add()\n  );\n  [\n    [roundNice(zMax), 0],\n    [roundNice((zMin + zMax) / 2), 0.5],\n    [roundNice(zMin), 1],\n  ].forEach(([value, frac]) => {\n    drawn.push(\n      r\n        .text(value.toString(), barLeft + barWidth + 10, barTop + frac * barHeight + 5)\n        .attr({ align: 'left', zIndex: 3 })\n        .css({ color: t.inkSoft, fontSize: '14px' })\n        .add()\n    );\n  });\n  drawn.push(\n    r\n      .text('Elevation (m)', barLeft + barWidth / 2, barTop - 18)\n      .attr({ align: 'center', zIndex: 3 })\n      .css({ color: t.inkSoft, fontSize: '15px', fontWeight: '500' })\n      .add()\n  );\n}\n\n// Sparse invisible scatter layer so hovering the surface still gives a real\n// Highcharts tooltip with the exact elevation at nearby grid points.\nconst SAMPLE_STRIDE = 4;\nconst samplePoints = [];\nfor (let j = 0; j < NY; j += SAMPLE_STRIDE) {\n  for (let i = 0; i < NX; i += SAMPLE_STRIDE) {\n    samplePoints.push({ x: xs[i], y: ys[j], elevation: Z[j][i] });\n  }\n}\n\nHighcharts.chart('container', {\n  chart: {\n    type: 'scatter',\n    backgroundColor: 'transparent',\n    animation: false,\n    style: { fontFamily: 'inherit' },\n    margin: [100, 220, 90, 100],\n    events: { load: drawAll, redraw: drawAll },\n  },\n  credits: { enabled: false },\n  title: {\n    text: TITLE_TEXT,\n    style: { color: t.ink, fontSize: TITLE_FS + 'px', fontWeight: '600' },\n  },\n  subtitle: {\n    text: 'Three overlapping Gaussian peaks sampled on a 40×40 grid, banded into 14 elevation levels',\n    style: { color: t.inkSoft, fontSize: '14px' },\n  },\n  xAxis: {\n    title: { text: 'X (km)', style: { color: t.inkSoft, fontSize: '16px' } },\n    min: X_MIN,\n    max: X_MAX,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: '14px' } },\n  },\n  yAxis: {\n    title: { text: 'Y (km)', style: { color: t.inkSoft, fontSize: '16px' } },\n    min: Y_MIN,\n    max: Y_MAX,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: '14px' } },\n  },\n  legend: { enabled: false },\n  tooltip: {\n    enabled: true,\n    backgroundColor: t.elevatedBg,\n    borderColor: t.inkSoft,\n    borderRadius: 6,\n    style: { color: t.ink, fontSize: '13px' },\n    formatter: function () {\n      const p = this.point;\n      return `<b>(${p.x.toFixed(1)}, ${p.y.toFixed(1)}) km</b><br/>Elevation: ${Math.round(p.elevation)} m`;\n    },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: {\n      enableMouseTracking: true,\n      stickyTracking: false,\n      marker: {\n        enabled: true,\n        symbol: 'circle',\n        radius: 14,\n        fillColor: 'rgba(0,0,0,0.001)',\n        lineWidth: 0,\n        states: { hover: { enabled: false } },\n      },\n    },\n  },\n  series: [\n    {\n      type: 'scatter',\n      name: 'Elevation sample',\n      data: samplePoints,\n      zIndex: 1,\n    },\n  ],\n});\n"}