{"spec_id":"ice-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// ice-basic: Individual Conditional Expectation (ICE) Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-17\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1103515245 + 12345) % 2147483648;\n  return seed / 2147483648;\n}\n\nconst N_OBS = 60;\nconst N_GRID = 60;\nconst SQFT_MIN = 800;\nconst SQFT_MAX = 4000;\n\nconst grid = [];\nfor (let j = 0; j < N_GRID; j++) {\n  grid.push(SQFT_MIN + (j / (N_GRID - 1)) * (SQFT_MAX - SQFT_MIN));\n}\n\n// Simulated GradientBoostingRegressor predictions: each house gets its own\n// baseline value, price-per-sqft slope, and saturation curvature, producing\n// heterogeneous, non-linear individual curves around a shared trend — some\n// houses keep appreciating with size, others hit a price ceiling.\nconst curves = [];\nconst actualSqft = [];\nfor (let i = 0; i < N_OBS; i++) {\n  const base = 180000 + (lcg() - 0.5) * 50000;\n  const slope = 120 + lcg() * 40;\n  const curvature = 0.3 + lcg() * 1.0;\n  actualSqft.push(SQFT_MIN + lcg() * (SQFT_MAX - SQFT_MIN));\n\n  const curve = grid.map((sqft) => {\n    const delta = sqft - SQFT_MIN;\n    return base + slope * delta * (1 - (curvature * delta) / 6000);\n  });\n  curves.push(curve);\n}\n\nconst pdp = grid.map((_, j) => {\n  let sum = 0;\n  for (let i = 0; i < N_OBS; i++) sum += curves[i][j];\n  return sum / N_OBS;\n});\n\nlet yMin = Infinity;\nlet yMax = -Infinity;\nfor (const curve of curves) {\n  for (const v of curve) {\n    if (v < yMin) yMin = v;\n    if (v > yMax) yMax = v;\n  }\n}\nconst yRange = yMax - yMin;\nconst rugY = yMin - yRange * 0.05;\nconst yAxisMin = yMin - yRange * 0.14;\n\n// --- Series -------------------------------------------------------------------\nconst iceSeries = curves.map((curve) => ({\n  name: \"Individual houses (ICE)\",\n  type: \"line\",\n  data: grid.map((x, j) => [x, curve[j]]),\n  showSymbol: false,\n  silent: true,\n  lineStyle: { color: t.palette[0], width: 1.3, opacity: 0.18 },\n  itemStyle: { color: t.palette[0] },\n  z: 1,\n}));\n\nconst pdpSeries = {\n  name: \"Average prediction (PDP)\",\n  type: \"line\",\n  data: grid.map((x, j) => [x, pdp[j]]),\n  showSymbol: false,\n  lineStyle: { color: t.ink, width: 4 },\n  itemStyle: { color: t.ink },\n  z: 3,\n};\n\nconst rugSeries = {\n  name: \"Observed square footage\",\n  type: \"scatter\",\n  data: actualSqft.map((x) => [x, rugY]),\n  symbol: \"rect\",\n  symbolSize: [3, 16],\n  itemStyle: { color: t.muted, opacity: 0.55 },\n  silent: true,\n  z: 2,\n};\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -----------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"House Price ICE · ice-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    data: [\"Individual houses (ICE)\", \"Average prediction (PDP)\"],\n    top: 66,\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 15 },\n    itemWidth: 30,\n    itemHeight: 14,\n  },\n  tooltip: {\n    trigger: \"item\",\n    formatter: (params) => {\n      const [sqft, price] = params.data;\n      return `${params.seriesName}<br/>${Math.round(sqft).toLocaleString()} sq ft → $${Math.round(price).toLocaleString()}`;\n    },\n  },\n  grid: { left: 115, right: 60, top: 130, bottom: 90 },\n  xAxis: {\n    type: \"value\",\n    min: SQFT_MIN,\n    max: SQFT_MAX,\n    name: \"Square Footage (sq ft)\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    min: yAxisMin,\n    name: \"Predicted Price ($)\",\n    nameLocation: \"middle\",\n    nameGap: 70,\n    nameRotate: 90,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      formatter: (value) => \"$\" + Math.round(value / 1000) + \"k\",\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [...iceSeries, pdpSeries, rugSeries],\n});\n"}