{"spec_id":"sequence-logo-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// sequence-logo-basic: Sequence Logo for Motif Visualization\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-26\n//# anyplot-orientation: landscape\n// anyplot.ai\n// sequence-logo-basic: Sequence Logo for Motif Visualization\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-26\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Relative letter frequencies at each aligned position of a synthetic 10-bp\n// transcription-factor binding site motif (rows sum to 1). Positions 3-7 form\n// a conserved core (TAGCG); the flanks are weakly conserved, as is typical for\n// real ChIP-seq / SELEX motifs.\nconst LETTERS = [\"A\", \"C\", \"G\", \"T\"];\n\nconst FREQUENCIES = [\n  { A: 0.3, C: 0.25, G: 0.2, T: 0.25 },\n  { A: 0.15, C: 0.55, G: 0.1, T: 0.2 },\n  { A: 0.05, C: 0.05, G: 0.05, T: 0.85 },\n  { A: 0.9, C: 0.04, G: 0.03, T: 0.03 },\n  { A: 0.04, C: 0.04, G: 0.88, T: 0.04 },\n  { A: 0.03, C: 0.9, G: 0.04, T: 0.03 },\n  { A: 0.05, C: 0.05, G: 0.85, T: 0.05 },\n  { A: 0.7, C: 0.1, G: 0.12, T: 0.08 },\n  { A: 0.2, C: 0.3, G: 0.3, T: 0.2 },\n  { A: 0.25, C: 0.25, G: 0.25, T: 0.25 },\n];\n\nconst MAX_BITS = 2; // DNA: log2(4 possible letters)\nconst positions = FREQUENCIES.map((_, i) => `${i + 1}`);\n\n// Standard nucleotide color code (per the specification), mapped onto the\n// Imprint palette: A -> brand green, C -> blue, G -> ochre, T -> matte red.\nconst LETTER_COLOR = {\n  A: t.palette[0], // #009E73\n  C: t.palette[2], // #4467A3\n  G: t.palette[3], // #BD8233\n  T: t.palette[4], // #AE3030\n};\n\nfunction informationContent(freqs) {\n  const entropy = LETTERS.reduce((bits, letter) => {\n    const p = freqs[letter];\n    return p > 0 ? bits - p * Math.log2(p) : bits;\n  }, 0);\n  return MAX_BITS - entropy;\n}\n\n// Stacked segments per position, ascending by frequency so the most frequent\n// letter ends up on top, per the specification.\nconst STACKS = FREQUENCIES.map((freqs) => {\n  const ic = informationContent(freqs);\n  const order = [...LETTERS].sort((a, b) => freqs[a] - freqs[b]);\n  let cumulative = 0;\n  return order\n    .filter((letter) => freqs[letter] > 0)\n    .map((letter) => {\n      const bits = freqs[letter] * ic;\n      const segment = { letter, from: cumulative, to: cumulative + bits };\n      cumulative += bits;\n      return segment;\n    });\n});\n\n// Bold uppercase letters (Arial/Liberation Sans) render at ~72% cap-height of\n// their font-size; stretching the glyph vertically by the inverse ratio makes\n// the visible letter fill its allocated box exactly (fontSize is set to the\n// box height, so this factor is constant across every glyph).\nconst CAP_HEIGHT_RATIO = 0.72;\nconst VERTICAL_STRETCH = 1 / CAP_HEIGHT_RATIO;\n\n// --- Sequence logo layer: letters as scaled glyphs, not plain labels --------\nfunction SequenceLogoGlyphs() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const bandwidth = xScale.bandwidth();\n  const paddingX = bandwidth * 0.08;\n  const usableWidth = bandwidth - 2 * paddingX;\n\n  return (\n    <g>\n      {STACKS.map((segments, posIndex) =>\n        segments.map((seg) => {\n          const xLeft = xScale(positions[posIndex]) + paddingX;\n          const yBottom = yScale(seg.from);\n          const yTop = yScale(seg.to);\n          const heightPx = yBottom - yTop;\n          if (heightPx < 1.5) return null;\n          // Stretch about the segment's own baseline (yBottom) so it grows\n          // upward to fill heightPx without shifting its footing.\n          const translateY = yBottom * (1 - VERTICAL_STRETCH);\n          return (\n            <text\n              key={`${posIndex}-${seg.letter}`}\n              x={xLeft + usableWidth / 2}\n              y={yBottom}\n              textAnchor=\"middle\"\n              textLength={usableWidth}\n              lengthAdjust=\"spacingAndGlyphs\"\n              fontSize={heightPx}\n              fontWeight={700}\n              fontFamily=\"Arial, Liberation Sans, DejaVu Sans, sans-serif\"\n              fill={LETTER_COLOR[seg.letter]}\n              transform={`translate(0 ${translateY}) scale(1 ${VERTICAL_STRETCH})`}\n            >\n              {seg.letter}\n            </text>\n          );\n        }),\n      )}\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nconst TITLE_H = 56;\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  return (\n    <Box sx={{ width: W, height: H, bgcolor: t.pageBg, display: \"flex\", flexDirection: \"column\" }}>\n      <Typography\n        sx={{\n          color: t.ink,\n          fontSize: 22,\n          fontWeight: 500,\n          textAlign: \"center\",\n          height: TITLE_H,\n          lineHeight: `${TITLE_H}px`,\n          flexShrink: 0,\n        }}\n      >\n        sequence-logo-basic · javascript · muix · anyplot.ai\n      </Typography>\n      <ChartContainer\n        width={W}\n        height={H - TITLE_H}\n        series={[]}\n        margin={{ top: 30, right: 40, bottom: 74, left: 96 }}\n        xAxis={[\n          {\n            scaleType: \"band\",\n            data: positions,\n            label: \"Position\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n          },\n        ]}\n        yAxis={[\n          {\n            scaleType: \"linear\",\n            min: 0,\n            max: MAX_BITS,\n            label: \"Information content (bits)\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n          },\n        ]}\n        sx={{\n          \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeWidth: 1 },\n          \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft },\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n        }}\n      >\n        <ChartsGrid horizontal />\n        <SequenceLogoGlyphs />\n        <ChartsXAxis />\n        <ChartsYAxis />\n      </ChartContainer>\n    </Box>\n  );\n}\n"}