diff --git a/plots/icicle-basic/implementations/javascript/muix.tsx b/plots/icicle-basic/implementations/javascript/muix.tsx index 4a97be8458c..67a36f11ce2 100644 --- a/plots/icicle-basic/implementations/javascript/muix.tsx +++ b/plots/icicle-basic/implementations/javascript/muix.tsx @@ -1,363 +1,408 @@ // anyplot.ai // icicle-basic: Basic Icicle Chart -// Library: muix 7.29.1 | JavaScript 22.23.2 -// Quality: 86/100 | Created: 2026-09-05 -import { ChartContainer } from "@mui/x-charts/ChartContainer"; +// Library: muix 9.16.0 | JavaScript 22.23.3 +// Quality: 83/100 | Updated: 2026-10-24 + +import { ChartsContainer } from "@mui/x-charts/ChartsContainer"; import { useDrawingArea } from "@mui/x-charts/hooks"; +import { getLuminance } from "@mui/material/styles"; const t = window.ANYPLOT_TOKENS; -const title = "icicle-basic · javascript · muix · anyplot.ai"; -const titleFontSize = Math.round(30 * Math.min(1, 67 / title.length)); +// Constants +const FONT = '"Plus Jakarta Sans", "Inter", -apple-system, BlinkMacSystemFont, sans-serif'; +const MONO = '"JetBrains Mono", "SFMono-Regular", Menlo, monospace'; +const ROOT_FILL = "#6E6A61"; +const FIXED_DARK_TEXT = "#1A1A17"; +const FIXED_LIGHT_TEXT = "#FAF8F1"; +const TINT_STEP = 0.24; +const ROW_GUTTER = 5; +const COL_GUTTER = 2; +const LABEL_FONT_SIZE = [20, 16, 15, 14]; +const SUBLABEL_FONT_SIZE = 14; +const CHAR_WIDTH_RATIO = 0.56; -// --- Data: a file-system hierarchy (name/children/value in KB) -- one of the -// spec's listed applications. 4 top-level directories, 11 subdirectories, 25 -// files -- 41 nodes total, well inside the spec's 10-100 node range. Only -// leaves carry an explicit size; directory sizes are the sum of their -// contents, exactly like `du` on a real file system. ----------------------- -const TREE = { - name: "repo/", - children: [ - { - name: "node_modules/", - children: [ - { - name: "react/", - children: [ - { name: "index.js", value: 180 }, - { name: "package.json", value: 60 }, - { name: "README.md", value: 80 }, - ], - }, - { - name: "webpack/", - children: [ - { name: "webpack.js", value: 140 }, - { name: "loader.js", value: 90 }, - { name: "config.js", value: 50 }, - ], - }, - { - name: "typescript/", - children: [ - { name: "tsc.js", value: 120 }, - { name: "lib.d.ts", value: 70 }, - { name: "compiler.js", value: 60 }, - ], - }, - { - name: "eslint/", - children: [ - { name: "index.js", value: 55 }, - { name: "rules.js", value: 45 }, - ], - }, - ], - }, - { - name: "src/", - children: [ - { - name: "components/", - children: [ - { name: "Button.tsx", value: 90 }, - { name: "Modal.tsx", value: 110 }, - { name: "Chart.tsx", value: 80 }, - ], - }, - { - name: "utils/", - children: [ - { name: "format.ts", value: 100 }, - { name: "api.ts", value: 90 }, - ], - }, - { - name: "styles/", - children: [ - { name: "theme.css", value: 75 }, - { name: "globals.css", value: 55 }, - ], - }, - ], - }, - { - name: "tests/", - children: [ - { - name: "unit/", - children: [ - { name: "button.test.ts", value: 100 }, - { name: "utils.test.ts", value: 90 }, - ], - }, - { - name: "integration/", - children: [ - { name: "api.test.ts", value: 85 }, - { name: "flow.test.ts", value: 75 }, - ], - }, - ], - }, - { - name: "docs/", - children: [ - { - name: "guides/", - children: [ - { name: "getting-started.md", value: 70 }, - { name: "deployment.md", value: 60 }, - ], - }, - { - name: "reference/", - children: [{ name: "api-reference.md", value: 70 }], - }, - ], - }, - ], -}; +// Columns: set these to your data's column names +const ID = "Node path"; // role: id +const NAME = "Node name"; // role: name +const PARENT = "Parent path"; // role: parent +const VALUE = "Size (KB)"; // role: value -// Bottom-up: a directory's size is the sum of what it contains (leaves carry -// their own explicit `value`). -function computeValue(node) { - if (!node.children) return node.value; - node.value = node.children.reduce((sum, c) => sum + computeValue(c), 0); - return node.value; -} -computeValue(TREE); +// Data: replace this block with your own records +const data: Array<{ + [ID]: string; + [NAME]: string; + [PARENT]: string; + [VALUE]: number; +}> = [ + { [ID]: "repo/", [NAME]: "repo/", [PARENT]: "", [VALUE]: 0 }, + // Top-level directories + { [ID]: "repo/dist/", [NAME]: "dist/", [PARENT]: "repo/", [VALUE]: 0 }, + { [ID]: "repo/src/", [NAME]: "src/", [PARENT]: "repo/", [VALUE]: 0 }, + { [ID]: "repo/tests/", [NAME]: "tests/", [PARENT]: "repo/", [VALUE]: 0 }, + { [ID]: "repo/docs/", [NAME]: "docs/", [PARENT]: "repo/", [VALUE]: 0 }, + // Subdirectories under dist/ (build output) + { [ID]: "repo/dist/js/", [NAME]: "js/", [PARENT]: "repo/dist/", [VALUE]: 0 }, + { [ID]: "repo/dist/assets/", [NAME]: "assets/", [PARENT]: "repo/dist/", [VALUE]: 0 }, + { [ID]: "repo/dist/fonts/", [NAME]: "fonts/", [PARENT]: "repo/dist/", [VALUE]: 0 }, + { [ID]: "repo/dist/css/", [NAME]: "css/", [PARENT]: "repo/dist/", [VALUE]: 0 }, + // Subdirectories under src/ + { [ID]: "repo/src/components/", [NAME]: "components/", [PARENT]: "repo/src/", [VALUE]: 0 }, + { [ID]: "repo/src/utils/", [NAME]: "utils/", [PARENT]: "repo/src/", [VALUE]: 0 }, + { [ID]: "repo/src/styles/", [NAME]: "styles/", [PARENT]: "repo/src/", [VALUE]: 0 }, + // Subdirectories under tests/ + { [ID]: "repo/tests/unit/", [NAME]: "unit/", [PARENT]: "repo/tests/", [VALUE]: 0 }, + { [ID]: "repo/tests/integration/", [NAME]: "integration/", [PARENT]: "repo/tests/", [VALUE]: 0 }, + // Subdirectories under docs/ + { [ID]: "repo/docs/guides/", [NAME]: "guides/", [PARENT]: "repo/docs/", [VALUE]: 0 }, + { [ID]: "repo/docs/reference/", [NAME]: "reference/", [PARENT]: "repo/docs/", [VALUE]: 0 }, + // Files under js/ + { [ID]: "repo/dist/js/vendor.js", [NAME]: "vendor.js", [PARENT]: "repo/dist/js/", [VALUE]: 180 }, + { [ID]: "repo/dist/js/polyfills.js", [NAME]: "polyfills.js", [PARENT]: "repo/dist/js/", [VALUE]: 60 }, + { [ID]: "repo/dist/js/main.js", [NAME]: "main.js", [PARENT]: "repo/dist/js/", [VALUE]: 80 }, + // Files under assets/ + { [ID]: "repo/dist/assets/hero.webp", [NAME]: "hero.webp", [PARENT]: "repo/dist/assets/", [VALUE]: 140 }, + { [ID]: "repo/dist/assets/logo.png", [NAME]: "logo.png", [PARENT]: "repo/dist/assets/", [VALUE]: 90 }, + { [ID]: "repo/dist/assets/icons.svg", [NAME]: "icons.svg", [PARENT]: "repo/dist/assets/", [VALUE]: 50 }, + // Files under fonts/ + { [ID]: "repo/dist/fonts/inter.woff2", [NAME]: "inter.woff2", [PARENT]: "repo/dist/fonts/", [VALUE]: 120 }, + { [ID]: "repo/dist/fonts/jakarta.woff2", [NAME]: "jakarta.woff2", [PARENT]: "repo/dist/fonts/", [VALUE]: 70 }, + { [ID]: "repo/dist/fonts/mono.woff2", [NAME]: "mono.woff2", [PARENT]: "repo/dist/fonts/", [VALUE]: 60 }, + // Files under css/ + { [ID]: "repo/dist/css/main.css", [NAME]: "main.css", [PARENT]: "repo/dist/css/", [VALUE]: 55 }, + { [ID]: "repo/dist/css/vendor.css", [NAME]: "vendor.css", [PARENT]: "repo/dist/css/", [VALUE]: 45 }, + // Files under components/ + { [ID]: "repo/src/components/Button.tsx", [NAME]: "Button.tsx", [PARENT]: "repo/src/components/", [VALUE]: 90 }, + { [ID]: "repo/src/components/Modal.tsx", [NAME]: "Modal.tsx", [PARENT]: "repo/src/components/", [VALUE]: 110 }, + { [ID]: "repo/src/components/Chart.tsx", [NAME]: "Chart.tsx", [PARENT]: "repo/src/components/", [VALUE]: 80 }, + // Files under utils/ + { [ID]: "repo/src/utils/format.ts", [NAME]: "format.ts", [PARENT]: "repo/src/utils/", [VALUE]: 100 }, + { [ID]: "repo/src/utils/api.ts", [NAME]: "api.ts", [PARENT]: "repo/src/utils/", [VALUE]: 90 }, + // Files under styles/ + { [ID]: "repo/src/styles/theme.css", [NAME]: "theme.css", [PARENT]: "repo/src/styles/", [VALUE]: 75 }, + { [ID]: "repo/src/styles/globals.css", [NAME]: "globals.css", [PARENT]: "repo/src/styles/", [VALUE]: 55 }, + // Files under unit/ + { [ID]: "repo/tests/unit/button.test.ts", [NAME]: "button.test.ts", [PARENT]: "repo/tests/unit/", [VALUE]: 100 }, + { [ID]: "repo/tests/unit/utils.test.ts", [NAME]: "utils.test.ts", [PARENT]: "repo/tests/unit/", [VALUE]: 90 }, + // Files under integration/ + { [ID]: "repo/tests/integration/api.test.ts", [NAME]: "api.test.ts", [PARENT]: "repo/tests/integration/", [VALUE]: 85 }, + { [ID]: "repo/tests/integration/flow.test.ts", [NAME]: "flow.test.ts", [PARENT]: "repo/tests/integration/", [VALUE]: 75 }, + // Files under guides/ + { [ID]: "repo/docs/guides/getting-started.md", [NAME]: "getting-started.md", [PARENT]: "repo/docs/guides/", [VALUE]: 70 }, + { [ID]: "repo/docs/guides/deployment.md", [NAME]: "deployment.md", [PARENT]: "repo/docs/guides/", [VALUE]: 60 }, + // Files under reference/ + { [ID]: "repo/docs/reference/api-reference.md", [NAME]: "api-reference.md", [PARENT]: "repo/docs/reference/", [VALUE]: 70 }, +]; -// Fixed neutral grey for the root/total band -- unlike `t.ink`, this stays -// pixel-identical across light and dark renders, so every data band -// (including root) is theme-stable, not just the top-level branches. -const ROOT_FILL = "#6B6A63"; +// Plot: reads only data and the constants above +const unitMatch = VALUE.match(/\(([^)]+)\)/); +const valueUnit = unitMatch ? unitMatch[1] : ""; +const unitSuffix = valueUnit ? ` ${valueUnit}` : ""; -// Each top-level directory owns one Imprint hue; every descendant inherits -// its ancestor's hue so a branch stays recognizable at any depth (the -// spec's "color by hierarchy level or category" note). -function assignBranch(node, color) { - node.branchColor = color; - node.children?.forEach((c) => assignBranch(c, color)); +interface IcicleNode { + id: string; + name: string; + parentId: string; + value: number; + depth: number; + childrenIds: string[]; + branchColor: string; + x0Norm: number; + x1Norm: number; + fill: string; + textFill: string; + parentValue: number; } -assignBranch(TREE, ROOT_FILL); // root itself reads as the neutral/total anchor -TREE.children.forEach((dir, i) => assignBranch(dir, t.palette[i % t.palette.length])); -// --- Icicle (partition) layout: each depth is a full-width horizontal band; -// within a band, a node's horizontal span is proportional to its value -// within its parent's span. Root at top, children stacked below -- the -// spec's "horizontal orientation, root at top" note. ------------------------ -function partition(node, x0, x1, depth) { - node.x0 = x0; - node.x1 = x1; - node.depth = depth; - if (!node.children) return; - let cx = x0; - const total = node.value; - node.children.forEach((child) => { - const w = total > 0 ? (child.value / total) * (x1 - x0) : 0; - partition(child, cx, cx + w, depth + 1); - cx += w; +const nodeById = new Map(); +for (let i = 0; i < data.length; i += 1) { + const row = data[i]; + nodeById.set(row[ID], { + id: row[ID], + name: row[NAME], + parentId: row[PARENT], + value: row[VALUE], + depth: 0, + childrenIds: [], + branchColor: ROOT_FILL, + x0Norm: 0, + x1Norm: 1, + fill: ROOT_FILL, + textFill: FIXED_LIGHT_TEXT, + parentValue: 0, }); } -function flatten(node, acc) { - acc.push(node); - node.children?.forEach((c) => flatten(c, acc)); - return acc; +let rootId = ""; +for (let i = 0; i < data.length; i += 1) { + const row = data[i]; + const node = nodeById.get(row[ID])!; + if (!node.parentId) { + rootId = node.id; + } else { + const parent = nodeById.get(node.parentId); + if (parent) parent.childrenIds.push(node.id); + } } -const DEPTH_COUNT = 4; // root, directory, subdirectory, file -const TINT_STEP = 0.28; // lighter per depth level below the branch's directory - -// --- Color: contrast-aware text over an arbitrary tinted fill, same -// relative-luminance technique used for every other custom-drawn anyplot -// chart so labels stay legible regardless of hue or tint. ------------------- -function hexToRgb(hex) { - const n = parseInt(hex.slice(1), 16); - return [(n >> 16) & 255, (n >> 8) & 255, n & 255]; -} -function rgbToHsl(r, g, b) { - r /= 255; - g /= 255; - b /= 255; - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - const l = (max + min) / 2; - if (max === min) return [0, 0, l]; - const d = max - min; - const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - let h; - if (max === r) h = (g - b) / d + (g < b ? 6 : 0); - else if (max === g) h = (b - r) / d + 2; - else h = (r - g) / d + 4; - return [h / 6, s, l]; -} -function hueToRgb(p, q, tIn) { - let tt = tIn; - if (tt < 0) tt += 1; - if (tt > 1) tt -= 1; - if (tt < 1 / 6) return p + (q - p) * 6 * tt; - if (tt < 1 / 2) return q; - if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6; - return p; -} -function hslToRgb(h, s, l) { - if (s === 0) { - const v = Math.round(l * 255); - return [v, v, v]; +// Topological BFS order from root +const bfsOrder: IcicleNode[] = []; +const queue: string[] = [rootId]; +while (queue.length > 0) { + const curId = queue.shift()!; + const cur = nodeById.get(curId)!; + bfsOrder.push(cur); + for (let c = 0; c < cur.childrenIds.length; c += 1) { + const child = nodeById.get(cur.childrenIds[c])!; + child.depth = cur.depth + 1; + queue.push(child.id); } - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - return [Math.round(hueToRgb(p, q, h + 1 / 3) * 255), Math.round(hueToRgb(p, q, h) * 255), Math.round(hueToRgb(p, q, h - 1 / 3) * 255)]; -} -function tintRgb(hex, factor) { - const [r, g, b] = hexToRgb(hex); - const [h, s, l] = rgbToHsl(r, g, b); - return hslToRgb(h, s, l + (0.94 - l) * factor); -} -function relLuminanceRgb([r, g, b]) { - const srgb = [r, g, b].map((v) => v / 255).map((v) => (v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4))); - return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2]; } -// Fixed, theme-independent ink/paper pair -- the data fills themselves don't -// change between themes, so the contrast decision must depend only on the -// fill's own luminance, never on `t.ink`/`t.pageBg` (which flip meaning -// between light and dark theme and previously produced near-invisible text -// on the leaf-level tints in dark mode). -const FIXED_DARK_TEXT = "#1A1A17"; -const FIXED_LIGHT_TEXT = "#FFFDF6"; -function textColorForRgb(rgb) { - return relLuminanceRgb(rgb) > 0.45 ? FIXED_DARK_TEXT : FIXED_LIGHT_TEXT; + +// Bottom-up sum of leaf sizes +for (let i = bfsOrder.length - 1; i >= 0; i -= 1) { + const node = bfsOrder[i]; + if (node.childrenIds.length > 0) { + let sum = 0; + for (let c = 0; c < node.childrenIds.length; c += 1) { + sum += nodeById.get(node.childrenIds[c])!.value; + } + node.value = sum; + } } -// --- Sizing + label fitting ------------------------------------------------- -const MARGIN = { top: 96, right: 24, bottom: 20, left: 24 }; -const ROW_GUTTER = 5; -const COL_GUTTER = 2; -const LABEL_FONT_SIZE = [22, 17, 15, 13]; // by depth -- 15/13 for the two deepest levels (was 14/12) so labels hold up better at mobile thumbnail scale -const CHAR_WIDTH_RATIO = 0.56; +// Top-down branch color assignment and normalized [0, 1] partition +const rootNode = nodeById.get(rootId)!; +const topDirs = rootNode.childrenIds.map((id, idx) => { + const dir = nodeById.get(id)!; + dir.branchColor = t.palette[idx]; + return dir; +}); -function fmtKB(v) { - return `${v.toLocaleString()} KB`; -} -function fitsLabel(w, h, text, fontSize) { - return w - 10 >= text.length * fontSize * CHAR_WIDTH_RATIO && h >= fontSize + 4; -} +for (let i = 0; i < bfsOrder.length; i += 1) { + const node = bfsOrder[i]; + if (node.depth >= 2) { + const parent = nodeById.get(node.parentId)!; + node.branchColor = parent.branchColor; + } + if (node.parentId) { + node.parentValue = nodeById.get(node.parentId)!.value; + } else { + node.parentValue = node.value; + } -function IcicleRect({ x, y, w, h, fill, textFill, label, sublabel, fontSize, tooltip }) { - const showLabel = label && fitsLabel(w, h, label, fontSize); - const showSub = showLabel && sublabel && h >= fontSize * 2 + 8 && fitsLabel(w, h, sublabel, fontSize - 2); - return ( - - - {tooltip} - - {showLabel && ( - - {label} - - )} - {showSub && ( - - {sublabel} - - )} - - ); + let cx = node.x0Norm; + const span = node.x1Norm - node.x0Norm; + for (let c = 0; c < node.childrenIds.length; c += 1) { + const child = nodeById.get(node.childrenIds[c])!; + const w = node.value > 0 ? (child.value / node.value) * span : 0; + child.x0Norm = cx; + child.x1Norm = cx + w; + cx += w; + } + + // Tint the branch hue toward the warm paper per depth; label ink by luminance + const hex = node.depth === 0 ? ROOT_FILL : node.branchColor; + const hexInt = parseInt(hex.slice(1), 16); + const baseR = (hexInt >> 16) & 255; + const baseG = (hexInt >> 8) & 255; + const baseB = hexInt & 255; + const tintFactor = node.depth === 0 ? 0 : (node.depth - 1) * TINT_STEP; + const r = Math.round(baseR + (248 - baseR) * tintFactor); + const g = Math.round(baseG + (246 - baseG) * tintFactor); + const b = Math.round(baseB + (238 - baseB) * tintFactor); + node.fill = `rgb(${r}, ${g}, ${b})`; + node.textFill = getLuminance(node.fill) > 0.18 ? FIXED_DARK_TEXT : FIXED_LIGHT_TEXT; } -function Icicle() { - // Drawing area comes from MUI X's own DrawingProvider (via ChartContainer's - // `margin` prop), the documented composition primitive for a custom mark -- - // MUI X community has no native Icicle/partition chart type. +const maxDepth = Math.max(...bfsOrder.map((n) => n.depth)); +const DEPTH_COUNT = maxDepth + 1; +const leafCount = bfsOrder.filter((n) => n.childrenIds.length === 0).length; + +function IcicleLayer() { const { left, top, width, height } = useDrawingArea(); - partition(TREE, left, left + width, 0); - const nodes = flatten(TREE, []); const rowH = height / DEPTH_COUNT; return ( - {nodes.map((node) => { - const x = node.x0 + COL_GUTTER / 2; - const w = Math.max(0, node.x1 - node.x0 - COL_GUTTER); + {bfsOrder.map((node) => { + const x = left + node.x0Norm * width + COL_GUTTER / 2; + const w = Math.max(0, (node.x1Norm - node.x0Norm) * width - COL_GUTTER); const y = top + node.depth * rowH + ROW_GUTTER / 2; const h = rowH - ROW_GUTTER; - const fontSize = LABEL_FONT_SIZE[node.depth]; + const fontSize = + LABEL_FONT_SIZE[ + Math.min(node.depth, LABEL_FONT_SIZE.length - 1) + ]; - if (node.depth === 0) { - return ( - - ); - } + const labelText = + node.depth === 0 + ? `${node.name} · ${node.value.toLocaleString()}${unitSuffix} total` + : node.name; + const subText = + node.depth === 0 ? "" : `${node.value.toLocaleString()}${unitSuffix}`; + const pct = + node.parentValue > 0 + ? ((node.value / node.parentValue) * 100).toFixed(0) + : "100"; + + const showLabel = + w - 12 >= labelText.length * fontSize * CHAR_WIDTH_RATIO && + h >= fontSize + 6; + const showSub = + showLabel && + subText.length > 0 && + h >= fontSize + SUBLABEL_FONT_SIZE + 12 && + w - 12 >= subText.length * SUBLABEL_FONT_SIZE * CHAR_WIDTH_RATIO; - const tintFactor = (node.depth - 1) * TINT_STEP; - const rgb = tintRgb(node.branchColor, tintFactor); - const fill = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; - const parentValue = findParentValue(TREE, node); - const pct = parentValue > 0 ? ((node.value / parentValue) * 100).toFixed(0) : "0"; return ( - + + + + {node.id}: {node.value.toLocaleString()} + {unitSuffix} ({pct}% of parent) + + + {showLabel && ( + + {labelText} + + )} + {showSub && ( + + {subText} + + )} + ); })} ); } -// Walks the tree to find `target`'s parent value, used for the "% of parent" -// tooltip -- flatten() discards parent links, so this is the cheap way back. -function findParentValue(node, target) { - if (!node.children) return node.value; - if (node.children.includes(target)) return node.value; - for (const c of node.children) { - const found = findParentValue(c, target); - if (found !== undefined) return found; - } - return undefined; -} - -// --- Chart (default-exported component -- the harness mounts it) ---------- -// ChartContainer supplies the SVG root and theme context; its -// `margin` prop drives the DrawingProvider that Icicle() reads back via -// useDrawingArea(), so the title/plot split uses MUI X's own layout system -// rather than a parallel hand-rolled offset. The bands themselves are laid -// out in absolute pixel space via partition() above, so no axis/scale is -// needed -- xAxis/yAxis are omitted entirely. export default function Chart() { const { width, height } = window.ANYPLOT_SIZE; + const headerHeight = 84; + const chartHeight = height - headerHeight; return ( - - - {title} - - - Repository file sizes by directory depth · width ∝ size (KB) - - - +
+
+
+
+ icicle-basic · javascript · muix · anyplot.ai +
+
+ {DEPTH_COUNT} levels · {leafCount} leaves ·{" "} + {rootNode.value.toLocaleString()} + {unitSuffix} total · width ∝ {VALUE} +
+
+ +
+ {topDirs.map((dir) => ( +
+ + + {dir.name}{" "} + + ({dir.value.toLocaleString()} + {unitSuffix}) + + +
+ ))} +
+
+ + + + +
); } diff --git a/plots/icicle-basic/metadata/javascript/muix.yaml b/plots/icicle-basic/metadata/javascript/muix.yaml index ccc951650bf..aa050bd7f43 100644 --- a/plots/icicle-basic/metadata/javascript/muix.yaml +++ b/plots/icicle-basic/metadata/javascript/muix.yaml @@ -2,64 +2,60 @@ library: muix language: javascript specification_id: icicle-basic created: '2026-09-05T05:36:47Z' -updated: '2026-09-05T06:00:35Z' -generated_by: claude-sonnet -workflow_run: 33947337379 +updated: '2026-10-24T18:24:49Z' +generated_by: gemini-4-argon +adopted_from: 5b2bcb3e5e4dc5b3685a669c07cf3ab1b9918f69 (5b2bcb3e5e4dc5b3685a669c07cf3ab1b9918f69) +workflow_run: 40084393541 issue: 2497 -language_version: 22.23.2 -library_version: 7.29.1 +language_version: 22.23.3 +library_version: 9.16.0 preview_url_light: https://storage.googleapis.com/anyplot-images/plots/icicle-basic/javascript/muix/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/icicle-basic/javascript/muix/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/icicle-basic/javascript/muix/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/icicle-basic/javascript/muix/plot-dark.html -quality_score: 86 +quality_score: 83 review: strengths: - - 'The Review-2 dark-mode text-legibility bug is fully fixed: textColorForRgb() - now branches on two fixed, theme-independent absolute colors (FIXED_DARK_TEXT - #1A1A17 / FIXED_LIGHT_TEXT #FFFDF6) chosen purely from the fill''s own relative - luminance, instead of the theme tokens t.ink/t.pageBg. Verified by pixel-cropping - the leaf-file row in plot-dark.png: labels like ''index.js / 180 KB'' render in - clear dark text on the mint tint, matching the light render.' - - 'Correct icicle/partition math: bottom-up value aggregation (computeValue) mirrors - du-style directory sizing, and partition() produces gapless, proportional bands - per the spec''s width-proportional-to-value requirement.' - - 'Palette compliance is excellent and theme-stable: node_modules/ uses Imprint - #009E73, remaining top-level branches follow canonical Imprint order (lavender, - blue, ochre), the root band uses a fixed neutral grey, and all data fills are - byte-identical between light and dark renders -- only chrome (title, subtitle, - page background) flips.' - - fitsLabel() correctly hides labels on boxes too small to hold them (both primary - label and KB sublabel gated independently), matching the spec's 'label rectangles - with sufficient space, hide labels for small nodes' note. - - 'Realistic, in-range dataset: 41-node file-system hierarchy (4 top-level directories, - 11 subdirectories, 25 files) sits well inside the spec''s 10-100 node recommendation, - with plausible KB sizes.' - - 'Solid harness compliance: single default export, sized from window.ANYPLOT_SIZE, - skipAnimation set, no AR-09 edge clipping on any border in either render.' + - 'Correct icicle layout: one row per level with the root at the top, every child + within its parent''s horizontal extent, siblings adjacent with thin page-colored + gutters; leaf sums fall out of a bottom-up aggregation (dist 950, src 600, tests + 350, docs 200, total 2,100 KB all verified)' + - 'Color by top-level branch in canonical Imprint order (#009E73, #C475FD, #4467A3, + #BD8233), tinted lighter per depth, so branch membership and hierarchy level read + at once and identically in both themes' + - Label ink chosen per rectangle from the fill's luminance via MUI's getLuminance, + so every label is readable against its fill in both themes + - 'Two-tier label typography: sans-serif bold names over monospace values, with + font size stepping down by depth; labels are suppressed on rectangles too narrow + to hold them, as the Notes ask' + - Uses ChartsContainer composition with a custom SVG layer positioned by the useDrawingArea + hook, the community MUI X way to draw a chart type it has no component for; native + SVG tooltips give path, size and share of parent in the HTML weaknesses: - - Library Mastery is thin beyond the required custom-mark composition primitive - -- no use of MUI X's own ChartsTooltip/ChartsLegend components; hover text relies - on a plain SVG <title> element instead. - - Design Excellence is competent but generic -- the HSL tint ramp and consistent - gutters are clean, but there is no additional visual-hierarchy device (e.g. an - accent outline or subtle emphasis on the single largest branch/file) beyond the - color-by-branch scheme. - - The root band's own label ('repo/ · 2,100 KB total') is set at the same modest - fontsize step as other depth-0 content despite occupying by far the largest area - of the canvas -- a slightly larger or bolder treatment would let the root read - more clearly as the anchor of the whole chart. + - 'VQ-07 (both): the root block is filled with the custom hex #6E6A61, which is + neither an Imprint position nor the theme-adaptive muted anchor → the muted anchor + (#6B6A63 light / #A8A79F dark), with label ink still chosen by luminance. Likely + cause: the ROOT_FILL constant.' + - 'CQ-04 (code): lines 205–214 parse the hex and blend each channel toward the paper + by hand, which @mui/material/styles'' lighten(color, coefficient) computes in + one call → node.fill = lighten(hex, tintFactor). Likely cause: a written-out color + blend instead of the MUI color helper already imported alongside getLuminance.' + - 'Suggestion: 21 of the 25 leaf rectangles carry no label although each is about + 235 px tall; a vertical (rotated) name for the tall narrow leaves would identify + the files in the static PNG.' + - 'Suggestion: the root row spends a quarter of the chart height on one grey block; + a shorter root row would give the leaf row more room.' image_description: |- Light render (plot-light.png): - Background: warm off-white, matches #FAF8F1. - Chrome: bold dark title "icicle-basic · javascript · muix · anyplot.ai" and muted secondary-tone subtitle "Repository file sizes by directory depth · width ∝ size (KB)" -- both crisp and fully readable against the light background. - Data: 4-row icicle (partition) layout. Row 0 is a full-width neutral-grey "repo/ · 2,100 KB total" root band with white text. Row 1 has four Imprint-colored top-level directories in canonical order: node_modules/ = #009E73 brand green, src/ = lavender, tests/ = blue, docs/ = ochre, each with a white label. Row 2 (subdirectories) and row 3 (leaf files) use progressively lighter HSL tints of their ancestor's branch color, with automatically contrast-selected dark or light text; wide-enough leaf boxes (index.js, webpack.js, tsc.js, Modal.tsx, api.ts) show name + KB sublabel in dark ink, narrower leaf boxes correctly hide their labels. No overlapping text, no clipping at any canvas edge. + Background: warm off-white #FAF8F1 (sampled 250,248,241). + Chrome: title "icicle-basic · javascript · muix · anyplot.ai" in dark bold sans at top left, a monospace subtitle "4 levels · 25 leaves · 2,100 KB total · width ∝ Size (KB)" in soft ink below it, and at top right a legend of four swatches (dist/ 950 KB, src/ 600 KB, tests/ 350 KB, docs/ 200 KB). All dark text, all readable. + Data: four equal-height rows. Row 1: the root "repo/ · 2,100 KB total" as one full-width grey (#6E6A61) block with white label. Row 2: dist/ (#009E73), src/ (#C475FD), tests/ (#4467A3), docs/ (#BD8233) with widths proportional to 950/600/350/200. Row 3: 11 subdirectories in lighter tints of their branch hue, labelled with name + size except the narrow docs/reference/. Row 4: 25 files in the lightest tints; only vendor.js, hero.webp and api.ts carry labels, the others are narrow and unlabelled. Thin page-colored gutters separate every rectangle; children sit within their parents. First series is #009E73. Legibility verdict: PASS Dark render (plot-dark.png): - Background: warm near-black, matches #1A1A17. - Chrome: light/cream title and subtitle, both fully readable against the dark background. - Data: confirmed pixel-identical fill colors to the light render (root grey, node_modules/ green, src/ lavender, tests/ blue, docs/ ochre, and all descendant tints) -- only the page chrome flips. Critically, the leaf-file row (previously the Review-2 rejection reason: near-invisible ~1.1:1 contrast text from using theme tokens instead of fixed colors) is now fully legible: a pixel crop of the leaf row shows clear dark text ("index.js" / "180 KB", "webpack.js" / "140 KB") on the mint-tinted fills, matching the light render's presentation. All rows at all depths pass legibility in this render. + Background: warm near-black #1A1A17 (sampled 26,26,23). + Chrome: title, subtitle and legend text flip to light ink and are clearly readable; no dark-on-dark text. Gutters are now near-black. + Data: rectangle fills are identical to the light render (sampled root 110,106,97 and a dist/ leaf 119,200,174 in both); label ink inside rectangles is chosen by fill luminance and stays readable. Brand green is clearly visible. Legibility verdict: PASS criteria_checklist: visual_quality: @@ -68,78 +64,76 @@ review: items: - id: VQ-01 name: Text Legibility - score: 7 + score: 8 max: 8 passed: true - comment: Dark-mode leaf-label bug from Review 2 is fixed (verified via pixel - crop); all font sizes explicitly set per depth (22/17/15/13px); readable - in both themes with no overflow + comment: Title 22 px, legend and subtitle 14 px, labels 14–20 px by depth; + all readable in both themes, label ink picked per fill by luminance - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No text/element collisions in either render + comment: No text collisions; labels only where they fit their rectangle - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: All bands clearly visible with consistent gutters; labels correctly - hidden on boxes too small to hold them + comment: All 39 rectangles visible with thin gutters; legend swatches match + the branch fills - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Imprint palette is CVD-safe; contrast-aware text selection based - on fill luminance + comment: Green, lavender, blue and ochre branches stay distinguishable at + every tint; no red-green sole signal - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: 3200x1800 canvas confirmed, no gate failure, no overflow/clipping, - sensible margins + comment: 3200x1800, full-width layout with balanced header; root block as + one row is permitted by the spec - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Descriptive subtitle explains size encoding with units (KB); no traditional - axes needed for this chart type + comment: N/A for axes (hierarchy layout has none); title and subtitle descriptive + with units - id: VQ-07 name: Palette Compliance - score: 2 + score: 1 max: 2 passed: true - comment: 'First branch color #009E73 verified, canonical Imprint order for - remaining branches, data colors identical across themes, correct theme backgrounds' + comment: 'Branches in canonical Imprint order and identical across themes, + backgrounds correct; root uses custom hex #6E6A61 (defect line)' design_excellence: - score: 13 + score: 10 max: 20 items: - id: DE-01 name: Aesthetic Sophistication score: 5 max: 8 - passed: false - comment: Contrast-aware HSL tint ramp shows real effort and is now bug-free, - but overall look stays close to a well-configured default + passed: true + comment: Two-tier typography (bold sans names over monospace values) with + font size stepping down by depth, monospace subtitle; beyond the baseline - id: DE-02 name: Visual Refinement - score: 4 + score: 3 max: 6 - passed: false - comment: Clean gutters and stroke separators, generous margins, but fairly - generic beyond the tint ramp + passed: true + comment: Depth tints toward the paper tone and page-colored gutters with consistent + 8 px inset labels - id: DE-03 name: Data Storytelling - score: 4 + score: 2 max: 6 passed: false - comment: Depth rows and branch coloring communicate hierarchy clearly, but - there is no additional focal point or emphasis on the largest contributor + comment: Color by branch is what the Notes ask; no further focal point chosen spec_compliance: score: 15 max: 15 @@ -149,117 +143,122 @@ review: score: 5 max: 5 passed: true - comment: Correct horizontal icicle/partition layout, root at top, children - below + comment: Horizontal icicle, root at top, no unasked layers - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Root at top with children below, color by hierarchy/branch, labels - hidden on small nodes -- all spec notes satisfied + comment: 'All Notes and A-good-version bullets met: one row per level, widths + proportional, children within parents, consistent color by branch, labels + only where room' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Bottom-up aggregation, correct proportional widths within each parent - span + comment: Widths proportional to aggregated sizes; children inside parent extent - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title matches required format exactly; band labels substitute for - a legend appropriately + comment: Title in mandated format; legend entries match the branch names and + fills data_quality: - score: 14 + score: 15 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 5 + score: 6 max: 6 passed: true - comment: 41 nodes across 4 depth levels, full hierarchy shown with varied - branch sizes + comment: 4 levels, 39 nodes, uneven sibling sizes show proportional widths + well - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Plausible, neutral file-system dataset (repo directories and files) + comment: Plausible front-end repository with dist/src/tests/docs and realistic + file names and sizes - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Sensible KB values across the hierarchy, correctly summing bottom-up + comment: 'Checked sums: js 320, assets 280, fonts 250, css 100 → dist 950; + src 600; tests 350; docs 200; total 2,100 KB, all computed bottom-up' code_quality: score: 9 max: 10 items: - id: CQ-01 name: KISS Structure - score: 2 + score: 3 max: 3 passed: true - comment: Many helper functions, but justified by the custom SVG partition - layout MUI X has no native primitive for + comment: Default component plus one layer component required for the useDrawingArea + hook - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic, hard-coded tree data + comment: Hard-coded data, deterministic - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: Only ChartContainer + useDrawingArea imported, both used + comment: All three imports used - id: CQ-04 name: Code Elegance - score: 2 + score: 1 max: 2 passed: true - comment: No fake UI, appropriate complexity for a hand-rolled icicle mark + comment: Lines 205–214 hand-blend hex channels where MUI lighten() computes + it (defect line) - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Correct default export, sized from ANYPLOT_SIZE, skipAnimation set, - no clipping + comment: Default export, skipAnimation, sized to ANYPLOT_SIZE, v9 ChartsContainer + API library_mastery: - score: 6 + score: 5 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 3 max: 5 passed: true - comment: ChartContainer + useDrawingArea is exactly the documented custom-mark - composition primitive for a chart type MUI X community has no native component - for + comment: Community ChartsContainer with axes hidden via position none; mostly + hand-written SVG as the community surface has no icicle - id: LM-02 name: Distinctive Features score: 2 max: 5 passed: false - comment: No use of MUI X's own ChartsTooltip/ChartsLegend components; relies - on a plain SVG <title> for hover text + comment: Custom SVG layer composed into ChartsContainer via the useDrawingArea + hook; getLuminance from @mui/material for adaptive label ink verdict: APPROVED + model: claude-opus-5-5 + criteria_version: qc-899b86ffde.aqr-869a70087c.sg-c73b4641dd.lib-f7283eb626 + rendered_at: '2026-10-24T18:21:42Z' impl_tags: dependencies: [] techniques: - - custom-mark + - custom-legend + - hover-tooltips + - html-export patterns: - data-generation - - iteration-over-groups + dataprep: - hierarchical-aggregation - dataprep: [] styling: + - minimal-chrome - edge-highlighting - - alpha-blending