{"spec_id":"point-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// point-basic: Point Estimate Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Standardized regression coefficients predicting home sale price, with 95%\n// confidence intervals. Sorted by effect size so the strongest positive\n// predictor sits on top, mirroring a typical forest-plot reading order.\nconst coefficients = [\n  { predictor: \"Square Footage\", estimate: 0.42, lower: 0.35, upper: 0.49 },\n  { predictor: \"Renovated\", estimate: 0.31, lower: 0.19, upper: 0.43 },\n  { predictor: \"Bathrooms\", estimate: 0.18, lower: 0.09, upper: 0.27 },\n  { predictor: \"Garage Spaces\", estimate: 0.12, lower: 0.02, upper: 0.22 },\n  { predictor: \"Lot Size\", estimate: 0.08, lower: -0.03, upper: 0.19 },\n  { predictor: \"Age of Home\", estimate: -0.15, lower: -0.24, upper: -0.06 },\n  { predictor: \"Distance to Downtown\", estimate: -0.22, lower: -0.31, upper: -0.13 },\n];\n\nconst labels = coefficients.map((d) => d.predictor);\n\nconst withAlpha = (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};\n\n// --- Title (length-scaled per anyplot title rules) -------------------------\nconst title = \"House Price Model Coefficients · point-basic · javascript · chartjs · anyplot.ai\";\nconst titleFontSize = title.length > 67 ? Math.max(14, Math.round(22 * (67 / title.length))) : 22;\n\n// --- Custom draw plugins (native Chart.js plugin API, no external deps) ----\n// Dashed reference line at the null-effect value (x = 0).\nconst referenceLinePlugin = {\n  id: \"referenceLine\",\n  beforeDatasetsDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    const xZero = scales.x.getPixelForValue(0);\n    if (xZero < chartArea.left || xZero > chartArea.right) return;\n    ctx.save();\n    ctx.strokeStyle = t.inkSoft;\n    ctx.lineWidth = 1.5;\n    ctx.setLineDash([6, 6]);\n    ctx.beginPath();\n    ctx.moveTo(xZero, chartArea.top);\n    ctx.lineTo(xZero, chartArea.bottom);\n    ctx.stroke();\n    ctx.restore();\n  },\n};\n\n// Caps at each confidence-interval endpoint, derived from the floating bar's\n// real pixel bounds (chart.getDatasetMeta), not decorative marks.\nconst errorCapsPlugin = {\n  id: \"errorCaps\",\n  afterDatasetsDraw(chart) {\n    const meta = chart.getDatasetMeta(0);\n    const ctx = chart.ctx;\n    const capHalf = 9;\n    ctx.save();\n    ctx.strokeStyle = t.palette[0];\n    ctx.lineWidth = 2.5;\n    meta.data.forEach((bar) => {\n      [bar.x, bar.base].forEach((px) => {\n        ctx.beginPath();\n        ctx.moveTo(px, bar.y - capHalf);\n        ctx.lineTo(px, bar.y + capHalf);\n        ctx.stroke();\n      });\n    });\n    ctx.restore();\n  },\n};\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart (mixed bar + line: floating bar draws the CI, line draws the point estimate marker only)\nnew Chart(canvas, {\n  data: {\n    labels,\n    datasets: [\n      {\n        type: \"bar\",\n        label: \"95% CI\",\n        data: coefficients.map((d) => [d.lower, d.upper]),\n        backgroundColor: withAlpha(t.palette[0], 0.35),\n        barThickness: 6,\n        borderWidth: 0,\n      },\n      {\n        type: \"line\",\n        label: \"Estimate\",\n        data: coefficients.map((d) => d.estimate),\n        showLine: false,\n        pointStyle: \"circle\",\n        pointRadius: 9,\n        pointBackgroundColor: t.palette[0],\n        pointBorderColor: t.pageBg,\n        pointBorderWidth: 2,\n      },\n    ],\n  },\n  options: {\n    indexAxis: \"y\",\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 8, right: 24, bottom: 8, left: 8 } },\n    plugins: {\n      title: { display: true, text: title, color: t.ink, font: { size: titleFontSize, weight: \"medium\" } },\n      legend: { display: false },\n    },\n    scales: {\n      x: {\n        title: { display: true, text: \"Standardized Coefficient (95% CI)\", color: t.ink, font: { size: 16 } },\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n      },\n    },\n  },\n  plugins: [referenceLinePlugin, errorCapsPlugin],\n});\n"}