{"spec_id":"tree-phylogenetic","library":"muix","language":"javascript","code":"// anyplot.ai\n// tree-phylogenetic: Phylogenetic Tree Diagram\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-09\n//# anyplot-orientation: square\n// anyplot.ai\n// tree-phylogenetic: Phylogenetic Tree Diagram\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: pending | Created: 2026-09-09\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ScatterPlot } from \"@mui/x-charts/ScatterChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: a primate mitochondrial-DNA phylogeny (in-memory, deterministic).\n// Nested objects mirror a Newick tree — each node's `length` is its branch\n// length (substitutions per site) above its parent; leaves carry `name`. The\n// Human/Chimpanzee/Gorilla node carries a `clade` tag so its subtree can be\n// highlighted as a distinct lineage. --------------------------------------\nconst HIGHLIGHT_CLADE = \"homininae\";\nconst tree = {\n  length: 0,\n  children: [\n    {\n      length: 0.06,\n      clade: HIGHLIGHT_CLADE,\n      children: [\n        {\n          length: 0.03,\n          children: [\n            { name: \"Human\", length: 0.02 },\n            { name: \"Chimpanzee\", length: 0.021 },\n          ],\n        },\n        { name: \"Gorilla\", length: 0.04 },\n      ],\n    },\n    {\n      length: 0.02,\n      children: [\n        { name: \"Orangutan\", length: 0.05 },\n        {\n          length: 0.03,\n          children: [\n            { name: \"Gibbon\", length: 0.07 },\n            {\n              length: 0.04,\n              children: [\n                { name: \"Rhesus Macaque\", length: 0.09 },\n                {\n                  length: 0.05,\n                  children: [\n                    { name: \"Marmoset\", length: 0.15 },\n                    { name: \"Lemur\", length: 0.2 },\n                  ],\n                },\n              ],\n            },\n          ],\n        },\n      ],\n    },\n  ],\n};\n\n// --- Rectangular-cladogram layout: x is the cumulative branch length from\n// the root (evolutionary distance), y is an evenly spaced slot per leaf, and\n// each internal node sits at the mean y of its children. A horizontal branch\n// carries each child out to its own x; a vertical connector at the parent's x\n// joins the children's y range, giving the classic elbowed tree shape. Each\n// node inherits `clade` from its parent unless it declares its own. --------\nlet nextLeafSlot = 0;\nconst leaves = [];\nconst branches = [];\n\nfunction layout(node, parentX, inheritedClade) {\n  const clade = node.clade ?? inheritedClade;\n  const x = parentX + node.length;\n  node.x = x;\n  if (!node.children) {\n    node.y = nextLeafSlot;\n    nextLeafSlot += 1;\n    leaves.push({ name: node.name, x, y: node.y, clade });\n    return;\n  }\n  node.children.forEach((child) => {\n    layout(child, x, clade);\n    branches.push({ x1: x, y1: child.y, x2: child.x, y2: child.y, clade: child.clade ?? clade });\n  });\n  const childYs = node.children.map((child) => child.y);\n  node.y = (Math.min(...childYs) + Math.max(...childYs)) / 2;\n  branches.push({ x1: x, y1: Math.min(...childYs), x2: x, y2: Math.max(...childYs), clade });\n}\nlayout(tree, 0, undefined);\n\nconst maxDistance = Math.max(...leaves.map((leaf) => leaf.x));\nconst xDomainMax = maxDistance * 1.1;\nconst leafCount = leaves.length;\n\nconst cladeLeaves = leaves.filter((leaf) => leaf.clade === HIGHLIGHT_CLADE);\nconst otherLeaves = leaves.filter((leaf) => leaf.clade !== HIGHLIGHT_CLADE);\nconst cladeOriginX = tree.children[0].x;\nconst cladeLabelY = (Math.min(...cladeLeaves.map((l) => l.y)) + Math.max(...cladeLeaves.map((l) => l.y))) / 2;\n\nconst TITLE = \"tree-phylogenetic · javascript · muix · anyplot.ai\";\nconst TITLE_HEIGHT = 64;\n// Square canvas gives 8 sparse leaf rows more vertical room per row than a\n// wide landscape frame would, so the short-branch rows read as a balanced\n// composition instead of mostly empty space.\nconst MARGIN = { top: 24, right: 230, bottom: 90, left: 48 };\n\n// --- Custom overlay: branch lines, tip markers, leaf labels, and a clade\n// callout — all mapped through the chart's own linear scales so everything\n// stays pixel-aligned at any render size. ------------------------------------\nfunction BranchLayer() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n\n  return (\n    <g>\n      {branches.map((b, i) => (\n        <line\n          key={i}\n          x1={xScale(b.x1)}\n          y1={yScale(b.y1)}\n          x2={xScale(b.x2)}\n          y2={yScale(b.y2)}\n          stroke={b.clade === HIGHLIGHT_CLADE ? t.palette[2] : t.palette[0]}\n          strokeWidth={b.clade === HIGHLIGHT_CLADE ? 4 : 3}\n          strokeLinecap=\"round\"\n        />\n      ))}\n      {leaves.map((leaf) => (\n        <text\n          key={leaf.name}\n          x={xScale(leaf.x) + 16}\n          y={yScale(leaf.y)}\n          dominantBaseline=\"middle\"\n          textAnchor=\"start\"\n          fontSize={16}\n          fill={t.ink}\n        >\n          {leaf.name}\n        </text>\n      ))}\n      <text\n        x={xScale(cladeOriginX) - 10}\n        y={yScale(cladeLabelY)}\n        dominantBaseline=\"middle\"\n        textAnchor=\"end\"\n        fontSize={12}\n        fontStyle=\"italic\"\n        fill={t.palette[2]}\n      >\n        African apes\n      </text>\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n\n  return (\n    <div style={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <div\n        style={{\n          height: TITLE_HEIGHT,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          fontSize: 22,\n          fontWeight: 500,\n          color: t.ink,\n        }}\n      >\n        {TITLE}\n      </div>\n      <ChartContainer\n        width={width}\n        height={height - TITLE_HEIGHT}\n        margin={MARGIN}\n        skipAnimation\n        xAxis={[{ id: \"distance\", scaleType: \"linear\", min: 0, max: xDomainMax }]}\n        yAxis={[{ id: \"leaf\", scaleType: \"linear\", min: -0.6, max: leafCount - 0.4, reverse: true }]}\n        series={[\n          {\n            type: \"scatter\",\n            id: \"leaves\",\n            data: otherLeaves.map((leaf, i) => ({ x: leaf.x, y: leaf.y, id: `leaf-${i}` })),\n            color: t.palette[0],\n            markerSize: 8,\n          },\n          {\n            type: \"scatter\",\n            id: \"clade-leaves\",\n            data: cladeLeaves.map((leaf, i) => ({ x: leaf.x, y: leaf.y, id: `clade-leaf-${i}` })),\n            color: t.palette[2],\n            markerSize: 8,\n          },\n        ]}\n      >\n        <BranchLayer />\n        <ScatterPlot />\n        <ChartsXAxis\n          axisId=\"distance\"\n          label=\"Evolutionary distance (substitutions per site)\"\n          labelStyle={{ fontSize: 16, fill: t.ink }}\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft }}\n        />\n      </ChartContainer>\n    </div>\n  );\n}\n"}