{"spec_id":"density-rug","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// density-rug: Density Plot with Rug Marks\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Support-ticket first-response times (minutes): a fast \"auto-triaged\" cohort\n// and a slower \"needs a human\" cohort — a realistic bimodal shape.\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1103515245 + 12345) & 0x7fffffff;\n    return state / 0x7fffffff;\n  };\n}\nconst rand = lcg(42);\nconst jitterRand = lcg(7);\nfunction gaussian() {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst responseTimes = [];\nfor (let i = 0; i < 110; i++) {\n  responseTimes.push(Math.max(0.2, 2.6 + gaussian() * 0.55));\n}\nfor (let i = 0; i < 70; i++) {\n  responseTimes.push(Math.max(0.2, 6.3 + gaussian() * 1.15));\n}\n\n// --- Kernel density estimate (Gaussian kernel, Silverman bandwidth) --------\nconst n = responseTimes.length;\nconst mean = responseTimes.reduce((a, b) => a + b, 0) / n;\nconst variance =\n  responseTimes.reduce((a, b) => a + (b - mean) ** 2, 0) / (n - 1);\nconst std = Math.sqrt(variance);\nconst bandwidth = 1.06 * std * n ** (-1 / 5);\n\nfunction gaussianKernel(u) {\n  return Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI);\n}\nfunction density(x) {\n  const sum = responseTimes.reduce(\n    (acc, xi) => acc + gaussianKernel((x - xi) / bandwidth),\n    0,\n  );\n  return sum / (n * bandwidth);\n}\n\nconst dataMin = Math.min(...responseTimes);\nconst dataMax = Math.max(...responseTimes);\nconst gridMin = Math.max(0, dataMin - 3 * bandwidth);\nconst gridMax = dataMax + 3 * bandwidth;\nconst gridSteps = 200;\nconst curve = Array.from({ length: gridSteps + 1 }, (_, i) => {\n  const x = gridMin + ((gridMax - gridMin) * i) / gridSteps;\n  return { x, y: density(x) };\n});\nconst peakDensity = Math.max(...curve.map((p) => p.y));\n\n// Two tallest local maxima, ordered by x, call out the fast/slow cohorts.\nfunction localMaxima(points) {\n  const maxima = [];\n  for (let i = 1; i < points.length - 1; i++) {\n    if (points[i].y > points[i - 1].y && points[i].y > points[i + 1].y) {\n      maxima.push(points[i]);\n    }\n  }\n  return maxima;\n}\nconst modes = localMaxima(curve)\n  .sort((a, b) => b.y - a.y)\n  .slice(0, 2)\n  .sort((a, b) => a.x - b.x);\nconst modeLabels = [\"Auto-triaged\", \"Needs a human\"];\n\n// --- Color helpers -----------------------------------------------------------\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\nconst brand = t.palette[0];\n\n// --- Mount --------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Rug marks plugin (core Chart.js plugin API — no external dependency) ---\nconst rugTickHeight = 22;\nconst rugPlugin = {\n  id: \"rugMarks\",\n  afterDatasetsDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    const xScale = scales.x;\n    ctx.save();\n    ctx.strokeStyle = hexToRgba(brand, 0.3);\n    ctx.lineWidth = 1.5;\n    responseTimes.forEach((value) => {\n      const xPixel = xScale.getPixelForValue(value);\n      // Stagger tick height so overlapping ticks in the two dense clusters\n      // don't merge into a solid block.\n      const tickHeight = rugTickHeight + (jitterRand() - 0.5) * 10;\n      ctx.beginPath();\n      ctx.moveTo(xPixel, chartArea.bottom);\n      ctx.lineTo(xPixel, chartArea.bottom - tickHeight);\n      ctx.stroke();\n    });\n    ctx.restore();\n  },\n};\n\n// --- Mode guides plugin (dashed callouts at the two KDE peaks) --------------\nconst modeGuidesPlugin = {\n  id: \"modeGuides\",\n  afterDatasetsDraw(chart) {\n    if (modes.length < 2) return;\n    const { ctx, chartArea, scales } = chart;\n    const xScale = scales.x;\n    const yScale = scales.y;\n    ctx.save();\n    ctx.strokeStyle = hexToRgba(t.inkSoft, 0.5);\n    ctx.lineWidth = 1;\n    ctx.setLineDash([4, 4]);\n    ctx.font = \"12px sans-serif\";\n    ctx.fillStyle = t.inkSoft;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"top\";\n    const lineTop = chartArea.top + 16;\n    modes.forEach((mode, i) => {\n      const xPixel = xScale.getPixelForValue(mode.x);\n      const yPixel = yScale.getPixelForValue(mode.y);\n      ctx.beginPath();\n      ctx.moveTo(xPixel, lineTop);\n      ctx.lineTo(xPixel, yPixel);\n      ctx.stroke();\n      ctx.fillText(modeLabels[i], xPixel, chartArea.top);\n    });\n    ctx.restore();\n  },\n};\n\n// --- Chart ---------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    datasets: [\n      {\n        label: \"Density estimate\",\n        data: curve,\n        parsing: false,\n        borderColor: brand,\n        backgroundColor: hexToRgba(brand, 0.2),\n        borderWidth: 3,\n        fill: \"origin\",\n        tension: 0.3,\n        pointRadius: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 4, bottom: 4 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"density-rug · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n        padding: { bottom: 20 },\n      },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: gridMin,\n        max: gridMax,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n        border: { color: t.inkSoft },\n        title: {\n          display: true,\n          text: \"First Response Time (minutes)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n      y: {\n        beginAtZero: true,\n        suggestedMax: peakDensity * 1.2,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        border: { display: false },\n        title: {\n          display: true,\n          text: \"Density\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n    },\n  },\n  plugins: [rugPlugin, modeGuidesPlugin],\n});\n"}