{"spec_id":"arc-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// arc-basic: Basic Arc Diagram\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-24\nimport * as React from \"react\";\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: synthetic gene regulatory network along a chromosome ------------\n// Genes are ordered by their (illustrative) position along the chromosome —\n// the axis an arc diagram is built to read. Each edge is a regulatory\n// interaction: \"activate\" (up-regulates the target) or \"repress\"\n// (down-regulates it), with a strength in [1, 4].\nconst GENES = [\n  \"FOXP2\",\n  \"GATA3\",\n  \"MYC\",\n  \"KRAS\",\n  \"EGFR\",\n  \"PIK3CA\",\n  \"PTEN\",\n  \"TP53\",\n  \"ATM\",\n  \"BRCA1\",\n  \"BRCA2\",\n  \"CDK4\",\n  \"ESR1\",\n  \"NOTCH1\",\n  \"AKT1\",\n  \"VEGFA\",\n  \"APOE\",\n  \"ACE2\",\n];\n\n// [sourceIndex, targetIndex, strength, kind]\nconst INTERACTIONS = [\n  [0, 1, 2, \"activate\"],\n  [1, 2, 3, \"activate\"],\n  [2, 3, 2, \"activate\"],\n  [3, 4, 3, \"activate\"],\n  [4, 5, 2, \"activate\"],\n  [5, 6, 4, \"repress\"],\n  [6, 7, 3, \"activate\"],\n  [7, 8, 2, \"activate\"],\n  [8, 9, 3, \"activate\"],\n  [9, 10, 4, \"activate\"],\n  [10, 11, 1, \"repress\"],\n  [11, 12, 2, \"activate\"],\n  [12, 13, 2, \"repress\"],\n  [13, 14, 3, \"activate\"],\n  [14, 15, 2, \"activate\"],\n  [15, 16, 1, \"activate\"],\n  [16, 17, 2, \"activate\"],\n  [2, 7, 3, \"repress\"],\n  [5, 14, 4, \"activate\"],\n  [7, 9, 3, \"activate\"],\n  [9, 17, 1, \"repress\"],\n  [0, 17, 1, \"activate\"],\n  [3, 10, 2, \"repress\"],\n];\n\nconst degree = new Array(GENES.length).fill(0);\nINTERACTIONS.forEach(([a, b]) => {\n  degree[a] += 1;\n  degree[b] += 1;\n});\nconst maxDegree = Math.max(...degree);\nconst nodeRadius = (i) => 6 + (degree[i] / maxDegree) * 9;\n\nconst maxDistance = GENES.length - 1;\n// Arc \"height\" (normalized 0..1) grows with node distance — the reading a\n// basic arc diagram is built around — with a floor so adjacent-gene edges\n// stay visible above the baseline.\nconst archHeight = (dist) => 0.12 + 0.88 * (dist / maxDistance);\n\n// Up-regulation (activate) reads as \"gain\", down-regulation (repress) as\n// \"loss\" — the same green/red convention as a P&L chart, so brand green\n// stays the mandatory first series and matte-red is the deferred semantic\n// anchor for the negative case (see default-style-guide \"Semantic exception\").\nconst kindColor = (kind) => (kind === \"activate\" ? t.palette[0] : t.palette[4]);\n\n// --- Custom SVG layers, positioned via the chart's own scales --------------\nfunction ArcEdges() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const baselineY = yScale(0);\n  return (\n    <g data-drawing-container>\n      {INTERACTIONS.map(([a, b, strength, kind], i) => {\n        const x1 = xScale(a);\n        const x2 = xScale(b);\n        // A quadratic bezier's apex sits at the *midpoint* between the\n        // baseline and the control point (both endpoints are level), so the\n        // control point must overshoot by 2x for the rendered apex to land\n        // exactly at yScale(archHeight).\n        const controlY = baselineY - 2 * (baselineY - yScale(archHeight(Math.abs(b - a))));\n        return (\n          <path\n            key={`edge-${i}`}\n            d={`M ${x1} ${baselineY} Q ${(x1 + x2) / 2} ${controlY} ${x2} ${baselineY}`}\n            fill=\"none\"\n            stroke={kindColor(kind)}\n            strokeWidth={1 + strength * 0.6}\n            strokeOpacity={0.42 + strength * 0.1}\n            strokeDasharray={kind === \"repress\" ? \"6 4\" : undefined}\n          />\n        );\n      })}\n    </g>\n  );\n}\n\nfunction ArcNodes() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const baselineY = yScale(0);\n  return (\n    <g data-drawing-container>\n      {GENES.map((name, i) => {\n        const x = xScale(i);\n        return (\n          <React.Fragment key={name}>\n            <circle cx={x} cy={baselineY} r={nodeRadius(i)} fill={t.palette[0]} stroke={t.pageBg} strokeWidth={1.5} />\n            <text\n              x={x - 4}\n              y={baselineY + nodeRadius(i) + 16}\n              textAnchor=\"end\"\n              fontSize={13}\n              fill={t.inkSoft}\n              transform={`rotate(-40 ${x - 4} ${baselineY + nodeRadius(i) + 16})`}\n            >\n              {name}\n            </text>\n          </React.Fragment>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Title + legend chrome ---------------------------------------------------\nconst TITLE = \"arc-basic · javascript · muix · anyplot.ai\";\nconst TITLE_FONT_DEFAULT = 25;\nconst titleFontSize =\n  TITLE.length > 67 ? Math.round(TITLE_FONT_DEFAULT * (67 / TITLE.length)) : TITLE_FONT_DEFAULT;\nconst TITLE_H = 46;\nconst LEGEND_H = 34;\n\nfunction Legend() {\n  return (\n    <div style={{ height: LEGEND_H, display: \"flex\", alignItems: \"center\", gap: \"22px\", flexWrap: \"wrap\" }}>\n      <div style={{ display: \"flex\", alignItems: \"center\", gap: \"7px\" }}>\n        <span style={{ width: \"18px\", height: \"3px\", backgroundColor: t.palette[0], display: \"inline-block\" }} />\n        <span style={{ fontSize: \"14px\", color: t.inkSoft }}>Activates</span>\n      </div>\n      <div style={{ display: \"flex\", alignItems: \"center\", gap: \"7px\" }}>\n        <svg width=\"18\" height=\"3\" style={{ display: \"inline-block\" }}>\n          <line x1=\"0\" y1=\"1.5\" x2=\"18\" y2=\"1.5\" stroke={t.palette[4]} strokeWidth=\"3\" strokeDasharray=\"6 4\" />\n        </svg>\n        <span style={{ fontSize: \"14px\", color: t.inkSoft }}>Represses (dashed)</span>\n      </div>\n      <span style={{ fontSize: \"14px\", color: t.inkSoft }}>Arc height ∝ distance along the chromosome</span>\n    </div>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const chartHeight = height - TITLE_H - LEGEND_H;\n\n  return (\n    <div style={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <div style={{ paddingLeft: \"50px\" }}>\n        <div\n          style={{\n            height: `${TITLE_H}px`,\n            lineHeight: `${TITLE_H}px`,\n            fontSize: `${titleFontSize}px`,\n            fontWeight: 500,\n            color: t.ink,\n          }}\n        >\n          {TITLE}\n        </div>\n        <Legend />\n      </div>\n      <ChartContainer\n        width={width}\n        height={chartHeight}\n        series={[]}\n        margin={{ top: 30, bottom: 110, left: 50, right: 50 }}\n        xAxis={[{ id: \"x\", scaleType: \"linear\", min: -0.6, max: GENES.length - 0.4 }]}\n        yAxis={[{ id: \"y\", scaleType: \"linear\", min: 0, max: 1 }]}\n        skipAnimation\n      >\n        <ArcEdges />\n        <ArcNodes />\n      </ChartContainer>\n    </div>\n  );\n}\n"}