{"spec_id":"subplot-mosaic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// subplot-mosaic: Mosaic Subplot Layout with Varying Sizes\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-09\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n// \"muted\" semantic anchor (other/rest) — not in ANYPLOT_TOKENS, so derive it\n// the same way the harness derives inkSoft/grid (theme-adaptive, off ink).\nconst MUTED = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\n// --- Data (in-memory, deterministic, retail-operations scenario) -----------\nconst weeks = Array.from({ length: 12 }, (_, i) => `Wk ${i + 1}`);\nconst revenueNorth = [82, 85, 88, 84, 91, 95, 93, 98, 101, 99, 104, 108];\nconst revenueSouth = [55, 58, 57, 60, 62, 59, 64, 66, 63, 68, 67, 70];\nconst revenueWest = [40, 44, 42, 47, 45, 50, 48, 52, 55, 51, 56, 58];\n\nconst categories = [\"Electronics\", \"Apparel\", \"Home Goods\", \"Sporting Goods\", \"Beauty\"];\nconst unitsSold = [1240, 2860, 1975, 1330, 2100];\n\nconst hours = [\"9a\", \"10a\", \"11a\", \"12p\", \"1p\", \"2p\", \"3p\", \"4p\", \"5p\", \"6p\", \"7p\", \"8p\"];\nconst footTraffic = [120, 180, 240, 310, 360, 340, 300, 280, 260, 320, 410, 260];\n\nconst metrics = [\n  { title: \"Conversion Rate\", actual: 3.8, target: 4.5, suffix: \"%\", good: false },\n  { title: \"Avg. Basket Size\", actual: 61.5, target: 58.0, suffix: \"$\", good: true },\n  { title: \"Return Rate\", actual: 2.1, target: 3.0, suffix: \"%\", good: true },\n];\n\n// --- Layout (mosaic grid built as DOM; each cell mounts its own Highcharts\n//     instance, exactly like matplotlib's ASCII subplot_mosaic strings but\n//     expressed as CSS grid-template-areas — \".\" marks a deliberate gap) -----\nconst root = document.getElementById(\"container\");\n\nconst header = document.createElement(\"div\");\nheader.style.cssText = `padding:20px 28px 6px; font-size:22px; font-weight:600; color:${t.ink}; font-family:inherit;`;\nheader.textContent = \"subplot-mosaic · javascript · highcharts · anyplot.ai\";\nroot.appendChild(header);\n\nconst mosaic = document.createElement(\"div\");\nmosaic.style.cssText = [\n  \"display:grid\",\n  \"grid-template-columns:repeat(6, 1fr)\",\n  \"grid-template-rows:1.35fr 1.35fr 1.1fr 0.85fr\",\n  `grid-template-areas:` +\n    `\"overview overview overview overview overview overview\" ` +\n    `\"overview overview overview overview overview overview\" ` +\n    `\"detail1 detail1 detail1 detail2 detail2 detail2\" ` +\n    `\"metric0 metric0 . metric1 metric1 metric2\"`,\n  \"gap:18px\",\n  \"margin:8px 28px 26px\",\n  \"height:calc(100% - 66px)\",\n].join(\";\");\nroot.appendChild(mosaic);\n\n// Tiered card chrome — a soft lift on the dominant overview panel, a flatter\n// and more compact treatment on the small KPI tiles — so the mosaic reads as\n// a deliberate hierarchy rather than six identically-styled boxes.\nfunction makeCell(area, id, tier) {\n  const card = document.createElement(\"div\");\n  const pad = tier === \"tertiary\" ? \"10px 16px\" : \"14px 18px\";\n  const radius = tier === \"tertiary\" ? \"10px\" : \"12px\";\n  const shadow =\n    tier === \"primary\"\n      ? t.theme === \"dark\"\n        ? \"0 2px 8px rgba(0,0,0,0.4)\"\n        : \"0 2px 8px rgba(0,0,0,0.08)\"\n      : \"none\";\n  card.style.cssText = `grid-area:${area}; background:${t.elevatedBg}; border-radius:${radius}; padding:${pad}; min-width:0; min-height:0; box-shadow:${shadow};`;\n  const mount = document.createElement(\"div\");\n  mount.id = id;\n  mount.style.cssText = \"width:100%; height:100%;\";\n  card.appendChild(mount);\n  mosaic.appendChild(card);\n  return mount;\n}\n\nconst overviewMount = makeCell(\"overview\", \"cell-overview\", \"primary\");\nconst detail1Mount = makeCell(\"detail1\", \"cell-detail1\", \"secondary\");\nconst detail2Mount = makeCell(\"detail2\", \"cell-detail2\", \"secondary\");\nconst metricMounts = metrics.map((m, i) => makeCell(`metric${i}`, `cell-metric${i}`, \"tertiary\"));\n\n// The mosaic string's \".\" is a genuinely empty grid cell (no grid-area name,\n// so it can only be targeted by explicit line placement) — give it a faint\n// tint + dashed border so it reads as deliberate structure, not a missing\n// panel. Column 3 / row 4 matches the \".\" position in the template-areas\n// string above.\nconst gapCell = document.createElement(\"div\");\ngapCell.style.cssText = `grid-column:3 / 4; grid-row:4 / 5; border-radius:10px; border:1px dashed ${t.inkSoft}; background:${t.grid}; opacity:0.6;`;\nmosaic.appendChild(gapCell);\n\n// Chart instances created below, so their sizes can be reflowed once the grid\n// has actually resolved row heights (see the reflow block at the bottom of\n// this file — a fresh mount's percentage height reads back as 0 the instant\n// Highcharts.chart() runs, so every panel would otherwise fall back to\n// Highcharts' default 400px and clip most of the data out of view).\nconst panelCharts = [];\n\n// --- Overview: wide dominant panel, weekly revenue trend by region ---------\npanelCharts.push(Highcharts.chart(overviewMount.id, {\n  chart: { type: \"spline\", backgroundColor: \"transparent\", animation: false, style: { fontFamily: \"inherit\" } },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: { text: \"Weekly Revenue by Region\", align: \"left\", style: { color: t.ink, fontSize: \"18px\", fontWeight: \"600\" } },\n  xAxis: {\n    categories: weeks,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"13px\" } },\n  },\n  yAxis: {\n    title: { text: \"Revenue ($k)\", style: { color: t.inkSoft, fontSize: \"14px\" } },\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"13px\" } },\n  },\n  legend: { itemStyle: { color: t.inkSoft, fontSize: \"13px\" }, itemHoverStyle: { color: t.ink } },\n  plotOptions: { series: { animation: false, lineWidth: 3, marker: { radius: 6 } } },\n  series: [\n    { name: \"North\", data: revenueNorth },\n    { name: \"South\", data: revenueSouth },\n    { name: \"West\", data: revenueWest },\n  ],\n}));\n\n// --- Detail 1: medium panel, units sold by category -------------------------\npanelCharts.push(Highcharts.chart(detail1Mount.id, {\n  chart: { type: \"column\", backgroundColor: \"transparent\", animation: false, style: { fontFamily: \"inherit\" } },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: { text: \"Units Sold by Category\", align: \"left\", style: { color: t.ink, fontSize: \"16px\", fontWeight: \"600\" } },\n  xAxis: {\n    categories,\n    lineWidth: 0,\n    tickLength: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"12px\" } },\n  },\n  yAxis: {\n    title: { text: null },\n    lineWidth: 0,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"12px\" } },\n  },\n  legend: { enabled: false },\n  plotOptions: { series: { animation: false }, column: { borderWidth: 0 } },\n  series: [{ name: \"Units\", data: unitsSold, colorByPoint: true }],\n}));\n\n// --- Detail 2: medium panel, hourly foot traffic ----------------------------\npanelCharts.push(Highcharts.chart(detail2Mount.id, {\n  chart: { type: \"area\", backgroundColor: \"transparent\", animation: false, style: { fontFamily: \"inherit\" } },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: { text: \"Foot Traffic by Hour\", align: \"left\", style: { color: t.ink, fontSize: \"16px\", fontWeight: \"600\" } },\n  xAxis: {\n    categories: hours,\n    lineWidth: 0,\n    tickLength: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"12px\" } },\n  },\n  yAxis: {\n    title: { text: null },\n    lineWidth: 0,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"12px\" } },\n  },\n  legend: { enabled: false },\n  plotOptions: {\n    series: { animation: false },\n    area: { fillOpacity: 0.18, lineWidth: 2.5, marker: { enabled: false } },\n  },\n  series: [{ name: \"Visitors\", data: footTraffic, color: t.palette[0] }],\n}));\n\n// --- Metrics row: three small KPI panels, actual vs. target ----------------\nmetrics.forEach((m, i) => {\n  const statusColor = m.good ? t.palette[0] : t.amber;\n  const format = (v) => (m.suffix === \"$\" ? `$${v.toFixed(2)}` : `${v.toFixed(1)}%`);\n  // Explicit delta annotation so the story reads even without relying on the\n  // amber/green hue alone (percentage-point metrics report \"pp\", dollar\n  // metrics report the raw currency difference).\n  const delta = m.actual - m.target;\n  const deltaText =\n    m.suffix === \"$\"\n      ? `${delta >= 0 ? \"+\" : \"-\"}$${Math.abs(delta).toFixed(2)} vs target`\n      : `${delta >= 0 ? \"+\" : \"-\"}${Math.abs(delta).toFixed(1)}pp vs target`;\n  panelCharts.push(Highcharts.chart(metricMounts[i].id, {\n    chart: { type: \"bar\", backgroundColor: \"transparent\", animation: false, style: { fontFamily: \"inherit\" } },\n    credits: { enabled: false },\n    title: { text: m.title, align: \"left\", style: { color: t.ink, fontSize: \"14px\", fontWeight: \"600\" } },\n    subtitle: {\n      text: deltaText,\n      align: \"left\",\n      style: { color: statusColor, fontSize: \"11px\", fontWeight: \"600\" },\n    },\n    xAxis: {\n      categories: [\"Actual\", \"Target\"],\n      lineWidth: 0,\n      tickLength: 0,\n      labels: { style: { color: t.inkSoft, fontSize: \"12px\" } },\n    },\n    yAxis: { title: { text: null }, lineWidth: 0, gridLineColor: t.grid, labels: { enabled: false } },\n    legend: { enabled: false },\n    plotOptions: {\n      series: {\n        animation: false,\n        dataLabels: {\n          enabled: true,\n          style: { color: t.ink, fontSize: \"12px\", fontWeight: \"600\", textOutline: \"none\" },\n          formatter() {\n            return format(this.y);\n          },\n        },\n      },\n      bar: { borderWidth: 0, pointPadding: 0.15, groupPadding: 0.1 },\n    },\n    series: [\n      {\n        data: [\n          { y: m.actual, color: statusColor },\n          { y: m.target, color: MUTED },\n        ],\n      },\n    ],\n  }));\n});\n\n// A brand-new mount's percentage height resolves to 0 the instant\n// Highcharts.chart() reads it (CSS Grid rows haven't settled a layout pass\n// yet), so every panel above was created at Highcharts' 400px fallback.\n// chart.reflow() alone doesn't fix this — it still trusts the container's\n// computed CSS height, which stays wrong for a percentage-height mount in\n// some Chromium timing cases — so set each chart's real pixel size directly\n// from its mount's post-layout bounding box once two animation frames have\n// let the grid settle, then signal the harness explicitly.\nconst panelMounts = [overviewMount, detail1Mount, detail2Mount, ...metricMounts];\nrequestAnimationFrame(() => {\n  requestAnimationFrame(() => {\n    panelCharts.forEach((chart, i) => {\n      const rect = panelMounts[i].getBoundingClientRect();\n      chart.setSize(rect.width, rect.height, false);\n    });\n    window.__anyplotReady = true;\n  });\n});\n"}