{"spec_id":"contour-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// contour-basic: Basic Contour Plot\n// Library: highcharts 12.6.0 | JavaScript 22.22.3\n// Quality: 87/100 | Created: 2026-06-25\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (deterministic elevation: two peaks with saddle pass) --------------\nconst N = 80;\nconst xMin = -5, xMax = 5, yMin = -4, yMax = 4;\nconst xs = Array.from({ length: N }, (_, i) => xMin + (xMax - xMin) * i / (N - 1));\nconst ys = Array.from({ length: N }, (_, i) => yMin + (yMax - yMin) * i / (N - 1));\n\nfunction rawElev(x, y) {\n    const p1 = Math.exp(-((x - 1.4) ** 2 + (y - 0.9) ** 2) / 3.5);\n    const p2 = 0.84 * Math.exp(-((x + 1.6) ** 2 + (y + 0.5) ** 2) / 3.8);\n    const p3 = 0.40 * Math.exp(-(((x - 0.4) ** 2) / 6 + ((y - 2.6) ** 2) / 1.8));\n    return p1 + p2 + p3;\n}\n\nconst grid = [];\nlet zMin = Infinity, zMax = -Infinity;\nfor (let i = 0; i < N; i++) {\n    grid[i] = [];\n    for (let j = 0; j < N; j++) {\n        const z = 100 + 900 * rawElev(xs[i], ys[j]);\n        grid[i][j] = z;\n        if (z < zMin) zMin = z;\n        if (z > zMax) zMax = z;\n    }\n}\n\nconst NUM_LEVELS = 10;\nconst levels = Array.from({ length: NUM_LEVELS }, (_, k) =>\n    zMin + (k + 0.5) * (zMax - zMin) / NUM_LEVELS\n);\n\n// Color interpolation: Imprint seq (#009E73 → #4467A3)\nfunction lerpColor(c1, c2, tt) {\n    const parse = h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16));\n    const [r1, g1, b1] = parse(c1);\n    const [r2, g2, b2] = parse(c2);\n    return '#' + [r1 + tt * (r2 - r1), g1 + tt * (g2 - g1), b1 + tt * (b2 - b1)]\n        .map(v => Math.round(v).toString(16).padStart(2, '0')).join('');\n}\n\n// Marching squares: return list of [[x1,y1],[x2,y2]] segment pairs\nfunction contourSegments(level) {\n    const segs = [];\n    for (let i = 0; i < N - 1; i++) {\n        for (let j = 0; j < N - 1; j++) {\n            const a = grid[i][j],     b = grid[i + 1][j];\n            const c = grid[i + 1][j + 1], d = grid[i][j + 1];\n            const code = (a >= level ? 1 : 0) | (b >= level ? 2 : 0) |\n                         (c >= level ? 4 : 0) | (d >= level ? 8 : 0);\n            if (code === 0 || code === 15) continue;\n\n            function ep(x1, y1, v1, x2, y2, v2) {\n                const s = (level - v1) / (v2 - v1);\n                return [x1 + s * (x2 - x1), y1 + s * (y2 - y1)];\n            }\n\n            const eAB = ep(xs[i], ys[j], a, xs[i + 1], ys[j], b);\n            const eBC = ep(xs[i + 1], ys[j], b, xs[i + 1], ys[j + 1], c);\n            const eCD = ep(xs[i + 1], ys[j + 1], c, xs[i], ys[j + 1], d);\n            const eDA = ep(xs[i], ys[j + 1], d, xs[i], ys[j], a);\n\n            const lookup = {\n                1: [[eDA, eAB]], 14: [[eDA, eAB]],\n                2: [[eAB, eBC]], 13: [[eAB, eBC]],\n                3: [[eDA, eBC]], 12: [[eDA, eBC]],\n                4: [[eBC, eCD]], 11: [[eBC, eCD]],\n                6: [[eAB, eCD]],  9: [[eAB, eCD]],\n                7: [[eDA, eCD]],  8: [[eDA, eCD]],\n                5: [[eDA, eAB], [eBC, eCD]],\n                10: [[eDA, eCD], [eAB, eBC]],\n            }[code];\n\n            if (lookup) segs.push(...lookup);\n        }\n    }\n    return segs;\n}\n\n// Dummy series for legend\nconst legendSeries = levels.map((level, k) => ({\n    type: 'line',\n    name: Math.round(level) + ' m',\n    color: lerpColor(t.seq[0], t.seq[1], k / (NUM_LEVELS - 1)),\n    data: [],\n    lineWidth: 2,\n    marker: { enabled: false },\n    showInLegend: true,\n    enableMouseTracking: false,\n}));\n\n// --- Chart ------------------------------------------------------------------\nconst chart = Highcharts.chart('container', {\n    chart: {\n        backgroundColor: 'transparent',\n        animation: false,\n        style: { fontFamily: 'inherit' },\n    },\n    credits: { enabled: false },\n    title: {\n        text: 'contour-basic · javascript · highcharts · anyplot.ai',\n        style: { color: t.ink, fontSize: '22px', fontWeight: '600' },\n    },\n    xAxis: {\n        min: xMin,\n        max: xMax,\n        title: { text: 'X (km)', style: { color: t.inkSoft, fontSize: '16px' } },\n        lineWidth: 0,\n        tickWidth: 0,\n        gridLineColor: t.grid,\n        gridLineWidth: 1,\n        labels: { style: { color: t.inkSoft, fontSize: '14px' } },\n    },\n    yAxis: {\n        min: yMin,\n        max: yMax,\n        title: { text: 'Y (km)', style: { color: t.inkSoft, fontSize: '16px' } },\n        lineWidth: 0,\n        tickWidth: 0,\n        gridLineColor: t.grid,\n        gridLineWidth: 1,\n        labels: { style: { color: t.inkSoft, fontSize: '14px' } },\n    },\n    legend: {\n        title: { text: 'Elevation', style: { color: t.inkSoft, fontSize: '14px' } },\n        itemStyle: { color: t.inkSoft, fontSize: '14px' },\n        itemHoverStyle: { color: t.ink },\n        layout: 'vertical',\n        align: 'right',\n        verticalAlign: 'middle',\n    },\n    tooltip: { enabled: false },\n    plotOptions: {\n        series: { animation: false },\n    },\n    series: legendSeries,\n});\n\n// --- Draw contour lines via SVG renderer ------------------------------------\nconst clipRect = chart.renderer.clipRect(\n    chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight\n);\nconst contourGroup = chart.renderer.g('contour-lines').clip(clipRect).add();\n\n// Pre-compute all segments\nconst allSegs = levels.map(level => contourSegments(level));\n\n// Draw lines\nallSegs.forEach((segs, k) => {\n    const color = lerpColor(t.seq[0], t.seq[1], k / (NUM_LEVELS - 1));\n    segs.forEach(([p1, p2]) => {\n        const px1 = chart.xAxis[0].toPixels(p1[0]);\n        const py1 = chart.yAxis[0].toPixels(p1[1]);\n        const px2 = chart.xAxis[0].toPixels(p2[0]);\n        const py2 = chart.yAxis[0].toPixels(p2[1]);\n        chart.renderer.path(['M', px1, py1, 'L', px2, py2])\n            .attr({ stroke: color, 'stroke-width': 1.5, zIndex: 5 })\n            .add(contourGroup);\n    });\n});\n\n// --- Add elevation labels on every other contour line ----------------------\n// Spread label target zones across the plot to avoid collisions\nconst labelZones = [\n    { tx: -3.0, ty:  2.5 },\n    { tx:  0.0, ty: -2.0 },\n    { tx:  3.0, ty:  0.5 },\n    { tx: -2.5, ty: -1.0 },\n    { tx:  2.0, ty:  2.0 },\n];\n\nconst labelGroup = chart.renderer.g('contour-labels').clip(clipRect).add();\n\nallSegs.forEach((segs, k) => {\n    if (k % 2 !== 0 || !segs.length) return;\n\n    const zone = labelZones[(k / 2) % labelZones.length];\n    let best = segs[0], bestDist = Infinity;\n    segs.forEach(([p1, p2]) => {\n        const mx = (p1[0] + p2[0]) / 2;\n        const my = (p1[1] + p2[1]) / 2;\n        const dist = (mx - zone.tx) ** 2 + (my - zone.ty) ** 2;\n        if (dist < bestDist) { bestDist = dist; best = [p1, p2]; }\n    });\n\n    const mx = (best[0][0] + best[1][0]) / 2;\n    const my = (best[0][1] + best[1][1]) / 2;\n    const px = chart.xAxis[0].toPixels(mx);\n    const py = chart.yAxis[0].toPixels(my);\n\n    const color = lerpColor(t.seq[0], t.seq[1], k / (NUM_LEVELS - 1));\n    const labelText = Math.round(levels[k]) + 'm';\n\n    // Pill background for readability\n    chart.renderer.rect(px - 23, py - 12, 46, 15, 3)\n        .attr({ fill: t.pageBg, opacity: 0.88, zIndex: 9 })\n        .add(labelGroup);\n\n    chart.renderer.text(labelText, px, py)\n        .attr({ align: 'center', zIndex: 10 })\n        .css({ color, fontSize: '11px', fontWeight: '700' })\n        .add(labelGroup);\n});\n"}