Stratigraphic Column with Lithology Patterns — MUI X Charts

A vertical column visualization showing geological rock layers with standardized lithology patterns, formation names, ages, and thickness scales. Each layer is represented as a stacked rectangular block filled with a distinctive pattern (e.g., brick pattern for limestone, dots for sandstone, dashes for shale) following FGDC/USGS conventions. This plot is essential for communicating subsurface geology and sedimentary sequences in a compact, standardized format.

Stratigraphic Column with Lithology Patterns rendered with MUI X Charts

JavaScript source (MUI X Charts)

// anyplot.ai
// column-stratigraphic: Stratigraphic Column with Lithology Patterns
// Library: muix 7.29.1 | JavaScript 22.22.3
// Quality: 95/100 | Created: 2026-06-17
//# anyplot-orientation: landscape
// anyplot.ai
// column-stratigraphic: Stratigraphic Column with Lithology Patterns
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-06-17
import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { useXScale, useYScale } from "@mui/x-charts/hooks";

const t = window.ANYPLOT_TOKENS;
const SIZE = window.ANYPLOT_SIZE;
const TITLE = "column-stratigraphic · javascript · muix · anyplot.ai";
const FONT =
  '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';

// --- Lithology dictionary --------------------------------------------------
// Each rock type maps to an Imprint colour + a distinct FGDC/USGS-style fill
// pattern. The pattern (not the colour) is the primary lithology cue; the colour
// is a redundant, colourblind-safe secondary. Sandstone takes brand green
// (Imprint position 1 — always the first categorical series).
const LITHO = {
  sandstone: { name: "Sandstone", color: t.palette[0] }, // brand green
  shale: { name: "Shale", color: t.palette[1] }, // lavender
  limestone: { name: "Limestone", color: t.palette[2] }, // blue
  siltstone: { name: "Siltstone", color: t.palette[3] }, // ochre
  dolomite: { name: "Dolomite", color: t.palette[5] }, // cyan
  conglomerate: { name: "Conglomerate", color: t.palette[4] }, // matte red
};
const LITHO_ORDER = [
  "sandstone",
  "shale",
  "limestone",
  "siltstone",
  "dolomite",
  "conglomerate",
];

// --- Data: synthetic borehole section (deterministic, in-memory) -----------
// A Western-Canada-style sedimentary succession: a young clastic/marine
// Cretaceous package resting unconformably on Paleozoic carbonates, with a
// second unconformity onto basal Cambrian clastics. Depth increases downward.
const LAYERS = [
  { top: 0, bottom: 28, litho: "sandstone", formation: "Horseshoe Canyon Fm", age: "Maastrichtian" },
  { top: 28, bottom: 62, litho: "shale", formation: "Bearpaw Shale", age: "Campanian" },
  { top: 62, bottom: 88, litho: "sandstone", formation: "Belly River Fm", age: "Campanian" },
  { top: 88, bottom: 120, litho: "limestone", formation: "Rundle Group", age: "Mississippian", unconf: true },
  { top: 120, bottom: 150, litho: "dolomite", formation: "Palliser Fm", age: "Famennian" },
  { top: 150, bottom: 178, litho: "shale", formation: "Ireton Fm", age: "Frasnian" },
  { top: 178, bottom: 214, litho: "limestone", formation: "Leduc Reef", age: "Frasnian" },
  { top: 214, bottom: 246, litho: "siltstone", formation: "Beaverhill Lake Gp", age: "Givetian" },
  { top: 246, bottom: 280, litho: "conglomerate", formation: "Basal Clastics", age: "Cambrian", unconf: true },
];
const MAXDEPTH = 280;

// Geological periods spanned, grouped for the left-margin time brackets.
const PERIODS = [
  { name: "Cretaceous", age: "100–66 Ma", top: 0, bottom: 88 },
  { name: "Carboniferous", age: "359–299 Ma", top: 88, bottom: 120 },
  { name: "Devonian", age: "419–359 Ma", top: 120, bottom: 246 },
  { name: "Cambrian", age: "539–485 Ma", top: 246, bottom: 280 },
];

// --- World layout (abstract 16×9 coordinate space, y increases upward) ------
const COL_LEFT = 4.25;
const COL_RIGHT = 7.7;
const COL_TOP_Y = 7.7; // world-y of the shallowest depth (top of column)
const COL_BOT_Y = 0.6; // world-y of the deepest depth (bottom of column)
const AXIS_X = 4.05;
const BRACKET_X = 2.95;
const FORM_X = 7.95;
const LEGEND_X = 11.3;

// Depth (m) → world-y. Depth 0 sits at the top, MAXDEPTH at the bottom.
const depthToY = (d) => COL_TOP_Y - (d / MAXDEPTH) * (COL_TOP_Y - COL_BOT_Y);

// --- SVG fill patterns (FGDC/USGS-style lithology motifs) -------------------
// Each tile paints a faint colour wash (identity) plus a saturated motif in the
// same Imprint hue. userSpaceOnUse keeps motifs a constant pixel size whatever
// the layer thickness. Theme-independent: identical in light & dark.
function Defs() {
  return (
    <defs>
      {/* Sandstone — stipple dots */}
      <pattern id="lith-sandstone" patternUnits="userSpaceOnUse" width={24} height={24}>
        <rect width={24} height={24} fill={LITHO.sandstone.color} fillOpacity={0.26} />
        {[[5, 5], [17, 9], [9, 17], [20, 20], [13, 13]].map(([x, y], i) => (
          <circle key={i} cx={x} cy={y} r={2.1} fill={LITHO.sandstone.color} />
        ))}
      </pattern>
      {/* Shale — fine horizontal laminations */}
      <pattern id="lith-shale" patternUnits="userSpaceOnUse" width={24} height={16}>
        <rect width={24} height={16} fill={LITHO.shale.color} fillOpacity={0.26} />
        {[4, 9, 14].map((y, i) => (
          <line key={i} x1={0} y1={y} x2={24} y2={y} stroke={LITHO.shale.color} strokeWidth={1.7} />
        ))}
      </pattern>
      {/* Limestone — brickwork */}
      <pattern id="lith-limestone" patternUnits="userSpaceOnUse" width={28} height={18}>
        <rect width={28} height={18} fill={LITHO.limestone.color} fillOpacity={0.26} />
        <g stroke={LITHO.limestone.color} strokeWidth={1.7}>
          <line x1={0} y1={0} x2={28} y2={0} />
          <line x1={0} y1={9} x2={28} y2={9} />
          <line x1={0} y1={18} x2={28} y2={18} />
          <line x1={14} y1={0} x2={14} y2={9} />
          <line x1={0} y1={9} x2={0} y2={18} />
          <line x1={28} y1={9} x2={28} y2={18} />
        </g>
      </pattern>
      {/* Siltstone — short scattered dashes */}
      <pattern id="lith-siltstone" patternUnits="userSpaceOnUse" width={26} height={26}>
        <rect width={26} height={26} fill={LITHO.siltstone.color} fillOpacity={0.26} />
        <g stroke={LITHO.siltstone.color} strokeWidth={1.8} strokeLinecap="round">
          <line x1={3} y1={6} x2={10} y2={6} />
          <line x1={15} y1={12} x2={22} y2={12} />
          <line x1={6} y1={19} x2={13} y2={19} />
          <line x1={18} y1={23} x2={25} y2={23} />
        </g>
      </pattern>
      {/* Dolomite — rhombic (diagonal) brick */}
      <pattern id="lith-dolomite" patternUnits="userSpaceOnUse" width={20} height={20}>
        <rect width={20} height={20} fill={LITHO.dolomite.color} fillOpacity={0.26} />
        <g stroke={LITHO.dolomite.color} strokeWidth={1.6}>
          <line x1={0} y1={0} x2={20} y2={20} />
          <line x1={0} y1={20} x2={20} y2={0} />
          <line x1={-10} y1={10} x2={10} y2={-10} />
          <line x1={10} y1={30} x2={30} y2={10} />
        </g>
      </pattern>
      {/* Conglomerate — clast outlines (pebbles) */}
      <pattern id="lith-conglomerate" patternUnits="userSpaceOnUse" width={32} height={32}>
        <rect width={32} height={32} fill={LITHO.conglomerate.color} fillOpacity={0.26} />
        <g fill="none" stroke={LITHO.conglomerate.color} strokeWidth={1.8}>
          <circle cx={9} cy={9} r={5.5} />
          <circle cx={24} cy={13} r={6.5} />
          <circle cx={15} cy={25} r={5} />
          <circle cx={30} cy={29} r={4} />
        </g>
      </pattern>
    </defs>
  );
}

// Wavy path (sine) for unconformity contacts.
function wavyPath(x1, x2, y, amp, wavelength) {
  const n = Math.max(2, Math.round((x2 - x1) / 6));
  let d = `M${x1.toFixed(1)} ${(y).toFixed(1)}`;
  for (let i = 1; i <= n; i++) {
    const x = x1 + ((x2 - x1) * i) / n;
    const yy = y + amp * Math.sin(((x - x1) / wavelength) * 2 * Math.PI);
    d += ` L${x.toFixed(1)} ${yy.toFixed(1)}`;
  }
  return d;
}

// --- Column: patterned lithology blocks + contacts --------------------------
function Column() {
  const xs = useXScale();
  const ys = useYScale();
  const xL = xs(COL_LEFT);
  const xR = xs(COL_RIGHT);
  return (
    <g>
      {LAYERS.map((L, i) => {
        const yTop = ys(depthToY(L.top));
        const yBot = ys(depthToY(L.bottom));
        return (
          <rect
            key={i}
            x={xL}
            y={yTop}
            width={xR - xL}
            height={yBot - yTop}
            fill={`url(#lith-${L.litho})`}
            stroke={t.ink}
            strokeWidth={1.4}
          />
        );
      })}
      {/* Contacts: solid = conformable, wavy = unconformity (hiatus) */}
      {LAYERS.map((L, i) => {
        const y = ys(depthToY(L.top));
        if (L.top === 0) return null;
        return L.unconf ? (
          <path
            key={`c${i}`}
            d={wavyPath(xL, xR, y, 4.5, (xR - xL) / 5)}
            fill="none"
            stroke={t.ink}
            strokeWidth={3}
            strokeLinecap="round"
          />
        ) : (
          <line key={`c${i}`} x1={xL} y1={y} x2={xR} y2={y} stroke={t.ink} strokeWidth={1.6} />
        );
      })}
    </g>
  );
}

// --- Depth scale (left axis, metres, increasing downward) -------------------
function DepthAxis() {
  const xs = useXScale();
  const ys = useYScale();
  const x = xs(AXIS_X);
  const ticks = [];
  for (let d = 0; d <= MAXDEPTH; d += 40) ticks.push(d);
  return (
    <g fontFamily={FONT}>
      <line x1={x} y1={ys(COL_TOP_Y)} x2={x} y2={ys(COL_BOT_Y)} stroke={t.inkSoft} strokeWidth={1.6} />
      {ticks.map((d) => {
        const y = ys(depthToY(d));
        return (
          <g key={d}>
            <line x1={x - 8} y1={y} x2={x} y2={y} stroke={t.inkSoft} strokeWidth={1.6} />
            <text x={x - 13} y={y} textAnchor="end" dominantBaseline="middle" fontSize={14} fill={t.inkSoft}>
              {d}
            </text>
          </g>
        );
      })}
      <text
        x={x - 13}
        y={ys(COL_TOP_Y) - 22}
        textAnchor="end"
        dominantBaseline="middle"
        fontSize={15}
        fontWeight={600}
        fill={t.ink}
      >
        Depth (m)
      </text>
    </g>
  );
}

// --- Geological time brackets (far left) ------------------------------------
function Periods() {
  const xs = useXScale();
  const ys = useYScale();
  const bx = xs(BRACKET_X);
  const cap = 10;
  return (
    <g fontFamily={FONT}>
      {PERIODS.map((p, i) => {
        const yTop = ys(depthToY(p.top));
        const yBot = ys(depthToY(p.bottom));
        const yMid = (yTop + yBot) / 2;
        return (
          <g key={i}>
            <line x1={bx} y1={yTop + 1} x2={bx} y2={yBot - 1} stroke={t.inkSoft} strokeWidth={1.8} />
            <line x1={bx} y1={yTop + 1} x2={bx - cap} y2={yTop + 1} stroke={t.inkSoft} strokeWidth={1.8} />
            <line x1={bx} y1={yBot - 1} x2={bx - cap} y2={yBot - 1} stroke={t.inkSoft} strokeWidth={1.8} />
            <text x={bx - 16} y={yMid - 9} textAnchor="end" dominantBaseline="middle" fontSize={16} fontWeight={600} fill={t.ink}>
              {p.name}
            </text>
            <text x={bx - 16} y={yMid + 11} textAnchor="end" dominantBaseline="middle" fontSize={12.5} fill={t.inkSoft}>
              {p.age}
            </text>
          </g>
        );
      })}
    </g>
  );
}

// --- Formation labels (right of each layer, with leader lines) --------------
function Formations() {
  const xs = useXScale();
  const ys = useYScale();
  const xR = xs(COL_RIGHT);
  const xLead = xs(FORM_X) - 8;
  const xText = xs(FORM_X);
  return (
    <g fontFamily={FONT}>
      {LAYERS.map((L, i) => {
        const yMid = ys(depthToY((L.top + L.bottom) / 2));
        return (
          <g key={i}>
            <line x1={xR} y1={yMid} x2={xLead} y2={yMid} stroke={t.grid} strokeWidth={1.2} />
            <text x={xText} y={yMid - 9} dominantBaseline="middle" fontSize={16} fontWeight={600} fill={t.ink}>
              {L.formation}
            </text>
            <text x={xText} y={yMid + 11} dominantBaseline="middle" fontSize={13} fill={t.inkSoft}>
              {`${L.age} · ${L.bottom - L.top} m`}
            </text>
          </g>
        );
      })}
    </g>
  );
}

// --- Legend: lithology patterns + contact types -----------------------------
function Legend() {
  const xs = useXScale();
  const ys = useYScale();
  const x = xs(LEGEND_X);
  const swW = xs(LEGEND_X + 0.85) - xs(LEGEND_X);
  const top = COL_TOP_Y;
  const step = 0.72;
  const rowY = (i) => ys(top - i * step);
  const swH = Math.abs(ys(0) - ys(0.42));
  return (
    <g fontFamily={FONT}>
      <text x={x} y={ys(top + 0.55)} fontSize={18} fontWeight={700} fill={t.ink}>
        Lithology
      </text>
      {LITHO_ORDER.map((key, i) => {
        const y = rowY(i);
        return (
          <g key={key}>
            <rect x={x} y={y - swH / 2} width={swW} height={swH} fill={`url(#lith-${key})`} stroke={t.ink} strokeWidth={1.2} />
            <text x={x + swW + 14} y={y} dominantBaseline="middle" fontSize={15} fill={t.inkSoft}>
              {LITHO[key].name}
            </text>
          </g>
        );
      })}
      {/* Contact-type key */}
      <text x={x} y={ys(top - LITHO_ORDER.length * step - 0.15)} fontSize={16} fontWeight={700} fill={t.ink}>
        Contacts
      </text>
      {(() => {
        const yC = rowY(LITHO_ORDER.length + 0.6);
        return (
          <g>
            <line x1={x} y1={yC} x2={x + swW} y2={yC} stroke={t.ink} strokeWidth={2} />
            <text x={x + swW + 14} y={yC} dominantBaseline="middle" fontSize={15} fill={t.inkSoft}>
              Conformable
            </text>
          </g>
        );
      })()}
      {(() => {
        const yC = rowY(LITHO_ORDER.length + 1.4);
        return (
          <g>
            <path d={wavyPath(x, x + swW, yC, 3.5, swW / 2.5)} fill="none" stroke={t.ink} strokeWidth={2.4} />
            <text x={x + swW + 14} y={yC} dominantBaseline="middle" fontSize={15} fill={t.inkSoft}>
              Unconformity (hiatus)
            </text>
          </g>
        );
      })()}
    </g>
  );
}

// --- Title / subtitle -------------------------------------------------------
function TitleBlock() {
  const xs = useXScale();
  const ys = useYScale();
  return (
    <g fontFamily={FONT}>
      <text x={xs(0.55)} y={ys(8.62)} fontSize={26} fontWeight={600} fill={t.ink}>
        {TITLE}
      </text>
      <text x={xs(0.55)} y={ys(8.2)} fontSize={15} fill={t.inkSoft}>
        Synthetic borehole section · Western Canada Sedimentary Basin · lithology symbols after FGDC/USGS
      </text>
    </g>
  );
}

// --- Chart (default-exported component — the harness mounts it) -------------
export default function Chart() {
  return (
    <ChartContainer
      width={SIZE.width}
      height={SIZE.height}
      margin={{ top: 12, right: 12, bottom: 12, left: 12 }}
      series={[]}
      xAxis={[{ scaleType: "linear", min: 0, max: 16 }]}
      yAxis={[{ scaleType: "linear", min: 0, max: 9 }]}
      skipAnimation
    >
      <Defs />
      <Column />
      <DepthAxis />
      <Periods />
      <Formations />
      <Legend />
      <TitleBlock />
    </ChartContainer>
  );
}

Part of Stratigraphic Column with Lithology Patterns on anyplot.ai.

Other implementations