{"spec_id":"ice-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// ice-basic: Individual Conditional Expectation (ICE) Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-17\n//# anyplot-orientation: landscape\n// anyplot.ai\n// ice-basic: Individual Conditional Expectation (ICE) Plot\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-08-17\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// GradientBoostingRegressor-style house-price model: one ICE curve per house\n// shows how the predicted price responds to square footage for that specific\n// house, holding its other features fixed. A minority \"luxury\" subgroup\n// exhibits diminishing returns at large square footage (a feature\n// interaction the averaged PDP line alone would hide).\nfunction mulberry32(seed) {\n  return function rng() {\n    seed |= 0;\n    seed = (seed + 0x6d2b79f5) | 0;\n    let x = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n    x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;\n    return ((x ^ (x >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nfunction gaussian(rng, mean, std) {\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 * std;\n}\n\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}\n\nconst rng = mulberry32(42);\nconst N_OBSERVATIONS = 70;\nconst N_GRID_POINTS = 55;\nconst SQFT_MIN = 900;\nconst SQFT_MAX = 3800;\n\nconst featureGrid = Array.from(\n  { length: N_GRID_POINTS },\n  (_, i) => SQFT_MIN + (i * (SQFT_MAX - SQFT_MIN)) / (N_GRID_POINTS - 1),\n);\n\nconst iceCurves = Array.from({ length: N_OBSERVATIONS }, (_, houseId) => {\n  const basePrice = gaussian(rng, 210000, 22000);\n  const pricePerSqft = gaussian(rng, 145, 22);\n  const isLuxurySubgroup = rng() < 0.25;\n  const curvature = isLuxurySubgroup\n    ? gaussian(rng, -0.028, 0.006)\n    : gaussian(rng, -0.003, 0.004);\n\n  const predictions = featureGrid.map((sqft) => {\n    const delta = sqft - SQFT_MIN;\n    return basePrice + pricePerSqft * delta + curvature * delta * delta;\n  });\n\n  return { houseId, isLuxurySubgroup, predictions };\n});\n\nconst partialDependence = featureGrid.map((_, gridIndex) => {\n  const sum = iceCurves.reduce((acc, curve) => acc + curve.predictions[gridIndex], 0);\n  return sum / iceCurves.length;\n});\n\n// Color-code the ~25% luxury subgroup (ochre) against the standard majority\n// (brand green) so the diminishing-returns interaction is visible directly\n// from line color, not only inferable from where curves happen to diverge.\nconst STANDARD_COLOR = hexToRgba(t.palette[0], 0.18);\nconst LUXURY_COLOR = hexToRgba(t.palette[3], 0.35);\n// Sorted standard-before-luxury so series[0] stays brand green, matching the\n// style guide's \"first series is always #009E73\" rule.\nconst orderedCurves = [\n  ...iceCurves.filter((curve) => !curve.isLuxurySubgroup),\n  ...iceCurves.filter((curve) => curve.isLuxurySubgroup),\n];\nconst firstStandardIndex = orderedCurves.findIndex((curve) => !curve.isLuxurySubgroup);\nconst firstLuxuryIndex = orderedCurves.findIndex((curve) => curve.isLuxurySubgroup);\n// Reference-line x position: where the luxury subgroup's diminishing-returns\n// curvature starts to visibly pull away from the standard trend.\nconst DIVERGENCE_SQFT = SQFT_MIN + 0.7 * (SQFT_MAX - SQFT_MIN);\n\n// --- Title (mandated format, fontsize scaled to length) ---------------------\nconst TITLE = \"House Price Predictions · ice-basic · javascript · muix · anyplot.ai\";\n// Base of 32 (vs. the generic 22px JS-library default) because this title is\n// rendered in a plain flex div, not SVG-measured MUI X chrome — the plain-div\n// layout reads visually smaller at the same fontsize than the library's own\n// title component, so it needs a larger base to hit the expected ~70-85% of\n// canvas width.\nconst TITLE_FONT_SIZE = Math.round(32 * Math.min(1, 67 / TITLE.length));\nconst TITLE_ROW_HEIGHT = 56;\nconst FONT_FAMILY = \"Roboto, Helvetica, Arial, sans-serif\";\nconst AXIS_LABEL_FONT_SIZE = 16;\n// MUI X positions the native yAxis label at a fixed offset from the axis\n// line, not from the (variable-width) tick label text, so a wide tick label\n// like \"$800k\" collides with it. Render the y-axis title ourselves in a\n// dedicated column instead.\nconst Y_LABEL_COLUMN_WIDTH = 32;\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  const chartHeight = window.ANYPLOT_SIZE.height - TITLE_ROW_HEIGHT;\n  const chartWidth = window.ANYPLOT_SIZE.width - Y_LABEL_COLUMN_WIDTH;\n\n  const series = [\n    ...orderedCurves.map((curve, index) => ({\n      id: `house-${curve.houseId}`,\n      data: curve.predictions,\n      color: curve.isLuxurySubgroup ? LUXURY_COLOR : STANDARD_COLOR,\n      showMark: false,\n      // Only the first curve of each subgroup carries a legend label — the\n      // other ~68 stay unlabeled so the legend shows 3 entries, not 70.\n      ...(index === firstStandardIndex ? { label: \"Standard homes\" } : {}),\n      ...(index === firstLuxuryIndex ? { label: \"Luxury subgroup (diminishing returns)\" } : {}),\n    })),\n    {\n      id: \"pdp\",\n      data: partialDependence,\n      color: t.ink,\n      label: \"Average prediction (PDP)\",\n      showMark: false,\n    },\n  ];\n\n  return (\n    <div style={{ width: window.ANYPLOT_SIZE.width, height: window.ANYPLOT_SIZE.height }}>\n      <div\n        style={{\n          height: TITLE_ROW_HEIGHT,\n          display: \"flex\",\n          alignItems: \"center\",\n          paddingLeft: 8,\n          fontSize: TITLE_FONT_SIZE,\n          fontWeight: 500,\n          color: t.ink,\n          fontFamily: FONT_FAMILY,\n        }}\n      >\n        {TITLE}\n      </div>\n      <div style={{ display: \"flex\", width: window.ANYPLOT_SIZE.width, height: chartHeight }}>\n        <div\n          style={{\n            width: Y_LABEL_COLUMN_WIDTH,\n            display: \"flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n          }}\n        >\n          <span\n            style={{\n              display: \"inline-block\",\n              transform: \"rotate(-90deg)\",\n              whiteSpace: \"nowrap\",\n              color: t.ink,\n              fontSize: AXIS_LABEL_FONT_SIZE,\n              fontFamily: FONT_FAMILY,\n            }}\n          >\n            Predicted Price ($)\n          </span>\n        </div>\n        <LineChart\n          width={chartWidth}\n          height={chartHeight}\n          series={series}\n          skipAnimation\n          xAxis={[\n            {\n              data: featureGrid,\n              scaleType: \"linear\",\n              label: \"Square Footage (sq ft)\",\n              labelStyle: { fontSize: AXIS_LABEL_FONT_SIZE },\n            },\n          ]}\n          yAxis={[\n            {\n              valueFormatter: (price) => `$${Math.round(price / 1000)}k`,\n            },\n          ]}\n          grid={{ horizontal: true }}\n          tooltip={{ trigger: \"item\" }}\n          margin={{ top: 30, right: 30, bottom: 55, left: 70 }}\n          sx={{\n            \"& .MuiChartsGrid-line\": { stroke: t.grid },\n            \"& .MuiLineElement-series-pdp\": { strokeWidth: 3.5 },\n          }}\n        >\n          <ChartsReferenceLine\n            x={DIVERGENCE_SQFT}\n            label=\"Luxury subgroup diverges →\"\n            labelAlign=\"start\"\n            labelStyle={{ fill: t.inkSoft, fontSize: 14, fontFamily: FONT_FAMILY }}\n            lineStyle={{ stroke: t.palette[3], strokeDasharray: \"6 4\", strokeWidth: 1.5 }}\n          />\n        </LineChart>\n      </div>\n    </div>\n  );\n}\n"}