{"spec_id":"facet-grid","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// facet-grid: Faceted Grid Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nfunction makeLcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\nfunction randNormal(mean, std) {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * std;\n}\nfunction linearRegression(points) {\n  const n = points.length;\n  const sumX = points.reduce((s, p) => s + p.x, 0);\n  const sumY = points.reduce((s, p) => s + p.y, 0);\n  const sumXY = points.reduce((s, p) => s + p.x * p.y, 0);\n  const sumXX = points.reduce((s, p) => s + p.x * p.x, 0);\n  const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);\n  const intercept = (sumY - slope * sumX) / n;\n  return { slope, intercept };\n}\n\nconst vehicleClasses = [\"Sedan\", \"SUV\"];\nconst drivetrains = [\"FWD\", \"AWD\", \"RWD\"];\nconst engineBaseByClass = { Sedan: 2.0, SUV: 3.2 };\nconst mpgPenaltyByDrivetrain = { FWD: 0, AWD: -3, RWD: -5 };\n\nconst POINTS_PER_FACET = 140;\nconst facets = [];\nfor (const vehicleClass of vehicleClasses) {\n  for (const drivetrain of drivetrains) {\n    const points = [];\n    for (let i = 0; i < POINTS_PER_FACET; i++) {\n      const engineSize = Math.max(\n        1.0,\n        randNormal(engineBaseByClass[vehicleClass], 0.6),\n      );\n      const baseMpg = 45 - engineSize * 6 + mpgPenaltyByDrivetrain[drivetrain];\n      const fuelEconomy = Math.max(8, baseMpg + randNormal(0, 2.5));\n      points.push({ x: engineSize, y: fuelEconomy });\n    }\n    facets.push({\n      row: vehicleClass,\n      col: drivetrain,\n      points,\n      trend: linearRegression(points),\n    });\n  }\n}\n\n// Shared axis range across every facet, per the spec's \"same axes scales by default\"\nconst allX = facets.flatMap((f) => f.points.map((p) => p.x));\nconst allY = facets.flatMap((f) => f.points.map((p) => p.y));\nconst xPad = (Math.max(...allX) - Math.min(...allX)) * 0.08;\nconst yPad = (Math.max(...allY) - Math.min(...allY)) * 0.08;\nconst X_MIN = Math.floor(Math.min(...allX) - xPad);\nconst X_MAX = Math.ceil(Math.max(...allX) + xPad);\nconst Y_MIN = Math.floor(Math.min(...allY) - yPad);\nconst Y_MAX = Math.ceil(Math.max(...allY) + yPad);\n\n// --- Scaffolding -------------------------------------------------------------\nconst style = document.createElement(\"style\");\nstyle.textContent = \"#container, #container * { box-sizing: border-box; }\";\ndocument.head.appendChild(style);\n\nconst container = document.getElementById(\"container\");\ncontainer.style.display = \"flex\";\ncontainer.style.flexDirection = \"column\";\ncontainer.style.padding = \"20px 28px 16px 28px\";\ncontainer.style.background = t.pageBg;\ncontainer.style.fontFamily =\n  \"system-ui, -apple-system, Helvetica, Arial, sans-serif\";\n\nconst title = document.createElement(\"div\");\ntitle.textContent = \"facet-grid · javascript · chartjs · anyplot.ai\";\ntitle.style.color = t.ink;\ntitle.style.fontSize = \"22px\";\ntitle.style.fontWeight = \"600\";\ntitle.style.textAlign = \"center\";\ntitle.style.marginBottom = \"14px\";\ncontainer.appendChild(title);\n\nconst body = document.createElement(\"div\");\nbody.style.display = \"flex\";\nbody.style.flex = \"1\";\nbody.style.minHeight = \"0\";\ncontainer.appendChild(body);\n\nconst yAxisLabel = document.createElement(\"div\");\nyAxisLabel.textContent = \"Fuel Economy (mpg)\";\nyAxisLabel.style.color = t.ink;\nyAxisLabel.style.fontSize = \"16px\";\nyAxisLabel.style.writingMode = \"vertical-rl\";\nyAxisLabel.style.transform = \"rotate(180deg)\";\nyAxisLabel.style.display = \"flex\";\nyAxisLabel.style.alignItems = \"center\";\nyAxisLabel.style.justifyContent = \"center\";\nyAxisLabel.style.padding = \"0 8px\";\nbody.appendChild(yAxisLabel);\n\nconst gridArea = document.createElement(\"div\");\ngridArea.style.display = \"flex\";\ngridArea.style.flexDirection = \"column\";\ngridArea.style.flex = \"1\";\ngridArea.style.minWidth = \"0\";\nbody.appendChild(gridArea);\n\n// Column strip labels (top), with a trailing spacer matching the row-label column\nconst colHeaderRow = document.createElement(\"div\");\ncolHeaderRow.style.display = \"flex\";\ncolHeaderRow.style.gap = \"6px\";\nfor (const drivetrain of drivetrains) {\n  const cell = document.createElement(\"div\");\n  cell.textContent = `Drivetrain: ${drivetrain}`;\n  cell.style.flex = \"1\";\n  cell.style.textAlign = \"center\";\n  cell.style.color = t.ink;\n  cell.style.fontSize = \"15px\";\n  cell.style.fontWeight = \"600\";\n  cell.style.background = t.elevatedBg;\n  cell.style.borderRadius = \"6px\";\n  cell.style.padding = \"5px 6px\";\n  cell.style.marginBottom = \"6px\";\n  colHeaderRow.appendChild(cell);\n}\nconst colHeaderSpacer = document.createElement(\"div\");\ncolHeaderSpacer.style.width = \"32px\";\ncolHeaderSpacer.style.flexShrink = \"0\";\ncolHeaderRow.appendChild(colHeaderSpacer);\ngridArea.appendChild(colHeaderRow);\n\nconst rowsWrap = document.createElement(\"div\");\nrowsWrap.style.display = \"flex\";\nrowsWrap.style.flexDirection = \"column\";\nrowsWrap.style.flex = \"1\";\nrowsWrap.style.minHeight = \"0\";\nrowsWrap.style.gap = \"6px\";\ngridArea.appendChild(rowsWrap);\n\nvehicleClasses.forEach((vehicleClass, rowIdx) => {\n  const rowDiv = document.createElement(\"div\");\n  rowDiv.style.display = \"flex\";\n  rowDiv.style.flex = \"1\";\n  rowDiv.style.minHeight = \"0\";\n  rowDiv.style.gap = \"6px\";\n\n  drivetrains.forEach((drivetrain, colIdx) => {\n    const cellWrap = document.createElement(\"div\");\n    cellWrap.style.position = \"relative\";\n    cellWrap.style.flex = \"1\";\n    cellWrap.style.minWidth = \"0\";\n    cellWrap.style.background = t.elevatedBg;\n    cellWrap.style.border = `1px solid ${t.grid}`;\n    cellWrap.style.borderRadius = \"8px\";\n    cellWrap.style.overflow = \"hidden\";\n\n    const canvas = document.createElement(\"canvas\");\n    cellWrap.appendChild(canvas);\n    rowDiv.appendChild(cellWrap);\n\n    const facet = facets.find(\n      (f) => f.row === vehicleClass && f.col === drivetrain,\n    );\n    const isLeftCol = colIdx === 0;\n    const isBottomRow = rowIdx === vehicleClasses.length - 1;\n    const { slope, intercept } = facet.trend;\n    const trendLine = [\n      { x: X_MIN, y: slope * X_MIN + intercept },\n      { x: X_MAX, y: slope * X_MAX + intercept },\n    ];\n\n    new Chart(canvas, {\n      type: \"scatter\",\n      data: {\n        datasets: [\n          {\n            label: \"points\",\n            data: facet.points,\n            backgroundColor: `${t.palette[0]}B3`,\n            pointRadius: 4,\n            pointHoverRadius: 5,\n            order: 1,\n          },\n          {\n            type: \"line\",\n            label: \"trend\",\n            data: trendLine,\n            borderColor: t.inkSoft,\n            borderWidth: 1.5,\n            borderDash: [6, 4],\n            pointRadius: 0,\n            pointHoverRadius: 0,\n            fill: false,\n            tension: 0,\n            order: 0,\n          },\n        ],\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: false,\n        animation: false,\n        plugins: {\n          legend: { display: false },\n          title: { display: false },\n          tooltip: {\n            filter: (item) => item.datasetIndex === 0,\n            callbacks: {\n              title: () => `${vehicleClass} · ${drivetrain}`,\n              label: (ctx) =>\n                `${ctx.parsed.x.toFixed(1)}L → ${ctx.parsed.y.toFixed(1)} mpg`,\n            },\n          },\n        },\n        scales: {\n          x: {\n            min: X_MIN,\n            max: X_MAX,\n            ticks: {\n              display: isBottomRow,\n              color: t.inkSoft,\n              font: { size: 13 },\n              maxTicksLimit: 5,\n            },\n            grid: { color: t.grid },\n          },\n          y: {\n            min: Y_MIN,\n            max: Y_MAX,\n            ticks: {\n              display: isLeftCol,\n              color: t.inkSoft,\n              font: { size: 13 },\n              maxTicksLimit: 5,\n            },\n            grid: { color: t.grid },\n          },\n        },\n      },\n    });\n  });\n\n  const rowLabel = document.createElement(\"div\");\n  rowLabel.textContent = `Class: ${vehicleClass}`;\n  rowLabel.style.width = \"32px\";\n  rowLabel.style.flexShrink = \"0\";\n  rowLabel.style.display = \"flex\";\n  rowLabel.style.alignItems = \"center\";\n  rowLabel.style.justifyContent = \"center\";\n  rowLabel.style.writingMode = \"vertical-rl\";\n  rowLabel.style.color = t.ink;\n  rowLabel.style.fontSize = \"15px\";\n  rowLabel.style.fontWeight = \"600\";\n  rowLabel.style.background = t.elevatedBg;\n  rowLabel.style.borderRadius = \"6px\";\n  rowDiv.appendChild(rowLabel);\n\n  rowsWrap.appendChild(rowDiv);\n});\n\nconst xAxisLabel = document.createElement(\"div\");\nxAxisLabel.textContent = \"Engine Size (L)\";\nxAxisLabel.style.color = t.ink;\nxAxisLabel.style.fontSize = \"16px\";\nxAxisLabel.style.textAlign = \"center\";\nxAxisLabel.style.marginTop = \"10px\";\ngridArea.appendChild(xAxisLabel);\n"}