{"spec_id":"column-stratigraphic","library":"muix","language":"javascript","code":"// anyplot.ai\n// column-stratigraphic: Stratigraphic Column with Lithology Patterns\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 95/100 | Created: 2026-06-17\n//# anyplot-orientation: landscape\n// anyplot.ai\n// column-stratigraphic: Stratigraphic Column with Lithology Patterns\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-06-17\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst SIZE = window.ANYPLOT_SIZE;\nconst TITLE = \"column-stratigraphic · javascript · muix · anyplot.ai\";\nconst FONT =\n  '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n\n// --- Lithology dictionary --------------------------------------------------\n// Each rock type maps to an Imprint colour + a distinct FGDC/USGS-style fill\n// pattern. The pattern (not the colour) is the primary lithology cue; the colour\n// is a redundant, colourblind-safe secondary. Sandstone takes brand green\n// (Imprint position 1 — always the first categorical series).\nconst LITHO = {\n  sandstone: { name: \"Sandstone\", color: t.palette[0] }, // brand green\n  shale: { name: \"Shale\", color: t.palette[1] }, // lavender\n  limestone: { name: \"Limestone\", color: t.palette[2] }, // blue\n  siltstone: { name: \"Siltstone\", color: t.palette[3] }, // ochre\n  dolomite: { name: \"Dolomite\", color: t.palette[5] }, // cyan\n  conglomerate: { name: \"Conglomerate\", color: t.palette[4] }, // matte red\n};\nconst LITHO_ORDER = [\n  \"sandstone\",\n  \"shale\",\n  \"limestone\",\n  \"siltstone\",\n  \"dolomite\",\n  \"conglomerate\",\n];\n\n// --- Data: synthetic borehole section (deterministic, in-memory) -----------\n// A Western-Canada-style sedimentary succession: a young clastic/marine\n// Cretaceous package resting unconformably on Paleozoic carbonates, with a\n// second unconformity onto basal Cambrian clastics. Depth increases downward.\nconst LAYERS = [\n  { top: 0, bottom: 28, litho: \"sandstone\", formation: \"Horseshoe Canyon Fm\", age: \"Maastrichtian\" },\n  { top: 28, bottom: 62, litho: \"shale\", formation: \"Bearpaw Shale\", age: \"Campanian\" },\n  { top: 62, bottom: 88, litho: \"sandstone\", formation: \"Belly River Fm\", age: \"Campanian\" },\n  { top: 88, bottom: 120, litho: \"limestone\", formation: \"Rundle Group\", age: \"Mississippian\", unconf: true },\n  { top: 120, bottom: 150, litho: \"dolomite\", formation: \"Palliser Fm\", age: \"Famennian\" },\n  { top: 150, bottom: 178, litho: \"shale\", formation: \"Ireton Fm\", age: \"Frasnian\" },\n  { top: 178, bottom: 214, litho: \"limestone\", formation: \"Leduc Reef\", age: \"Frasnian\" },\n  { top: 214, bottom: 246, litho: \"siltstone\", formation: \"Beaverhill Lake Gp\", age: \"Givetian\" },\n  { top: 246, bottom: 280, litho: \"conglomerate\", formation: \"Basal Clastics\", age: \"Cambrian\", unconf: true },\n];\nconst MAXDEPTH = 280;\n\n// Geological periods spanned, grouped for the left-margin time brackets.\nconst PERIODS = [\n  { name: \"Cretaceous\", age: \"100–66 Ma\", top: 0, bottom: 88 },\n  { name: \"Carboniferous\", age: \"359–299 Ma\", top: 88, bottom: 120 },\n  { name: \"Devonian\", age: \"419–359 Ma\", top: 120, bottom: 246 },\n  { name: \"Cambrian\", age: \"539–485 Ma\", top: 246, bottom: 280 },\n];\n\n// --- World layout (abstract 16×9 coordinate space, y increases upward) ------\nconst COL_LEFT = 4.25;\nconst COL_RIGHT = 7.7;\nconst COL_TOP_Y = 7.7; // world-y of the shallowest depth (top of column)\nconst COL_BOT_Y = 0.6; // world-y of the deepest depth (bottom of column)\nconst AXIS_X = 4.05;\nconst BRACKET_X = 2.95;\nconst FORM_X = 7.95;\nconst LEGEND_X = 11.3;\n\n// Depth (m) → world-y. Depth 0 sits at the top, MAXDEPTH at the bottom.\nconst depthToY = (d) => COL_TOP_Y - (d / MAXDEPTH) * (COL_TOP_Y - COL_BOT_Y);\n\n// --- SVG fill patterns (FGDC/USGS-style lithology motifs) -------------------\n// Each tile paints a faint colour wash (identity) plus a saturated motif in the\n// same Imprint hue. userSpaceOnUse keeps motifs a constant pixel size whatever\n// the layer thickness. Theme-independent: identical in light & dark.\nfunction Defs() {\n  return (\n    <defs>\n      {/* Sandstone — stipple dots */}\n      <pattern id=\"lith-sandstone\" patternUnits=\"userSpaceOnUse\" width={24} height={24}>\n        <rect width={24} height={24} fill={LITHO.sandstone.color} fillOpacity={0.26} />\n        {[[5, 5], [17, 9], [9, 17], [20, 20], [13, 13]].map(([x, y], i) => (\n          <circle key={i} cx={x} cy={y} r={2.1} fill={LITHO.sandstone.color} />\n        ))}\n      </pattern>\n      {/* Shale — fine horizontal laminations */}\n      <pattern id=\"lith-shale\" patternUnits=\"userSpaceOnUse\" width={24} height={16}>\n        <rect width={24} height={16} fill={LITHO.shale.color} fillOpacity={0.26} />\n        {[4, 9, 14].map((y, i) => (\n          <line key={i} x1={0} y1={y} x2={24} y2={y} stroke={LITHO.shale.color} strokeWidth={1.7} />\n        ))}\n      </pattern>\n      {/* Limestone — brickwork */}\n      <pattern id=\"lith-limestone\" patternUnits=\"userSpaceOnUse\" width={28} height={18}>\n        <rect width={28} height={18} fill={LITHO.limestone.color} fillOpacity={0.26} />\n        <g stroke={LITHO.limestone.color} strokeWidth={1.7}>\n          <line x1={0} y1={0} x2={28} y2={0} />\n          <line x1={0} y1={9} x2={28} y2={9} />\n          <line x1={0} y1={18} x2={28} y2={18} />\n          <line x1={14} y1={0} x2={14} y2={9} />\n          <line x1={0} y1={9} x2={0} y2={18} />\n          <line x1={28} y1={9} x2={28} y2={18} />\n        </g>\n      </pattern>\n      {/* Siltstone — short scattered dashes */}\n      <pattern id=\"lith-siltstone\" patternUnits=\"userSpaceOnUse\" width={26} height={26}>\n        <rect width={26} height={26} fill={LITHO.siltstone.color} fillOpacity={0.26} />\n        <g stroke={LITHO.siltstone.color} strokeWidth={1.8} strokeLinecap=\"round\">\n          <line x1={3} y1={6} x2={10} y2={6} />\n          <line x1={15} y1={12} x2={22} y2={12} />\n          <line x1={6} y1={19} x2={13} y2={19} />\n          <line x1={18} y1={23} x2={25} y2={23} />\n        </g>\n      </pattern>\n      {/* Dolomite — rhombic (diagonal) brick */}\n      <pattern id=\"lith-dolomite\" patternUnits=\"userSpaceOnUse\" width={20} height={20}>\n        <rect width={20} height={20} fill={LITHO.dolomite.color} fillOpacity={0.26} />\n        <g stroke={LITHO.dolomite.color} strokeWidth={1.6}>\n          <line x1={0} y1={0} x2={20} y2={20} />\n          <line x1={0} y1={20} x2={20} y2={0} />\n          <line x1={-10} y1={10} x2={10} y2={-10} />\n          <line x1={10} y1={30} x2={30} y2={10} />\n        </g>\n      </pattern>\n      {/* Conglomerate — clast outlines (pebbles) */}\n      <pattern id=\"lith-conglomerate\" patternUnits=\"userSpaceOnUse\" width={32} height={32}>\n        <rect width={32} height={32} fill={LITHO.conglomerate.color} fillOpacity={0.26} />\n        <g fill=\"none\" stroke={LITHO.conglomerate.color} strokeWidth={1.8}>\n          <circle cx={9} cy={9} r={5.5} />\n          <circle cx={24} cy={13} r={6.5} />\n          <circle cx={15} cy={25} r={5} />\n          <circle cx={30} cy={29} r={4} />\n        </g>\n      </pattern>\n    </defs>\n  );\n}\n\n// Wavy path (sine) for unconformity contacts.\nfunction wavyPath(x1, x2, y, amp, wavelength) {\n  const n = Math.max(2, Math.round((x2 - x1) / 6));\n  let d = `M${x1.toFixed(1)} ${(y).toFixed(1)}`;\n  for (let i = 1; i <= n; i++) {\n    const x = x1 + ((x2 - x1) * i) / n;\n    const yy = y + amp * Math.sin(((x - x1) / wavelength) * 2 * Math.PI);\n    d += ` L${x.toFixed(1)} ${yy.toFixed(1)}`;\n  }\n  return d;\n}\n\n// --- Column: patterned lithology blocks + contacts --------------------------\nfunction Column() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const xL = xs(COL_LEFT);\n  const xR = xs(COL_RIGHT);\n  return (\n    <g>\n      {LAYERS.map((L, i) => {\n        const yTop = ys(depthToY(L.top));\n        const yBot = ys(depthToY(L.bottom));\n        return (\n          <rect\n            key={i}\n            x={xL}\n            y={yTop}\n            width={xR - xL}\n            height={yBot - yTop}\n            fill={`url(#lith-${L.litho})`}\n            stroke={t.ink}\n            strokeWidth={1.4}\n          />\n        );\n      })}\n      {/* Contacts: solid = conformable, wavy = unconformity (hiatus) */}\n      {LAYERS.map((L, i) => {\n        const y = ys(depthToY(L.top));\n        if (L.top === 0) return null;\n        return L.unconf ? (\n          <path\n            key={`c${i}`}\n            d={wavyPath(xL, xR, y, 4.5, (xR - xL) / 5)}\n            fill=\"none\"\n            stroke={t.ink}\n            strokeWidth={3}\n            strokeLinecap=\"round\"\n          />\n        ) : (\n          <line key={`c${i}`} x1={xL} y1={y} x2={xR} y2={y} stroke={t.ink} strokeWidth={1.6} />\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Depth scale (left axis, metres, increasing downward) -------------------\nfunction DepthAxis() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const x = xs(AXIS_X);\n  const ticks = [];\n  for (let d = 0; d <= MAXDEPTH; d += 40) ticks.push(d);\n  return (\n    <g fontFamily={FONT}>\n      <line x1={x} y1={ys(COL_TOP_Y)} x2={x} y2={ys(COL_BOT_Y)} stroke={t.inkSoft} strokeWidth={1.6} />\n      {ticks.map((d) => {\n        const y = ys(depthToY(d));\n        return (\n          <g key={d}>\n            <line x1={x - 8} y1={y} x2={x} y2={y} stroke={t.inkSoft} strokeWidth={1.6} />\n            <text x={x - 13} y={y} textAnchor=\"end\" dominantBaseline=\"middle\" fontSize={14} fill={t.inkSoft}>\n              {d}\n            </text>\n          </g>\n        );\n      })}\n      <text\n        x={x - 13}\n        y={ys(COL_TOP_Y) - 22}\n        textAnchor=\"end\"\n        dominantBaseline=\"middle\"\n        fontSize={15}\n        fontWeight={600}\n        fill={t.ink}\n      >\n        Depth (m)\n      </text>\n    </g>\n  );\n}\n\n// --- Geological time brackets (far left) ------------------------------------\nfunction Periods() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const bx = xs(BRACKET_X);\n  const cap = 10;\n  return (\n    <g fontFamily={FONT}>\n      {PERIODS.map((p, i) => {\n        const yTop = ys(depthToY(p.top));\n        const yBot = ys(depthToY(p.bottom));\n        const yMid = (yTop + yBot) / 2;\n        return (\n          <g key={i}>\n            <line x1={bx} y1={yTop + 1} x2={bx} y2={yBot - 1} stroke={t.inkSoft} strokeWidth={1.8} />\n            <line x1={bx} y1={yTop + 1} x2={bx - cap} y2={yTop + 1} stroke={t.inkSoft} strokeWidth={1.8} />\n            <line x1={bx} y1={yBot - 1} x2={bx - cap} y2={yBot - 1} stroke={t.inkSoft} strokeWidth={1.8} />\n            <text x={bx - 16} y={yMid - 9} textAnchor=\"end\" dominantBaseline=\"middle\" fontSize={16} fontWeight={600} fill={t.ink}>\n              {p.name}\n            </text>\n            <text x={bx - 16} y={yMid + 11} textAnchor=\"end\" dominantBaseline=\"middle\" fontSize={12.5} fill={t.inkSoft}>\n              {p.age}\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Formation labels (right of each layer, with leader lines) --------------\nfunction Formations() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const xR = xs(COL_RIGHT);\n  const xLead = xs(FORM_X) - 8;\n  const xText = xs(FORM_X);\n  return (\n    <g fontFamily={FONT}>\n      {LAYERS.map((L, i) => {\n        const yMid = ys(depthToY((L.top + L.bottom) / 2));\n        return (\n          <g key={i}>\n            <line x1={xR} y1={yMid} x2={xLead} y2={yMid} stroke={t.grid} strokeWidth={1.2} />\n            <text x={xText} y={yMid - 9} dominantBaseline=\"middle\" fontSize={16} fontWeight={600} fill={t.ink}>\n              {L.formation}\n            </text>\n            <text x={xText} y={yMid + 11} dominantBaseline=\"middle\" fontSize={13} fill={t.inkSoft}>\n              {`${L.age} · ${L.bottom - L.top} m`}\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Legend: lithology patterns + contact types -----------------------------\nfunction Legend() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const x = xs(LEGEND_X);\n  const swW = xs(LEGEND_X + 0.85) - xs(LEGEND_X);\n  const top = COL_TOP_Y;\n  const step = 0.72;\n  const rowY = (i) => ys(top - i * step);\n  const swH = Math.abs(ys(0) - ys(0.42));\n  return (\n    <g fontFamily={FONT}>\n      <text x={x} y={ys(top + 0.55)} fontSize={18} fontWeight={700} fill={t.ink}>\n        Lithology\n      </text>\n      {LITHO_ORDER.map((key, i) => {\n        const y = rowY(i);\n        return (\n          <g key={key}>\n            <rect x={x} y={y - swH / 2} width={swW} height={swH} fill={`url(#lith-${key})`} stroke={t.ink} strokeWidth={1.2} />\n            <text x={x + swW + 14} y={y} dominantBaseline=\"middle\" fontSize={15} fill={t.inkSoft}>\n              {LITHO[key].name}\n            </text>\n          </g>\n        );\n      })}\n      {/* Contact-type key */}\n      <text x={x} y={ys(top - LITHO_ORDER.length * step - 0.15)} fontSize={16} fontWeight={700} fill={t.ink}>\n        Contacts\n      </text>\n      {(() => {\n        const yC = rowY(LITHO_ORDER.length + 0.6);\n        return (\n          <g>\n            <line x1={x} y1={yC} x2={x + swW} y2={yC} stroke={t.ink} strokeWidth={2} />\n            <text x={x + swW + 14} y={yC} dominantBaseline=\"middle\" fontSize={15} fill={t.inkSoft}>\n              Conformable\n            </text>\n          </g>\n        );\n      })()}\n      {(() => {\n        const yC = rowY(LITHO_ORDER.length + 1.4);\n        return (\n          <g>\n            <path d={wavyPath(x, x + swW, yC, 3.5, swW / 2.5)} fill=\"none\" stroke={t.ink} strokeWidth={2.4} />\n            <text x={x + swW + 14} y={yC} dominantBaseline=\"middle\" fontSize={15} fill={t.inkSoft}>\n              Unconformity (hiatus)\n            </text>\n          </g>\n        );\n      })()}\n    </g>\n  );\n}\n\n// --- Title / subtitle -------------------------------------------------------\nfunction TitleBlock() {\n  const xs = useXScale();\n  const ys = useYScale();\n  return (\n    <g fontFamily={FONT}>\n      <text x={xs(0.55)} y={ys(8.62)} fontSize={26} fontWeight={600} fill={t.ink}>\n        {TITLE}\n      </text>\n      <text x={xs(0.55)} y={ys(8.2)} fontSize={15} fill={t.inkSoft}>\n        Synthetic borehole section · Western Canada Sedimentary Basin · lithology symbols after FGDC/USGS\n      </text>\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  return (\n    <ChartContainer\n      width={SIZE.width}\n      height={SIZE.height}\n      margin={{ top: 12, right: 12, bottom: 12, left: 12 }}\n      series={[]}\n      xAxis={[{ scaleType: \"linear\", min: 0, max: 16 }]}\n      yAxis={[{ scaleType: \"linear\", min: 0, max: 9 }]}\n      skipAnimation\n    >\n      <Defs />\n      <Column />\n      <DepthAxis />\n      <Periods />\n      <Formations />\n      <Legend />\n      <TitleBlock />\n    </ChartContainer>\n  );\n}\n"}