{"spec_id":"facet-grid","library":"echarts","language":"javascript","code":"// anyplot.ai\n// facet-grid: Faceted Grid Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic, penguin-style measurements) -----------\n// Bill length (mm) vs. body mass (g), faceted by species (columns) and\n// sex (rows) — mirrors the Palmer Penguins scenario suggested by the spec.\nfunction makeLcg(seed) {\n  let state = seed;\n  return function () {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rng = makeLcg(42);\n\nfunction randNormal(mean, sd) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * sd;\n}\n\nconst species = [\"Adelie\", \"Chinstrap\", \"Gentoo\"];\nconst sexes = [\"Female\", \"Male\"];\nconst speciesStats = {\n  Adelie: { bill: 38.8, billSd: 2.2, mass: 3550, massSd: 380 },\n  Chinstrap: { bill: 48.8, billSd: 3.0, mass: 3730, massSd: 380 },\n  Gentoo: { bill: 47.5, billSd: 3.1, mass: 5076, massSd: 500 },\n};\nconst sexEffect = {\n  Female: { bill: -1.0, mass: -300 },\n  Male: { bill: 1.0, mass: 300 },\n};\nconst pointsPerGroup = 60;\n\n// grid order is row-major: row = sex index, col = species index\nconst facetData = [];\nfor (const sex of sexes) {\n  for (const sp of species) {\n    const stats = speciesStats[sp];\n    const effect = sexEffect[sex];\n    const points = [];\n    for (let i = 0; i < pointsPerGroup; i++) {\n      const bill = randNormal(stats.bill + effect.bill, stats.billSd);\n      const mass = randNormal(stats.mass + effect.mass, stats.massSd);\n      points.push([bill, mass]);\n    }\n    facetData.push(points);\n  }\n}\n\n// --- Layout (CSS px within the 1600x900 landscape mount) --------------------\nconst gridLeft = 110;\nconst gridRight = 1540;\nconst gridTop = 100;\nconst gridBottom = 830;\nconst colGap = 20;\nconst rowGap = 20;\nconst colWidth = (gridRight - gridLeft - 2 * colGap) / 3;\nconst rowHeight = (gridBottom - gridTop - rowGap) / 2;\nconst colLefts = [0, 1, 2].map((c) => gridLeft + c * (colWidth + colGap));\nconst rowTops = [0, 1].map((r) => gridTop + r * (rowHeight + rowGap));\n\nconst X_MIN = 28;\nconst X_MAX = 60;\nconst Y_MIN = 2000;\nconst Y_MAX = 7000;\n\nconst grids = [];\nconst xAxes = [];\nconst yAxes = [];\nconst series = [];\n\nfor (let r = 0; r < sexes.length; r++) {\n  for (let c = 0; c < species.length; c++) {\n    const idx = r * species.length + c;\n    grids.push({\n      left: colLefts[c],\n      top: rowTops[r],\n      width: colWidth,\n      height: rowHeight,\n    });\n    xAxes.push({\n      gridIndex: idx,\n      type: \"value\",\n      min: X_MIN,\n      max: X_MAX,\n      interval: 8,\n      axisLabel: {\n        show: r === sexes.length - 1,\n        color: t.inkSoft,\n        fontSize: 13,\n      },\n      axisLine: { lineStyle: { color: t.inkSoft } },\n      axisTick: { show: false },\n      splitLine: { show: true, lineStyle: { color: t.grid } },\n    });\n    yAxes.push({\n      gridIndex: idx,\n      type: \"value\",\n      min: Y_MIN,\n      max: Y_MAX,\n      interval: 1250,\n      axisLabel: {\n        show: c === 0,\n        color: t.inkSoft,\n        fontSize: 13,\n        formatter: (v) => `${v / 1000}k`,\n      },\n      axisLine: { lineStyle: { color: t.inkSoft } },\n      axisTick: { show: false },\n      splitLine: { show: true, lineStyle: { color: t.grid } },\n    });\n    series.push({\n      type: \"scatter\",\n      xAxisIndex: idx,\n      yAxisIndex: idx,\n      data: facetData[idx],\n      symbolSize: 9,\n      itemStyle: { color: t.palette[0], opacity: 0.75 },\n    });\n  }\n}\n\n// --- Facet strip labels + shared axis titles (graphic overlay) -------------\nconst graphic = [];\nspecies.forEach((sp, c) => {\n  graphic.push({\n    type: \"text\",\n    left: colLefts[c] + colWidth / 2,\n    top: 78,\n    style: {\n      text: sp,\n      fill: t.ink,\n      font: \"600 15px sans-serif\",\n      align: \"center\",\n      backgroundColor: t.elevatedBg,\n      padding: [6, 10],\n    },\n  });\n});\nsexes.forEach((sex, r) => {\n  graphic.push({\n    type: \"text\",\n    left: gridRight + 12,\n    top: rowTops[r] + rowHeight / 2,\n    rotation: Math.PI / 2,\n    style: {\n      text: sex,\n      fill: t.ink,\n      font: \"600 15px sans-serif\",\n      align: \"center\",\n      backgroundColor: t.elevatedBg,\n      padding: [6, 10],\n    },\n  });\n});\ngraphic.push({\n  type: \"text\",\n  left: (gridLeft + gridRight) / 2,\n  top: 865,\n  style: {\n    text: \"Bill Length (mm)\",\n    fill: t.ink,\n    font: \"14px sans-serif\",\n    align: \"center\",\n  },\n});\ngraphic.push({\n  type: \"text\",\n  left: 30,\n  top: (gridTop + gridBottom) / 2,\n  rotation: -Math.PI / 2,\n  style: {\n    text: \"Body Mass (g)\",\n    fill: t.ink,\n    font: \"14px sans-serif\",\n    align: \"center\",\n  },\n});\n\n// --- Init + Option ------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"Penguin Measurements · facet-grid · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 12,\n    textStyle: { color: t.ink, fontSize: 21 },\n  },\n  grid: grids,\n  xAxis: xAxes,\n  yAxis: yAxes,\n  series: series,\n  graphic: graphic,\n});\n"}