{"spec_id":"contour-density","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// contour-density: Density Contour Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-04\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: reactor temperature vs. pressure readings, two operating modes ---\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction gaussian() {\n  const u1 = Math.max(lcg(), 1e-9);\n  const u2 = lcg();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst stableCount = 650;\nconst highLoadCount = 450;\nconst temperature = [];\nconst pressure = [];\nfor (let i = 0; i < stableCount; i++) {\n  temperature.push(72 + gaussian() * 3.2); // stable operation\n  pressure.push(4.2 + gaussian() * 0.55);\n}\nfor (let i = 0; i < highLoadCount; i++) {\n  temperature.push(85 + gaussian() * 3.8); // high-load operation\n  pressure.push(6.1 + gaussian() * 0.65);\n}\n\n// Cluster centroids (sample means), used to place the operating-mode labels.\nconst mean = (arr) => arr.reduce((s, v) => s + v, 0) / arr.length;\nconst stableCentroid = {\n  x: mean(temperature.slice(0, stableCount)),\n  y: mean(pressure.slice(0, stableCount)),\n};\nconst highLoadCentroid = {\n  x: mean(temperature.slice(stableCount)),\n  y: mean(pressure.slice(stableCount)),\n};\n\n// --- Kernel density estimate on a grid --------------------------------------\nconst xMin = Math.min(...temperature) - 3;\nconst xMax = Math.max(...temperature) + 3;\nconst yMin = Math.min(...pressure) - 0.5;\nconst yMax = Math.max(...pressure) + 0.5;\nconst hx = (xMax - xMin) / 14;\nconst hy = (yMax - yMin) / 14;\n\nconst nx = 60;\nconst ny = 60;\nconst xs = Array.from({ length: nx }, (_, i) => xMin + (i * (xMax - xMin)) / (nx - 1));\nconst ys = Array.from({ length: ny }, (_, j) => yMin + (j * (yMax - yMin)) / (ny - 1));\n\nconst grid = Array.from({ length: ny }, () => new Array(nx).fill(0));\nfor (let j = 0; j < ny; j++) {\n  for (let i = 0; i < nx; i++) {\n    let density = 0;\n    for (let k = 0; k < temperature.length; k++) {\n      const dx = (xs[i] - temperature[k]) / hx;\n      const dy = (ys[j] - pressure[k]) / hy;\n      density += Math.exp(-0.5 * (dx * dx + dy * dy));\n    }\n    grid[j][i] = density / temperature.length;\n  }\n}\nconst maxDensity = Math.max(...grid.map((row) => Math.max(...row)));\n\n// =============================================================================\n// Marching-squares geometry helpers (extract iso-density line segments)\n// =============================================================================\nfunction edgeInterp(level, va, pa, vb, pb) {\n  const denom = vb - va;\n  const frac = denom === 0 ? 0.5 : (level - va) / denom;\n  return { x: pa.x + frac * (pb.x - pa.x), y: pa.y + frac * (pb.y - pa.y) };\n}\n\nfunction marchingSquares(level) {\n  const segments = [];\n  for (let j = 0; j < ny - 1; j++) {\n    for (let i = 0; i < nx - 1; i++) {\n      const a = grid[j][i]; // bottom-left\n      const b = grid[j][i + 1]; // bottom-right\n      const c = grid[j + 1][i + 1]; // top-right\n      const d = grid[j + 1][i]; // top-left\n      let idx = 0;\n      if (a > level) idx |= 1;\n      if (b > level) idx |= 2;\n      if (c > level) idx |= 4;\n      if (d > level) idx |= 8;\n      if (idx === 0 || idx === 15) continue;\n\n      const bl = { x: xs[i], y: ys[j] };\n      const br = { x: xs[i + 1], y: ys[j] };\n      const tr = { x: xs[i + 1], y: ys[j + 1] };\n      const tl = { x: xs[i], y: ys[j + 1] };\n      const bottom = () => edgeInterp(level, a, bl, b, br);\n      const right = () => edgeInterp(level, b, br, c, tr);\n      const top = () => edgeInterp(level, d, tl, c, tr);\n      const left = () => edgeInterp(level, a, bl, d, tl);\n\n      // Standard 16-case marching-squares table (cases 5 and 10 are the\n      // ambiguous saddle points, resolved with two crossing segments).\n      switch (idx) {\n        case 1:\n          segments.push([left(), bottom()]);\n          break;\n        case 2:\n          segments.push([bottom(), right()]);\n          break;\n        case 3:\n          segments.push([left(), right()]);\n          break;\n        case 4:\n          segments.push([right(), top()]);\n          break;\n        case 5:\n          segments.push([left(), top()], [bottom(), right()]);\n          break;\n        case 6:\n          segments.push([bottom(), top()]);\n          break;\n        case 7:\n          segments.push([left(), top()]);\n          break;\n        case 8:\n          segments.push([top(), left()]);\n          break;\n        case 9:\n          segments.push([bottom(), top()]);\n          break;\n        case 10:\n          segments.push([left(), bottom()], [right(), top()]);\n          break;\n        case 11:\n          segments.push([right(), top()]);\n          break;\n        case 12:\n          segments.push([left(), right()]);\n          break;\n        case 13:\n          segments.push([bottom(), right()]);\n          break;\n        case 14:\n          segments.push([left(), bottom()]);\n          break;\n      }\n    }\n  }\n  return segments;\n}\n// =============================================================================\n\n// --- Sequential Imprint gradient (imprint_seq) for the density levels ------\nfunction hexToRgb(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\nfunction lerpColor(hex1, hex2, frac) {\n  const [r1, g1, b1] = hexToRgb(hex1);\n  const [r2, g2, b2] = hexToRgb(hex2);\n  const r = Math.round(r1 + (r2 - r1) * frac);\n  const g = Math.round(g1 + (g2 - g1) * frac);\n  const b = Math.round(b1 + (b2 - b1) * frac);\n  return `rgb(${r}, ${g}, ${b})`;\n}\n\nconst levelFractions = [0.12, 0.3, 0.5, 0.7, 0.88];\nconst contourDatasets = levelFractions.map((frac) => {\n  const segments = marchingSquares(frac * maxDensity);\n  const points = [];\n  segments.forEach(([p1, p2]) => points.push(p1, p2, { x: NaN, y: NaN }));\n  return {\n    type: \"line\",\n    label: `${Math.round(frac * 100)}% density`,\n    data: points,\n    borderColor: lerpColor(t.seq[0], t.seq[1], frac),\n    borderWidth: 2.5,\n    pointRadius: 0,\n    fill: false,\n    tension: 0,\n    spanGaps: false,\n  };\n});\n\nconst rawReadings = {\n  type: \"scatter\",\n  label: \"Process readings\",\n  data: temperature.map((value, i) => ({ x: value, y: pressure[i] })),\n  backgroundColor: `${t.inkSoft}59`,\n  borderWidth: 0,\n  pointRadius: 2.5,\n};\n\n// --- Custom plugin: name the two operating-mode clusters --------------------\n// Uses Chart.js's own public plugin hook (afterDatasetsDraw) — no external\n// annotation package, just the core Canvas 2D API drawn onto the chart ctx.\nconst clusterLabelPlugin = {\n  id: \"clusterLabels\",\n  afterDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    const labels = [\n      { text: \"Stable operation\", point: stableCentroid },\n      { text: \"High-load operation\", point: highLoadCentroid },\n    ];\n    ctx.save();\n    ctx.font = \"600 13px sans-serif\";\n    ctx.fillStyle = t.ink;\n    ctx.textAlign = \"center\";\n    labels.forEach(({ text, point }) => {\n      const px = scales.x.getPixelForValue(point.x);\n      const py = scales.y.getPixelForValue(point.y) - 55;\n      ctx.fillText(text, px, py);\n    });\n    ctx.restore();\n  },\n};\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: { datasets: [rawReadings, ...contourDatasets] },\n  plugins: [clusterLabelPlugin],\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"contour-density · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n      },\n      subtitle: {\n        display: true,\n        text: \"Contours light green → blue as point density rises\",\n        color: t.inkSoft,\n        font: { size: 14 },\n        padding: { bottom: 12 },\n      },\n      legend: {\n        display: true,\n        position: \"right\",\n        labels: {\n          color: t.inkSoft,\n          font: { size: 12 },\n          boxWidth: 20,\n          boxHeight: 3,\n          filter: (item, data) => data.datasets[item.datasetIndex].type === \"line\",\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        title: { display: true, text: \"Reactor Temperature (°C)\", color: t.ink, font: { size: 16 } },\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n      },\n      y: {\n        type: \"linear\",\n        title: { display: true, text: \"Reactor Pressure (bar)\", color: t.ink, font: { size: 16 } },\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n      },\n    },\n  },\n});\n"}