diff --git a/plots/circlepacking-basic/implementations/javascript/muix.tsx b/plots/circlepacking-basic/implementations/javascript/muix.tsx
new file mode 100644
index 0000000000..2e7f84e9ca
--- /dev/null
+++ b/plots/circlepacking-basic/implementations/javascript/muix.tsx
@@ -0,0 +1,336 @@
+// anyplot.ai
+// circlepacking-basic: Circle Packing Chart
+// Library: muix 7.29.1 | JavaScript 22.23.2
+// Quality: 92/100 | Created: 2026-09-02
+//# anyplot-orientation: square
+// anyplot.ai
+// circlepacking-basic: Circle Packing Chart
+// Library: MUI X Charts | React | Node 22
+// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
+// Quality: pending | Created: 2026-09-02
+import { ChartContainer } from "@mui/x-charts/ChartContainer";
+import { useDrawingArea } from "@mui/x-charts/hooks";
+import { ChartsText } from "@mui/x-charts/ChartsText";
+
+const t = window.ANYPLOT_TOKENS;
+
+const title = "circlepacking-basic · javascript · muix · anyplot.ai";
+const TITLE_DEFAULT = 22;
+const TITLE_FLOOR = 15;
+const titleFontSize = Math.max(TITLE_FLOOR, Math.round(TITLE_DEFAULT * Math.min(1, 67 / title.length)));
+
+// --- Data: investment portfolio composition -- one of the spec's listed
+// applications ("breaking down investments by asset class and holdings").
+// Flat id/parent/value/label rows, exactly the fields the spec's Data
+// section describes; `value` is only present on leaf holdings, matching
+// "size value determining circle area (for leaf nodes)". 20 nodes across
+// 3 levels: portfolio -> asset class -> holding. -----------------------------
+const NODES = [
+ { id: "portfolio", parent: null, label: "Portfolio" },
+ { id: "equities", parent: "portfolio", label: "Equities" },
+ { id: "us-large-cap", parent: "equities", label: "US Large Cap", value: 420 },
+ { id: "us-small-cap", parent: "equities", label: "US Small Cap", value: 140 },
+ { id: "intl-developed", parent: "equities", label: "Int'l Developed", value: 210 },
+ { id: "emerging-markets", parent: "equities", label: "Emerging Markets", value: 95 },
+ { id: "fixed-income", parent: "portfolio", label: "Fixed Income" },
+ { id: "gov-bonds", parent: "fixed-income", label: "Government Bonds", value: 260 },
+ { id: "corp-bonds", parent: "fixed-income", label: "Corporate Bonds", value: 180 },
+ { id: "muni-bonds", parent: "fixed-income", label: "Municipal Bonds", value: 90 },
+ { id: "real-estate", parent: "portfolio", label: "Real Estate" },
+ { id: "reits", parent: "real-estate", label: "REITs", value: 150 },
+ { id: "direct-property", parent: "real-estate", label: "Direct Property", value: 110 },
+ { id: "alternatives", parent: "portfolio", label: "Alternatives" },
+ { id: "private-equity", parent: "alternatives", label: "Private Equity", value: 130 },
+ { id: "commodities", parent: "alternatives", label: "Commodities", value: 70 },
+ { id: "hedge-funds", parent: "alternatives", label: "Hedge Funds", value: 85 },
+ { id: "cash", parent: "portfolio", label: "Cash & Equivalents" },
+ { id: "money-market", parent: "cash", label: "Money Market", value: 60 },
+ { id: "treasury-bills", parent: "cash", label: "Treasury Bills", value: 40 },
+];
+
+function buildTree(nodes) {
+ const byId = new Map();
+ nodes.forEach((n) => byId.set(n.id, { ...n, children: [] }));
+ let treeRoot = null;
+ byId.forEach((node) => {
+ if (node.parent == null) treeRoot = node;
+ else byId.get(node.parent).children.push(node);
+ });
+ return treeRoot;
+}
+function computeValue(node) {
+ if (node.children.length === 0) return node.value;
+ node.value = node.children.reduce((sum, c) => sum + computeValue(c), 0);
+ return node.value;
+}
+const root = buildTree(NODES);
+computeValue(root);
+
+// First branch keeps the mandatory brand green; remaining branches follow
+// canonical Imprint order (asset classes are abstract categories -- no
+// semantic color expectation to override the default order).
+root.children.forEach((branch, i) => {
+ branch.color = t.palette[i % t.palette.length];
+});
+
+// --- Circle packing: the community package has no packing layout of its
+// own, so the geometry is a small hand-rolled force simulation -- each
+// sibling set is attracted toward its shared local center and pushed apart
+// on overlap, then the parent's own radius is set to the enclosing circle
+// of its settled children plus padding. Recursing bottom-up produces true
+// nested packing (not just flat non-overlapping bubbles). ------------------
+function lcg(seed) {
+ let state = seed;
+ return () => {
+ state = (state * 1664525 + 1013904223) % 4294967296;
+ return state / 4294967296;
+ };
+}
+const rand = lcg(42);
+
+function packChildren(children) {
+ const sorted = [...children].sort((a, b) => b.r - a.r);
+ const seedRadius = sorted[0].r * 1.4;
+ sorted.forEach((c, i) => {
+ const angle = (2 * Math.PI * i) / sorted.length;
+ c.x = Math.cos(angle) * seedRadius + (rand() - 0.5) * 4;
+ c.y = Math.sin(angle) * seedRadius + (rand() - 0.5) * 4;
+ });
+
+ const PADDING = 6;
+ const ATTRACTION = 0.02;
+ const ITERATIONS = 400;
+ for (let iter = 0; iter < ITERATIONS; iter++) {
+ for (const c of children) {
+ c.x -= c.x * ATTRACTION;
+ c.y -= c.y * ATTRACTION;
+ }
+ for (let i = 0; i < children.length; i++) {
+ for (let j = i + 1; j < children.length; j++) {
+ const a = children[i];
+ const b = children[j];
+ const dx = b.x - a.x;
+ const dy = b.y - a.y;
+ const dist = Math.hypot(dx, dy) || 0.01;
+ const minDist = a.r + b.r + PADDING;
+ if (dist < minDist) {
+ const overlap = (minDist - dist) / 2;
+ const ux = dx / dist;
+ const uy = dy / dist;
+ a.x -= ux * overlap;
+ a.y -= uy * overlap;
+ b.x += ux * overlap;
+ b.y += uy * overlap;
+ }
+ }
+ }
+ }
+
+ let enclosing = 0;
+ for (const c of children) enclosing = Math.max(enclosing, Math.hypot(c.x, c.y) + c.r);
+ return enclosing;
+}
+
+const LEAF_RADIUS_SCALE = 8;
+const NODE_PADDING = 10;
+
+function layout(node) {
+ if (node.children.length === 0) {
+ node.r = LEAF_RADIUS_SCALE * Math.sqrt(node.value);
+ return;
+ }
+ node.children.forEach(layout);
+ node.r = packChildren(node.children) + NODE_PADDING;
+}
+layout(root);
+
+function place(node, cx, cy) {
+ node.cx = cx;
+ node.cy = cy;
+ node.children.forEach((c) => place(c, cx + c.x, cy + c.y));
+}
+place(root, 0, 0);
+
+// --- Color: leaves get a white-mixed tint of their branch's hue, scaled by
+// their value relative to the largest sibling, so shade intensity echoes
+// relative size within the asset class while the hue keeps the grouping. --
+function hexToRgb(hex) {
+ const n = parseInt(hex.slice(1), 16);
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
+}
+function relativeLuminance([r, g, b]) {
+ const chan = (v) => {
+ const c = v / 255;
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
+ };
+ return 0.2126 * chan(r) + 0.7152 * chan(g) + 0.0722 * chan(b);
+}
+function mixWithWhite(rgb, factor) {
+ return rgb.map((c) => Math.round(c + (255 - c) * factor));
+}
+function rgbToCss([r, g, b]) {
+ return `rgb(${r}, ${g}, ${b})`;
+}
+function textColorFor(rgb) {
+ return relativeLuminance(rgb) > 0.45 ? "#1A1A17" : "#FAF8F1";
+}
+function truncateLabel(label, maxChars) {
+ if (label.length <= maxChars) return label;
+ const clipped = label.slice(0, maxChars);
+ const lastSpace = clipped.lastIndexOf(" ");
+ return lastSpace >= 3 ? `${clipped.slice(0, lastSpace)}…` : `${clipped}…`;
+}
+// Wrap a two-or-more-word label onto the most balanced two lines that both
+// fit `maxCharsPerLine`, so labels like "Corporate Bonds" show in full
+// instead of ellipsis-truncating mid-word. Falls back to truncation for
+// single-word labels or when no split fits.
+function wrapLabel(label, maxCharsPerLine) {
+ if (label.length <= maxCharsPerLine) return [label];
+ const words = label.split(" ");
+ if (words.length < 2) return [truncateLabel(label, maxCharsPerLine)];
+ let best = null;
+ for (let i = 1; i < words.length; i++) {
+ const line1 = words.slice(0, i).join(" ");
+ const line2 = words.slice(i).join(" ");
+ if (line1.length <= maxCharsPerLine && line2.length <= maxCharsPerLine) {
+ const diff = Math.abs(line1.length - line2.length);
+ if (!best || diff < best.diff) best = { line1, line2, diff };
+ }
+ }
+ return best ? [best.line1, best.line2] : [truncateLabel(label, maxCharsPerLine)];
+}
+
+// --- Legend: branch color identity, read once above the packing area so the
+// circles themselves stay uncluttered (no in-circle branch labels fighting
+// the child circles they contain). ------------------------------------------
+function Legend({ x, y, width: legendWidth }) {
+ const itemWidth = legendWidth / root.children.length;
+ return (
+
+ {root.children.map((branch, i) => {
+ const itemX = x + itemWidth * i;
+ return (
+
+
+
+ {branch.label}
+
+
+ );
+ })}
+
+ );
+}
+
+// --- Circles: root boundary, branch zones (light fill + colored stroke),
+// leaf holdings (solid tint, labeled when large enough). Leaf labels use
+// MUI X's own ChartsText primitive (not a raw ) so long names wrap
+// onto two lines via its native "\n"-line-splitting instead of truncating
+// mid-word -- ChartsTooltip/ChartsLegend don't apply here since they key
+// off a `series` data model this hand-rolled packing geometry has none of. -
+
+function CirclePacking() {
+ const { left, top, width, height } = useDrawingArea();
+ const availableRadius = Math.min(width, height) / 2 - 4;
+ const scale = availableRadius / root.r;
+ const originX = left + width / 2;
+ const originY = top + height / 2;
+
+ function project(node) {
+ return { cx: originX + node.cx * scale, cy: originY + node.cy * scale, r: node.r * scale };
+ }
+
+ const rootCircle = project(root);
+
+ return (
+
+
+ {root.children.map((branch) => {
+ const b = project(branch);
+ const maxLeafValue = Math.max(...branch.children.map((c) => c.value));
+ return (
+
+
+ {`${branch.label}: $${branch.value}K`}
+
+ {branch.children.map((leaf) => {
+ const l = project(leaf);
+ const tintFactor = maxLeafValue > 0 ? (1 - leaf.value / maxLeafValue) * 0.6 : 0;
+ const rgb = mixWithWhite(hexToRgb(branch.color), tintFactor);
+ const fill = rgbToCss(rgb);
+ const ink = textColorFor(rgb);
+ const nameSize = Math.max(10, Math.min(16, l.r * 0.22));
+ const valueSize = Math.round(nameSize * 0.82);
+ const showName = l.r >= 26;
+ const showValue = l.r >= 40;
+ const maxCharsPerLine = Math.max(4, Math.floor((l.r * 1.7) / (nameSize * 0.55)));
+ const nameLines = showName ? wrapLabel(leaf.label, maxCharsPerLine) : [];
+ const LINE_HEIGHT_EM = 1.18;
+ const nameHalfHeight = (nameLines.length * nameSize * LINE_HEIGHT_EM) / 2;
+ const valueHalfHeight = (valueSize * LINE_HEIGHT_EM) / 2;
+ const gap = nameSize * 0.3;
+ const nameCenterY = showValue ? l.cy - valueHalfHeight - gap / 2 : l.cy;
+ const valueCenterY = l.cy + nameHalfHeight + gap / 2;
+ return (
+
+
+ {`${branch.label} / ${leaf.label}: $${leaf.value}K`}
+
+ {showName && (
+
+ )}
+ {showValue && (
+
+ )}
+
+ );
+ })}
+
+ );
+ })}
+
+ );
+}
+
+// --- Chart (default-exported component -- the harness mounts it) ----------
+// ChartContainer supplies the SVG root and theme context; its
+// `margin` prop drives the DrawingProvider that CirclePacking() reads back
+// via useDrawingArea(), the same layout primitive MUI X's own axis/legend
+// components use. The packing body itself is laid out in local, scale-free
+// units by layout()/place() above and only projected into pixel space here,
+// so no axis/scale is needed -- xAxis/yAxis are omitted entirely.
+const MARGIN = { top: 176, right: 24, bottom: 24, left: 24 };
+
+export default function Chart() {
+ const { width, height } = window.ANYPLOT_SIZE;
+
+ return (
+
+
+ {title}
+
+
+ Investment portfolio by asset class and holding · circle area proportional to market value ($K)
+
+
+
+
+ );
+}
diff --git a/plots/circlepacking-basic/metadata/javascript/muix.yaml b/plots/circlepacking-basic/metadata/javascript/muix.yaml
new file mode 100644
index 0000000000..8f220c461b
--- /dev/null
+++ b/plots/circlepacking-basic/metadata/javascript/muix.yaml
@@ -0,0 +1,273 @@
+library: muix
+language: javascript
+specification_id: circlepacking-basic
+created: '2026-09-02T15:58:48Z'
+updated: '2026-09-02T16:15:19Z'
+generated_by: claude-sonnet
+workflow_run: 33650517579
+issue: 2498
+language_version: 22.23.2
+library_version: 7.29.1
+preview_url_light: https://storage.googleapis.com/anyplot-images/plots/circlepacking-basic/javascript/muix/plot-light.png
+preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/circlepacking-basic/javascript/muix/plot-dark.png
+preview_html_light: https://storage.googleapis.com/anyplot-images/plots/circlepacking-basic/javascript/muix/plot-light.html
+preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/circlepacking-basic/javascript/muix/plot-dark.html
+quality_score: 92
+review:
+ strengths:
+ - 'Truncation weakness from attempt 1 is fully fixed: wrapLabel() balances long
+ leaf names onto two lines via MUI X''s own ChartsText primitive, so every leaf
+ circle now shows its full label (''Corporate Bonds'', ''Government Bonds'', ''Int''l
+ Developed'', ''Treasury Bills'', etc.) instead of ellipsis-truncating mid-word'
+ - 'Correct Imprint palette usage: first branch #009E73, remaining branches in canonical
+ order, identical data colors between light/dark renders, theme-correct chrome
+ (background, ink, grid) in both'
+ - Genuinely custom circle-packing layout (seeded force simulation, seed=42) since
+ the community MUI X package has no built-in packing layout -- real engineering,
+ fully deterministic and reproducible
+ - Luminance-based text color switching keeps every leaf label high-contrast regardless
+ of the branch's fill tint or the page theme
+ - Clean, correctly-sized square canvas with no overlap, no clipping, and correct
+ use of ChartContainer/useDrawingArea/ChartsText per the harness contract (skipAnimation,
+ ANYPLOT_SIZE sizing)
+ weaknesses:
+ - 'The file header is duplicated/corrupted: lines 1-10 contain two overlapping ''//
+ anyplot.ai'' comment blocks (one with ''Library: muix 7.29.1 | JavaScript 22.23.2''
+ and ''Quality: 87/100'', another with ''Library: MUI X Charts | React | Node 22''
+ and ''Quality: pending'') -- collapse this into a single clean header block.'
+ - MUI X is still used mostly as an SVG canvas (ChartContainer + useDrawingArea +
+ ChartsText) with series=[] -- no actual MUI X chart component (e.g. PieChart,
+ ScatterChart) or MUI X-specific tooltip/legend primitive is exercised, so Library
+ Mastery stays capped; this may be an inherent limit of circle-packing on the community
+ package rather than something fixable.
+ - Dataset still sits at the floor of the spec's size range (20 nodes / 3 levels),
+ unchanged from attempt 1 -- a fourth hierarchy level or more leaf nodes per branch
+ would better demonstrate the plot type's ability to show deep nesting.
+ image_description: |-
+ Light render (plot-light.png):
+ Background: Warm off-white cream matching #FAF8F1, not pure white.
+ Chrome: Title "circlepacking-basic · javascript · muix · anyplot.ai" in bold dark ink, fully legible. Subtitle "Investment portfolio by asset class and holding · circle area proportional to market value ($K)" in softer gray ink, legible. Legend row (5 color-dot + label pairs: Equities, Fixed Income, Real Estate, Alternatives, Cash & Equivalents) fully legible dark-on-cream. Root circle outlined in a subtle light-gray stroke.
+ Data: Branch circles use light-tinted fills with a 2px colored stroke matching their legend swatch (green, lavender, blue, ochre, red -- canonical Imprint order, first branch #009E73). Leaf circles are white-mixed tints of their branch color scaled by relative value, each labeled in bold dark or white text chosen by relative luminance. Every leaf now shows its FULL label on one or two wrapped lines (e.g. "Direct Property", "Corporate Bonds", "Government Bonds", "Int'l Developed", "Treasury Bills", "Money Market", "Emerging Markets", "Municipal Bonds") -- no ellipsis truncation anywhere, fixing the attempt-1 weakness.
+ Legibility verdict: PASS (all title/subtitle/legend/leaf text readable against the light background; no light-on-light failures)
+
+ Dark render (plot-dark.png):
+ Background: Warm near-black matching #1A1A17, not pure black.
+ Chrome: Title and subtitle rendered in light/off-white ink, fully legible against the dark background. Legend text is light-colored and legible. Root circle stroke rendered in light gray, visible against the dark background.
+ Data: Branch and leaf circle colors are visually identical to the light render (green, lavender, blue, ochre, red branches; same value-scaled tints and same wrapped full-text labels) -- only the chrome (background, title/subtitle/legend text, root stroke) flipped to light tones as expected. No dark-on-dark failures: leaf/branch labels stay legible because the text-color logic is luminance-based on the fill color itself, independent of the page theme.
+ Legibility verdict: PASS (all title/subtitle/legend/leaf text readable against the dark background; no dark-on-dark failures)
+ criteria_checklist:
+ visual_quality:
+ score: 30
+ max: 30
+ items:
+ - id: VQ-01
+ name: Text Legibility
+ score: 8
+ max: 8
+ passed: true
+ comment: 'Attempt-1 mid-word truncation is fully resolved: wrapLabel() balances
+ long names onto two lines via ChartsText''s native line-splitting. Every
+ leaf label (including ''Corporate Bonds'' $180K, ''Government Bonds'' $260K,
+ ''Int''l Developed'' $210K) now renders in full, in both themes.'
+ - id: VQ-02
+ name: No Overlap
+ score: 6
+ max: 6
+ passed: true
+ comment: No overlapping text or shapes in either render; two-line wrapped
+ labels stay within their circles with sensible name/value vertical stacking.
+ - id: VQ-03
+ name: Element Visibility
+ score: 6
+ max: 6
+ passed: true
+ comment: Circle sizes cleanly scale with value; force-simulation packing avoids
+ overlap while keeping siblings tightly grouped.
+ - id: VQ-04
+ name: Color Accessibility
+ score: 2
+ max: 2
+ passed: true
+ comment: Luminance-based text color switching (dark ink on light tints, cream
+ on saturated fills) keeps every label high-contrast; palette itself is CVD-safe.
+ - id: VQ-05
+ name: Layout & Canvas
+ score: 4
+ max: 4
+ passed: true
+ comment: Root circle fills the square canvas with balanced margins; legend
+ sits directly above the packing area; no overflow or clipping.
+ - id: VQ-06
+ name: Axis Labels & Title
+ score: 2
+ max: 2
+ passed: true
+ comment: Subtitle explicitly states the value unit ($K) and the size-encoding
+ rule.
+ - id: VQ-07
+ name: Palette Compliance
+ score: 2
+ max: 2
+ passed: true
+ comment: 'First branch is #009E73, remaining branches follow canonical Imprint
+ order; backgrounds and chrome are theme-correct in both renders; data colors
+ identical across themes.'
+ design_excellence:
+ score: 17
+ max: 20
+ items:
+ - id: DE-01
+ name: Aesthetic Sophistication
+ score: 6
+ max: 8
+ passed: true
+ comment: Thoughtful value-scaled tinting per branch, hand-rolled packing geometry,
+ luminance-adaptive text -- clearly above a configured default, though legend
+ styling stays simple.
+ - id: DE-02
+ name: Visual Refinement
+ score: 6
+ max: 6
+ passed: true
+ comment: Clean whitespace, subtle root/branch strokes, no grid clutter; the
+ truncated-label rough edge from attempt 1 is gone now that every label wraps
+ cleanly.
+ - id: DE-03
+ name: Data Storytelling
+ score: 5
+ max: 6
+ passed: true
+ comment: Color groups by asset class and tint intensity within a class both
+ encode meaningful structure (hue = category, shade = relative size).
+ spec_compliance:
+ score: 15
+ max: 15
+ items:
+ - id: SC-01
+ name: Plot Type
+ score: 5
+ max: 5
+ passed: true
+ comment: True nested circle packing (root -> branch -> leaf), matching the
+ spec's plot type.
+ - id: SC-02
+ name: Required Features
+ score: 4
+ max: 4
+ passed: true
+ comment: Color by category, labels on larger circles with hover-title tooltips
+ on smaller ones, root circle with padding around all children.
+ - id: SC-03
+ name: Data Mapping
+ score: 3
+ max: 3
+ passed: true
+ comment: id/parent/value/label fields all correctly consumed; value aggregates
+ bottom-up for branch/root sizing.
+ - id: SC-04
+ name: Title & Legend
+ score: 3
+ max: 3
+ passed: true
+ comment: Title matches the mandated format exactly; legend labels match the
+ branch names shown in the chart.
+ data_quality:
+ score: 14
+ max: 15
+ items:
+ - id: DQ-01
+ name: Feature Coverage
+ score: 5
+ max: 6
+ passed: true
+ comment: Still 20 nodes across 3 levels -- the floor of the spec's 20-200/2-4
+ range, unchanged from attempt 1; a deeper or larger dataset would better
+ showcase the plot type.
+ - id: DQ-02
+ name: Realistic Context
+ score: 5
+ max: 5
+ passed: true
+ comment: Investment portfolio composition is realistic, comprehensible, and
+ neutral.
+ - id: DQ-03
+ name: Appropriate Scale
+ score: 4
+ max: 4
+ passed: true
+ comment: Asset-class dollar values and relative proportions are plausible
+ for a diversified portfolio.
+ code_quality:
+ score: 9
+ max: 10
+ items:
+ - id: CQ-01
+ name: KISS Structure
+ score: 2
+ max: 3
+ passed: true
+ comment: Necessarily uses several helper functions since MUI X has no native
+ packing layout -- justified. Also has a duplicated/malformed file header
+ (two overlapping '// anyplot.ai' comment blocks) that should be cleaned
+ up to a single block.
+ - id: CQ-02
+ name: Reproducibility
+ score: 2
+ max: 2
+ passed: true
+ comment: Custom seeded LCG (seed=42) makes the packing simulation fully deterministic.
+ - id: CQ-03
+ name: Clean Imports
+ score: 2
+ max: 2
+ passed: true
+ comment: Only imports ChartContainer, useDrawingArea, and ChartsText from
+ @mui/x-charts, all used.
+ - id: CQ-04
+ name: Code Elegance
+ score: 2
+ max: 2
+ passed: true
+ comment: No fake UI or simulated interactivity; geometry code is appropriately
+ complex for a chart type the library doesn't natively support.
+ - id: CQ-05
+ name: Output & API
+ score: 1
+ max: 1
+ passed: true
+ comment: Default-exports the component, sizes to window.ANYPLOT_SIZE, sets
+ skipAnimation.
+ library_mastery:
+ score: 7
+ max: 10
+ items:
+ - id: LM-01
+ name: Idiomatic Usage
+ score: 4
+ max: 5
+ passed: true
+ comment: Uses ChartContainer + useDrawingArea + ChartsText (three genuine
+ MUI X primitives) for layout and text rendering, but still no actual MUI
+ X chart component (series=[]).
+ - id: LM-02
+ name: Distinctive Features
+ score: 3
+ max: 5
+ passed: true
+ comment: ChartsText's native '\n' line-splitting is used deliberately for
+ balanced two-line label wrapping, and useDrawingArea ties the custom geometry
+ into MUI X's own DrawingProvider margin system -- distinctive but not chart-type-specific
+ since packing is entirely hand-rolled.
+ verdict: APPROVED
+impl_tags:
+ dependencies: []
+ techniques:
+ - custom-legend
+ - annotations
+ patterns:
+ - data-generation
+ - iteration-over-groups
+ dataprep: []
+ styling:
+ - alpha-blending
+ - edge-highlighting