From 908bac88cbcfdcad99e16fa1096cec73706e7d3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 15:58:38 +0000 Subject: [PATCH 1/5] feat(muix): implement circlepacking-basic --- .../implementations/javascript/muix.tsx | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 plots/circlepacking-basic/implementations/javascript/muix.tsx diff --git a/plots/circlepacking-basic/implementations/javascript/muix.tsx b/plots/circlepacking-basic/implementations/javascript/muix.tsx new file mode 100644 index 00000000000..5f7350e7d50 --- /dev/null +++ b/plots/circlepacking-basic/implementations/javascript/muix.tsx @@ -0,0 +1,294 @@ +//# 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"; + +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}…`; +} + +// --- 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). ------------------- +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 maxChars = Math.max(4, Math.floor((l.r * 1.7) / (nameSize * 0.55))); + return ( + + + {`${branch.label} / ${leaf.label}: $${leaf.value}K`} + + {showName && ( + + {truncateLabel(leaf.label, maxChars)} + + )} + {showValue && ( + + {`$${leaf.value}K`} + + )} + + ); + })} + + ); + })} + + ); +} + +// --- 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) + + + + + ); +} From 0dea8722b6f8f10eaf224dfc2cf4c03b4b02a32f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 15:58:48 +0000 Subject: [PATCH 2/5] chore(muix): add metadata for circlepacking-basic --- .../metadata/javascript/muix.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/circlepacking-basic/metadata/javascript/muix.yaml diff --git a/plots/circlepacking-basic/metadata/javascript/muix.yaml b/plots/circlepacking-basic/metadata/javascript/muix.yaml new file mode 100644 index 00000000000..e9b440191f6 --- /dev/null +++ b/plots/circlepacking-basic/metadata/javascript/muix.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for muix implementation of circlepacking-basic +# Auto-generated by impl-generate.yml + +library: muix +language: javascript +specification_id: circlepacking-basic +created: '2026-09-02T15:58:48Z' +updated: '2026-09-02T15:58:48Z' +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: null +review: + strengths: [] + weaknesses: [] From 177aca520d58c25031bbc749b070971b6e46f20d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 16:04:08 +0000 Subject: [PATCH 3/5] chore(muix): update quality score 87 and review feedback for circlepacking-basic --- .../implementations/javascript/muix.tsx | 4 + .../metadata/javascript/muix.yaml | 252 +++++++++++++++++- 2 files changed, 249 insertions(+), 7 deletions(-) diff --git a/plots/circlepacking-basic/implementations/javascript/muix.tsx b/plots/circlepacking-basic/implementations/javascript/muix.tsx index 5f7350e7d50..9696ba4b8a0 100644 --- a/plots/circlepacking-basic/implementations/javascript/muix.tsx +++ b/plots/circlepacking-basic/implementations/javascript/muix.tsx @@ -1,3 +1,7 @@ +// anyplot.ai +// circlepacking-basic: Circle Packing Chart +// Library: muix 7.29.1 | JavaScript 22.23.2 +// Quality: 87/100 | Created: 2026-09-02 //# anyplot-orientation: square // anyplot.ai // circlepacking-basic: Circle Packing Chart diff --git a/plots/circlepacking-basic/metadata/javascript/muix.yaml b/plots/circlepacking-basic/metadata/javascript/muix.yaml index e9b440191f6..ac9df6ea0f7 100644 --- a/plots/circlepacking-basic/metadata/javascript/muix.yaml +++ b/plots/circlepacking-basic/metadata/javascript/muix.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for muix implementation of circlepacking-basic -# Auto-generated by impl-generate.yml - library: muix language: javascript specification_id: circlepacking-basic created: '2026-09-02T15:58:48Z' -updated: '2026-09-02T15:58:48Z' +updated: '2026-09-02T16:04:08Z' generated_by: claude-sonnet workflow_run: 33650517579 issue: 2498 @@ -15,7 +12,248 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/circlepac 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: null +quality_score: 87 review: - strengths: [] - weaknesses: [] + strengths: + - 'Correct Imprint palette usage: first branch #009E73, remaining branches in canonical + order, identical data colors between light/dark renders, theme-correct chrome + in both' + - Genuinely custom circle-packing layout (seeded force simulation) since the community + MUI X package has no built-in packing layout -- real engineering, not a workaround + shortcut, and fully deterministic (seed=42) + - 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 sensible + use of ChartContainer/useDrawingArea per the harness contract (skipAnimation, + ANYPLOT_SIZE sizing) + weaknesses: + - Several leaf labels truncate mid-word ('Direct...', 'Corporate...', 'Int'l...', + 'Municipal...', 'Treasury...', 'Emerging...', 'Money...') -- tune truncateLabel/maxChars + so more leaf circles show their full label, especially ones as large as 'Corporate + Bonds' ($180K) that currently truncate while smaller 'US Small Cap' ($140K) does + not + - MUI X is used only as an SVG canvas (ChartContainer + useDrawingArea) with series=[] + -- no actual MUI X chart component or MUI X-specific tooltip/legend primitive + is exercised, so Library Mastery stays low + - Dataset sits at the floor of the spec's size range (20 nodes / 3 levels) -- 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 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 -- text color correctly switches per fill so every label stays high-contrast. Several smaller/mid leaf labels are truncated with an ellipsis (e.g. "Direct...", "Treasury...", "Money...", "Corporate..."). + 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 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) -- 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, 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: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 6 + max: 8 + passed: true + comment: Explicit font sizing throughout (title, leaf name/value scaled by + radius), but several leaf labels truncate mid-word ('Direct...', 'Corporate...', + 'Int'l...', 'Municipal...', 'Treasury...', 'Emerging...', 'Money...'), losing + readable information at a glance. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text or shapes in either render. + - 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 ~70% of the square canvas with balanced margins; + legend sits directly above the packing area. + - 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: 16 + 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: 5 + max: 6 + passed: true + comment: Clean whitespace, subtle root/branch strokes, no grid clutter; truncated + labels are the one rough edge in an otherwise polished render. + - 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: 20 nodes across 3 levels sits at the floor of the spec's 20-200/2-4 + range; 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 but more than a bare KISS import/data/plot script. + - 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 and useDrawingArea from @mui/x-charts. + - 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: 5 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 3 + max: 5 + passed: true + comment: Uses ChartContainer + useDrawingArea (genuine MUI X layout primitives) + but passes series=[] and never touches an actual MUI X chart component. + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: useDrawingArea ties layout to MUI X's DrawingProvider margin system, + but nothing else in the render is MUI X-specific. + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - custom-legend + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending + - edge-highlighting From 01b65180a3f35b6f984e21b34dd44ede5412d7ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 16:10:06 +0000 Subject: [PATCH 4/5] fix(muix): address review feedback for circlepacking-basic Attempt 1/4 - fixes based on AI review --- .../implementations/javascript/muix.tsx | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/plots/circlepacking-basic/implementations/javascript/muix.tsx b/plots/circlepacking-basic/implementations/javascript/muix.tsx index 9696ba4b8a0..e09b5aa1756 100644 --- a/plots/circlepacking-basic/implementations/javascript/muix.tsx +++ b/plots/circlepacking-basic/implementations/javascript/muix.tsx @@ -10,6 +10,7 @@ // 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; @@ -179,6 +180,25 @@ function truncateLabel(label, 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 @@ -203,7 +223,12 @@ function Legend({ x, y, width: legendWidth }) { } // --- Circles: root boundary, branch zones (light fill + colored stroke), -// leaf holdings (solid tint, labeled when large enough). ------------------- +// 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; @@ -238,28 +263,41 @@ function CirclePacking() { const valueSize = Math.round(nameSize * 0.82); const showName = l.r >= 26; const showValue = l.r >= 40; - const maxChars = Math.max(4, Math.floor((l.r * 1.7) / (nameSize * 0.55))); + 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 && ( - - {truncateLabel(leaf.label, maxChars)} - + y={nameCenterY} + style={{ + fontSize: nameSize, + fontWeight: 600, + fill: ink, + textAnchor: "middle", + dominantBaseline: "central", + }} + /> )} {showValue && ( - - {`$${leaf.value}K`} - + )} ); From b8c8bb5aa84c29f302de73ae40b46501c9cc5708 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 16:15:19 +0000 Subject: [PATCH 5/5] chore(muix): update quality score 92 and review feedback for circlepacking-basic --- .../implementations/javascript/muix.tsx | 2 +- .../metadata/javascript/muix.yaml | 112 ++++++++++-------- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/plots/circlepacking-basic/implementations/javascript/muix.tsx b/plots/circlepacking-basic/implementations/javascript/muix.tsx index e09b5aa1756..2e7f84e9ca8 100644 --- a/plots/circlepacking-basic/implementations/javascript/muix.tsx +++ b/plots/circlepacking-basic/implementations/javascript/muix.tsx @@ -1,7 +1,7 @@ // anyplot.ai // circlepacking-basic: Circle Packing Chart // Library: muix 7.29.1 | JavaScript 22.23.2 -// Quality: 87/100 | Created: 2026-09-02 +// Quality: 92/100 | Created: 2026-09-02 //# anyplot-orientation: square // anyplot.ai // circlepacking-basic: Circle Packing Chart diff --git a/plots/circlepacking-basic/metadata/javascript/muix.yaml b/plots/circlepacking-basic/metadata/javascript/muix.yaml index ac9df6ea0f7..8f220c461bc 100644 --- a/plots/circlepacking-basic/metadata/javascript/muix.yaml +++ b/plots/circlepacking-basic/metadata/javascript/muix.yaml @@ -2,7 +2,7 @@ library: muix language: javascript specification_id: circlepacking-basic created: '2026-09-02T15:58:48Z' -updated: '2026-09-02T16:04:08Z' +updated: '2026-09-02T16:15:19Z' generated_by: claude-sonnet workflow_run: 33650517579 issue: 2498 @@ -12,64 +12,70 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/circlepac 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: 87 +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 - in both' - - Genuinely custom circle-packing layout (seeded force simulation) since the community - MUI X package has no built-in packing layout -- real engineering, not a workaround - shortcut, and fully deterministic (seed=42) + (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 sensible - use of ChartContainer/useDrawingArea per the harness contract (skipAnimation, + - 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: - - Several leaf labels truncate mid-word ('Direct...', 'Corporate...', 'Int'l...', - 'Municipal...', 'Treasury...', 'Emerging...', 'Money...') -- tune truncateLabel/maxChars - so more leaf circles show their full label, especially ones as large as 'Corporate - Bonds' ($180K) that currently truncate while smaller 'US Small Cap' ($140K) does - not - - MUI X is used only as an SVG canvas (ChartContainer + useDrawingArea) with series=[] - -- no actual MUI X chart component or MUI X-specific tooltip/legend primitive - is exercised, so Library Mastery stays low - - Dataset sits at the floor of the spec's size range (20 nodes / 3 levels) -- a - fourth hierarchy level or more leaf nodes per branch would better demonstrate - the plot type's ability to show deep nesting + - '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 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 -- text color correctly switches per fill so every label stays high-contrast. Several smaller/mid leaf labels are truncated with an ellipsis (e.g. "Direct...", "Treasury...", "Money...", "Corporate..."). + 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 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) -- 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, independent of the page theme. + 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: 28 + score: 30 max: 30 items: - id: VQ-01 name: Text Legibility - score: 6 + score: 8 max: 8 passed: true - comment: Explicit font sizing throughout (title, leaf name/value scaled by - radius), but several leaf labels truncate mid-word ('Direct...', 'Corporate...', - 'Int'l...', 'Municipal...', 'Treasury...', 'Emerging...', 'Money...'), losing - readable information at a glance. + 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. + 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 @@ -89,8 +95,8 @@ review: score: 4 max: 4 passed: true - comment: Root circle fills ~70% of the square canvas with balanced margins; - legend sits directly above the packing area. + 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 @@ -107,7 +113,7 @@ review: order; backgrounds and chrome are theme-correct in both renders; data colors identical across themes.' design_excellence: - score: 16 + score: 17 max: 20 items: - id: DE-01 @@ -120,11 +126,12 @@ review: styling stays simple. - id: DE-02 name: Visual Refinement - score: 5 + score: 6 max: 6 passed: true - comment: Clean whitespace, subtle root/branch strokes, no grid clutter; truncated - labels are the one rough edge in an otherwise polished render. + 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 @@ -173,8 +180,9 @@ review: score: 5 max: 6 passed: true - comment: 20 nodes across 3 levels sits at the floor of the spec's 20-200/2-4 - range; a deeper or larger dataset would better showcase the plot type. + 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 @@ -199,7 +207,9 @@ review: max: 3 passed: true comment: Necessarily uses several helper functions since MUI X has no native - packing layout -- justified but more than a bare KISS import/data/plot script. + 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 @@ -211,7 +221,8 @@ review: score: 2 max: 2 passed: true - comment: Only imports ChartContainer and useDrawingArea from @mui/x-charts. + comment: Only imports ChartContainer, useDrawingArea, and ChartsText from + @mui/x-charts, all used. - id: CQ-04 name: Code Elegance score: 2 @@ -227,24 +238,27 @@ review: comment: Default-exports the component, sizes to window.ANYPLOT_SIZE, sets skipAnimation. library_mastery: - score: 5 + score: 7 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 3 + score: 4 max: 5 passed: true - comment: Uses ChartContainer + useDrawingArea (genuine MUI X layout primitives) - but passes series=[] and never touches an actual MUI X chart component. + 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: 2 + score: 3 max: 5 - passed: false - comment: useDrawingArea ties layout to MUI X's DrawingProvider margin system, - but nothing else in the render is MUI X-specific. - verdict: REJECTED + 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: