diff --git a/plots/polar-scatter/implementations/javascript/muix.tsx b/plots/polar-scatter/implementations/javascript/muix.tsx index fb523255cfa..c513759834b 100644 --- a/plots/polar-scatter/implementations/javascript/muix.tsx +++ b/plots/polar-scatter/implementations/javascript/muix.tsx @@ -1,201 +1,287 @@ // anyplot.ai // polar-scatter: Polar Scatter Plot -// Library: muix 7.29.1 | JavaScript 22.23.2 -// Quality: 85/100 | Created: 2026-09-05 +// Library: muix 9.16.0 | JavaScript 22.23.3 +// Quality: 87/100 | Updated: 2026-10-24 //# anyplot-orientation: square -import { ChartContainer } from "@mui/x-charts/ChartContainer"; + +import { ChartsDataProvider } from "@mui/x-charts/ChartsDataProvider"; +import { ChartsWrapper } from "@mui/x-charts/ChartsWrapper"; +import { ChartsSurface } from "@mui/x-charts/ChartsSurface"; +import { ChartsLegend } from "@mui/x-charts/ChartsLegend"; import { ScatterPlot } from "@mui/x-charts/ScatterChart"; -import { ChartsTooltip } from "@mui/x-charts/ChartsTooltip"; import { useXScale, useYScale } from "@mui/x-charts/hooks"; const t = window.ANYPLOT_TOKENS; -// --- Data (in-memory, deterministic LCG — the browser has no seeded RNG) ---- -let seed = 42; -function rand() { - seed = (seed * 1664525 + 1013904223) % 4294967296; - return seed / 4294967296; -} +// Constants +const TITLE = "polar-scatter · javascript · muix · anyplot.ai"; +const RING_COUNT = 4; +const SPOKE_STEP_DEG = 45; +const FULL_CIRCLE_DEG = 360; +const DEG_SYMBOL = "°"; +const FILL_OPACITY = 0.72; +const MARKER_SIZE = 8; +const TITLE_HEIGHT = 74; +const LEGEND_HEIGHT = 40; +const CHART_MARGIN = { top: 14, bottom: 14, left: 14, right: 14 }; +const LABEL_RADIUS_FACTOR = 1.065; +const DOMAIN_PAD_FACTOR = 1.11; -// Wind observations grouped by time of day. Coastal sites see a diurnal wind -// rotation (land breeze overnight/morning, sea breeze in the afternoon, with -// evening/night transitional flow), so each group's prevailing bearing is -// spaced ~90° apart around the full compass rather than clustered on one side. -const GROUPS = [ - { category: "Morning", bearing: 15, speed: 7, spread: 85, markerSize: 8 }, - { category: "Afternoon", bearing: 105, speed: 13, spread: 90, markerSize: 8 }, - { category: "Evening", bearing: 195, speed: 10, spread: 80, markerSize: 8 }, - { category: "Night", bearing: 285, speed: 4, spread: 75, markerSize: 6 }, -]; +// Columns: set these to your data's column names +const CATEGORY = "Time of Day"; // role: group +const BEARING = "Wind Bearing (°)"; // role: theta +const SPEED = "Wind Speed (m/s)"; // role: r + +// Data: replace this block with your own records const POINTS_PER_GROUP = 30; -const MAX_RADIUS = 20; // m/s -const FILL_OPACITY = 0.7; // keeps overlapping points distinguishable +const MIN_SPEED_FLOOR = 0.5; +const SPEED_CEIL_CAP = 20; +const GROUP_SEEDS = [ + { name: "Morning", meanBearing: 15, meanSpeed: 7, spread: 85 }, + { name: "Afternoon", meanBearing: 105, meanSpeed: 13, spread: 90 }, + { name: "Evening", meanBearing: 195, meanSpeed: 10, spread: 80 }, + { name: "Night", meanBearing: 285, meanSpeed: 4, spread: 75 }, +]; -function polarToXY(bearingDeg, radius) { - const rad = (bearingDeg * Math.PI) / 180; - return { x: radius * Math.sin(rad), y: radius * Math.cos(rad) }; +const data: Array> = []; +let lcgState = 42; +for (const cfg of GROUP_SEEDS) { + for (let idx = 0; idx < POINTS_PER_GROUP; idx += 1) { + lcgState = (lcgState * 1664525 + 1013904223) % 4294967296; + const u1 = lcgState / 4294967296; + lcgState = (lcgState * 1664525 + 1013904223) % 4294967296; + const u2 = lcgState / 4294967296; + const bearing = + (((cfg.meanBearing + (u1 - 0.5) * cfg.spread) % FULL_CIRCLE_DEG) + FULL_CIRCLE_DEG) % + FULL_CIRCLE_DEG; + const speed = Math.min( + SPEED_CEIL_CAP, + Math.max(MIN_SPEED_FLOOR, cfg.meanSpeed + (u2 - 0.5) * cfg.meanSpeed), + ); + data.push({ + [CATEGORY]: cfg.name, + [BEARING]: Math.round(bearing * 100) / 100, + [SPEED]: Math.round(speed * 100) / 100, + }); + } } -// `ScatterSeriesType` has no `fillOpacity` prop — the renderer paints markers -// with `fill: series.color` directly, so translucency is baked into the color. -function withAlpha(hex, alpha) { - const h = hex.replace("#", ""); - const r = parseInt(h.substring(0, 2), 16); - const g = parseInt(h.substring(2, 4), 16); - const b = parseInt(h.substring(4, 6), 16); - return `rgba(${r}, ${g}, ${b}, ${alpha})`; +// Plot: reads only data and the constants above +const categories: string[] = []; +for (const row of data) { + const cat = String(row[CATEGORY]); + if (!categories.includes(cat)) categories.push(cat); } -const series = GROUPS.map((group, i) => ({ - type: "scatter", - id: group.category, - label: group.category, - color: withAlpha(t.palette[i], FILL_OPACITY), - markerSize: group.markerSize, - data: Array.from({ length: POINTS_PER_GROUP }, (_, idx) => { - const bearing = (((group.bearing + (rand() - 0.5) * group.spread) % 360) + 360) % 360; - const radius = Math.min(MAX_RADIUS, Math.max(0.5, group.speed + (rand() - 0.5) * group.speed)); - const { x, y } = polarToXY(bearing, radius); - return { id: `${group.category}-${idx}`, x, y }; - }), -})); - -// --- Polar grid overlay — community `@mui/x-charts` has no native polar -// chart, so the radial/angular grid is drawn as an SVG layer that shares the -// same linear x/y scales as the scatter points (via useXScale/useYScale), -// keeping rings, spokes and the data perfectly aligned at any canvas size. -- -const RINGS = [5, 10, 15, 20]; -const SPOKE_DEGREES = [0, 45, 90, 135, 180, 225, 270, 315]; -const LABEL_RADIUS = MAX_RADIUS * 1.14; +const rawMaxSpeed = Math.max(...data.map((r) => Number(r[SPEED]))); +const rawStep = rawMaxSpeed / RING_COUNT; +const stepMag = 10 ** Math.floor(Math.log10(rawStep)); +const stepNorm = rawStep / stepMag; +const ringStep = (stepNorm <= 1 ? 1 : stepNorm <= 2 ? 2 : stepNorm <= 5 ? 5 : 10) * stepMag; +const ringDecimals = Math.max(0, -Math.floor(Math.log10(ringStep) + 1e-9)); +const maxRadius = ringStep * RING_COUNT; +const rings: number[] = []; +for (let k = 1; k <= RING_COUNT; k += 1) { + rings.push(Number((k * ringStep).toFixed(ringDecimals))); +} + +const spokeDegrees: number[] = []; +for (let deg = 0; deg < FULL_CIRCLE_DEG; deg += SPOKE_STEP_DEG) { + spokeDegrees.push(deg); +} + +// Choose ring-label bearing at the centre of the 45° sector with fewest points +const sectorCount = FULL_CIRCLE_DEG / SPOKE_STEP_DEG; +const sectorCounts = new Array(sectorCount).fill(0); +for (const row of data) { + const b = (((Number(row[BEARING]) % FULL_CIRCLE_DEG) + FULL_CIRCLE_DEG) % FULL_CIRCLE_DEG); + const sIdx = Math.min(sectorCount - 1, Math.floor(b / SPOKE_STEP_DEG)); + sectorCounts[sIdx] += 1; +} +let minSectorIdx = 0; +for (let s = 1; s < sectorCount; s += 1) { + if (sectorCounts[s] < sectorCounts[minSectorIdx]) { + minSectorIdx = s; + } +} +const tickSpokeDeg = minSectorIdx * SPOKE_STEP_DEG + SPOKE_STEP_DEG / 2; +const tickSpokeRad = (tickSpokeDeg * Math.PI) / 180; + +const speedUnitMatch = /\(([^)]+)\)/.exec(SPEED); +const speedUnit = speedUnitMatch ? speedUnitMatch[1] : ""; + +const labelRadius = maxRadius * LABEL_RADIUS_FACTOR; +const domainLimit = maxRadius * DOMAIN_PAD_FACTOR; + +const seriesGroups = categories.map((cat, i) => { + const catRows = data.filter((r) => String(r[CATEGORY]) === cat); + const points = catRows.map((row, idx) => { + const bearingDeg = Number(row[BEARING]); + const radiusVal = Number(row[SPEED]); + const rad = (bearingDeg * Math.PI) / 180; + return { + id: `${cat}-${idx}`, + x: radiusVal * Math.sin(rad), + y: radiusVal * Math.cos(rad), + }; + }); + + return { + type: "scatter" as const, + id: cat, + label: cat, + color: t.palette[i], + markerSize: MARKER_SIZE, + data: points, + }; +}); function PolarGrid() { const xScale = useXScale(); const yScale = useYScale(); - const cx = xScale(0); - const cy = yScale(0); + + const cx = Number(xScale(0)); + const cy = Number(yScale(0)); return ( - {RINGS.map((r) => ( + {rings.map((r) => ( ))} - {SPOKE_DEGREES.map((deg) => { - const { x, y } = polarToXY(deg, MAX_RADIUS); + {spokeDegrees.map((deg) => { + const rad = (deg * Math.PI) / 180; + const sx = maxRadius * Math.sin(rad); + const sy = maxRadius * Math.cos(rad); + const lx = labelRadius * Math.sin(rad); + const ly = labelRadius * Math.cos(rad); return ( - + + + + {`${deg}${DEG_SYMBOL}`} + + ); })} - {SPOKE_DEGREES.map((deg) => { - const { x, y } = polarToXY(deg, LABEL_RADIUS); + + ); +} + +// Drawn after so the ring labels stay readable above the markers +function RingLabels() { + const xScale = useXScale(); + const yScale = useYScale(); + + return ( + + {rings.map((r) => { + const tx = Number(xScale(r * Math.sin(tickSpokeRad))); + const ty = Number(yScale(r * Math.cos(tickSpokeRad))); + const rStr = r.toFixed(ringDecimals); + const tickText = speedUnit ? `${rStr} ${speedUnit}` : rStr; + const pillWidth = tickText.length * 8.2 + 12; return ( - - {`${deg}°`} - + + + + {tickText} + + ); })} - {RINGS.map((r) => ( - - {`${r} m/s`} - - ))} ); } -const TITLE = "polar-scatter · javascript · muix · anyplot.ai"; -const TITLE_HEIGHT = 70; -const LEGEND_HEIGHT = 50; -const MARGIN = { top: 40, bottom: 40, left: 40, right: 40 }; -const DOMAIN = MAX_RADIUS * 1.3; - -// --- Chart (default-exported component — the harness mounts it) ------------- export default function Chart() { const { width, height } = window.ANYPLOT_SIZE; const chartSize = height - TITLE_HEIGHT - LEGEND_HEIGHT; return ( -
+
- {TITLE} +
{TITLE}
+
+ {`Angle: ${BEARING}, clockwise from 0${DEG_SYMBOL} at top · Radius: ${SPEED}`} +
- - - - - -
- {GROUPS.map((group, i) => ( -
- - {group.category} -
- ))} -
+ + + + + + + + +
); } diff --git a/plots/polar-scatter/metadata/javascript/muix.yaml b/plots/polar-scatter/metadata/javascript/muix.yaml index d863376f97b..5a00be777f0 100644 --- a/plots/polar-scatter/metadata/javascript/muix.yaml +++ b/plots/polar-scatter/metadata/javascript/muix.yaml @@ -2,113 +2,112 @@ library: muix language: javascript specification_id: polar-scatter created: '2026-09-05T11:40:06Z' -updated: '2026-09-05T11:55:17Z' -generated_by: claude-sonnet -workflow_run: 33963668090 +updated: '2026-10-24T12:03:12Z' +generated_by: gemini-4-argon +adopted_from: 5b2bcb3e5e4dc5b3685a669c07cf3ab1b9918f69 (5b2bcb3e5e4dc5b3685a669c07cf3ab1b9918f69) +workflow_run: 40091679090 issue: 2023 -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/polar-scatter/javascript/muix/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/polar-scatter/javascript/muix/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/polar-scatter/javascript/muix/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/polar-scatter/javascript/muix/plot-dark.html -quality_score: 85 +quality_score: 87 review: strengths: - - Builds a genuine polar scatter on top of the community-only @mui/x-charts by drawing - a custom SVG grid (rings + spokes + labels) via useXScale/useYScale, so the overlay - stays perfectly aligned with the underlying linear scatter scale at any canvas - size — a clean way to work within the community-surface constraint instead of - reaching for Pro/Premium or another library. - - 'Deterministic, realistic wind-observation dataset (LCG-seeded) with a clear diurnal - wind-rotation narrative: Morning/Afternoon/Evening/Night bearings are spaced ~90° - apart, so the four categories visibly occupy distinct compass sectors.' - - 'Palette compliance is correct: first category (Morning) uses brand green, data - colors are identical between light and dark renders, and only chrome (background, - grid, text) flips per theme.' - - Marker size and alpha (0.7) are tuned per group to keep overlapping points distinguishable; - ChartsTooltip is wired for per-item detail in the interactive HTML output. - - 'Clean, idiomatic code: skipAnimation set, sizes from window.ANYPLOT_SIZE, no - unused imports, no fake interactivity.' + - 'Polar layout built on MUI X''s own composition API: a ScatterPlot inside ChartsSurface + with a custom SVG grid drawn from useXScale/useYScale, so native tooltips and + the HTML legend still work' + - Angular ticks every 45° in degrees with bold cardinals, 0° at top clockwise, and + a subtitle that states the convention and units + - Radius axis computed from the data to a nice 5 m/s step, starting at 0 and closing + past the 19 m/s maximum so every marker sits inside the rim + - Fill opacity 0.72 lets the dense Night cluster read darker while single outliers + stay visible + - Imprint palette in canonical order with identical data colors across themes; all + chrome uses theme tokens and reads cleanly in both renders + - 'Deterministic LCG data tells a clear diurnal story: the prevailing wind rotates + N → E → S → W through the day and calms at night' weaknesses: - - The '5 m/s' ring label sits directly against/behind a Morning-category marker - in both renders (visible at the inner ring along the 0° spoke) — move ring labels - to an unoccupied angular gap (e.g. between two spoke lines away from the densest - cluster) or give them a small background pill for contrast. - - The Night (amber) cluster near the center is dense enough that several markers - fully overlap even at alpha=0.7 — consider a slightly smaller markerSize for that - group or a touch more jitter/spread so individual observations stay distinguishable. - - Design excellence is solid but stops at functional refinement — no radial fade, - no emphasis/callout on one category, no secondary visual hierarchy beyond color - and position, which caps this a notch below top-tier polish. + - 'VQ-02 (both): the opaque page-colored pills behind the ''5 m/s'' and ''10 m/s'' + ring labels sit on top of the scatter layer and hide parts of two Morning markers + near bearing 337° (about half of the marker at ~5.5 m/s and an edge of the one + at ~10 m/s) → no marker covered by a ring label. Likely cause: + rendered after with fill={t.pageBg}, and the ''emptiest sector'' + bearing (337.5°) still holds Morning points; draw the labels below the markers + or drop the opaque pill.' + - 'Suggestion: placing the ring labels on a bearing chosen by distance to the nearest + marker rather than by sector count would keep them clear without a pill.' + - 'Suggestion: a slightly larger angular label size (16 px) would hold up better + in the 400 px thumbnail.' image_description: |- Light render (plot-light.png): - Background: Warm off-white, consistent with #FAF8F1, not pure white. - Chrome: Title "polar-scatter · javascript · muix · anyplot.ai" is dark ink, centered, fully legible. Radial ring labels (5/10/15/20 m/s) and angular spoke labels (0°, 45°, 90° ... 315°) are dark-gray and legible against the light background. Grid rings and spokes are thin, subtle gray lines — visible but not dominant. Bottom legend (Morning/Afternoon/Evening/Night) is clear with matching color swatches. - Data: Four color groups — Morning (brand green #009E73), Afternoon (light purple), Evening (indigo/blue), Night (amber) — each occupying a distinct ~90°-wide compass sector, consistent with a diurnal wind-rotation story. First categorical series correctly uses the brand green. - Legibility verdict: PASS, with one minor caveat — the "5 m/s" ring label sits directly against/behind a nearby Morning marker, a small text/marker overlap (not a legibility failure, all letters remain visible). + Background: warm off-white #FAF8F1 across the whole 2400x2400 canvas. + Chrome: bold dark title "polar-scatter · javascript · muix · anyplot.ai" at top, grey subtitle "Angle: Wind Bearing (°), clockwise from 0° at top · Radius: Wind Speed (m/s)". Four concentric rings (5/10/15/20 m/s, outer rim slightly heavier) and eight spokes in subtle grey; angular labels 0°–315° every 45° outside the rim, cardinals bold; ring labels on page-colored pills along the ~337° bearing. Legend at the bottom: Morning, Afternoon, Evening, Night. All text readable dark-on-light. + Data: 120 translucent circles — Morning green #009E73 clustered N–NE at 5–10 m/s, Afternoon purple spread E–SE at 8–19 m/s, Evening blue S–SW at 5–15 m/s, Night ochre tightly clustered W–NW at 2–6 m/s. All inside the 20 m/s rim. The 5 m/s and 10 m/s pills cover parts of two Morning markers. + Legibility verdict: PASS Dark render (plot-dark.png): - Background: Warm near-black, consistent with #1A1A17, not pure black. - Chrome: Title, ring labels, and spoke labels flip to light gray/white text, fully legible against the dark background — no dark-on-dark text anywhere. Grid lines remain subtle but visible. Legend text is light and readable. - Data: Same four colors as the light render, confirming data colors are identical between themes and only chrome flips. - Legibility verdict: PASS, same minor "5 m/s" label/marker overlap as in the light render — no dark-on-dark or light-on-light issues. + Background: warm near-black #1A1A17. + Chrome: title in light ink, subtitle, angular and ring labels in light grey; rings and spokes subtle dark grey, still visible; ring-label pills in the dark page color. Legend text light. No dark-on-dark text. + Data: same four hues as the light render (green first), same positions; translucency blends toward the dark page, so markers read slightly deeper but remain clearly distinguishable, brand green clearly visible. The same two Morning markers are partly covered by the ring-label pills. + Legibility verdict: PASS criteria_checklist: visual_quality: - score: 26 + score: 29 max: 30 items: - id: VQ-01 name: Text Legibility - score: 7 + score: 8 max: 8 passed: true - comment: All text readable in both themes at appropriate sizes; minor crowding - near the '5 m/s' label from a nearby marker + comment: Explicit font sizes (title 22, subtitle/legend 15, angular and ring + labels 14 CSS px); all text readable in both themes - id: VQ-02 name: No Overlap - score: 4 + score: 5 max: 6 passed: true - comment: '''5 m/s'' ring label overlaps a Morning marker in both renders; - some Night-cluster markers fully overlap each other' + comment: The opaque ring-label pills for 5 m/s and 10 m/s are drawn above + the markers and cover parts of two Morning markers (defect line) - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Marker size/alpha tuned per group for ~30-point clusters; dense Night - cluster still has some fully-hidden overlaps + comment: 120 markers at a prominent size with 0.72 fill opacity; legend dots + match the marks in both themes - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Imprint palette hues are distinguishable by hue, not relying on red-green - alone + comment: Green, purple, blue and ochre separate well; no red-green sole signal - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Square canvas gate passed, well-centered, generous padding, nothing - cropped + comment: 2400x2400 square, polar disc centered with balanced padding; title, + subtitle and legend fit cleanly - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Radial labels carry units (m/s), angular labels in degrees at 45° - intervals + comment: Subtitle names both axes with units and the angle convention; ring + labels carry m/s - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: First series is brand green, data colors identical across themes, - correct backgrounds in both themes + comment: 'Imprint palette in canonical order with first series #009E73; backgrounds + #FAF8F1 / #1A1A17; chrome theme-correct in both renders' design_excellence: - score: 12 + score: 11 max: 20 items: - id: DE-01 @@ -116,23 +115,24 @@ review: score: 5 max: 8 passed: true - comment: Custom polar overlay perfectly aligned to chart scale is a genuinely - sophisticated engineering solution; visual polish is functional rather than - showcase-level + comment: Bold cardinal angles vs regular intercardinals, a heavier outer rim, + and a quiet subtitle line explaining the angle convention form a typographic + hierarchy beyond the baseline - id: DE-02 name: Visual Refinement - score: 4 + score: 3 max: 6 passed: true - comment: Subtle grid, generous whitespace, integrated custom title/legend - bars + comment: Ring labels placed along the bearing of the emptiest sector on page-colored + pills; the placement still lands on markers, so only partial credit - id: DE-03 name: Data Storytelling score: 3 max: 6 passed: true - comment: Diurnal wind-rotation pattern (4 sectors ~90° apart) is visible at - a glance, though no additional emphasis/callout beyond color and position + comment: Data designed so the prevailing direction rotates clockwise through + the day (N morning, E afternoon, S evening, W night, calm at night) — a + readable diurnal story beyond the spec spec_compliance: score: 15 max: 15 @@ -142,28 +142,28 @@ review: score: 5 max: 5 passed: true - comment: True polar scatter behavior (theta/radius mapping) despite no native - MUI X polar primitive + comment: Polar scatter with color by the optional category column - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Radial gridlines, angular ticks at 45° intervals, category color - encoding, radius axis starts at 0 — all present + comment: Radians internally / degrees displayed, regular radial rings, angular + ticks every 45°, radius from 0 past the max, legend, translucent markers + — all Notes and 'A good version shows' bullets met - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: theta/radius correctly converted to x/y via polarToXY; full data - range shown within domain + comment: Markers at their computed (theta, r) positions, no jitter or nudging; + all points inside the 20 m/s rim - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title matches required format; legend labels match category names + comment: Title 'polar-scatter · javascript · muix · anyplot.ai'; legend Morning/Afternoon/Evening/Night data_quality: score: 15 max: 15 @@ -173,20 +173,21 @@ review: score: 6 max: 6 passed: true - comment: Angle, radius, and categorical color encoding all present, 120 points - across 4 groups + comment: Clusters around prevailing directions, a sparse WNW sector, outliers + near the rim, interleaving edges - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Plausible diurnal wind-direction/speed pattern, neutral subject matter + comment: Wind observations by time of day, neutral and plausible - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Wind speeds 0-20 m/s and bearings 0-360° are realistic ranges + comment: Speeds 0.5–20 m/s, 120 points (inside 100–150); no Check values in + the spec code_quality: score: 10 max: 10 @@ -196,32 +197,33 @@ review: score: 3 max: 3 passed: true - comment: No unnecessary classes; plain functions + comment: Data/plot zones at module level; PolarGrid and RingLabels are React + components required to call the useXScale/useYScale hooks - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fixed-seed LCG, fully deterministic + comment: Fixed-seed LCG (seed 42) - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports (ChartContainer, ScatterPlot, ChartsTooltip, useXScale, - useYScale) are used + comment: All imports used - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Appropriate complexity for the custom polar overlay; no fake UI/interactivity + comment: Nice-step ring computation and sector count are short and have no + available call equivalent - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: 'Follows harness contract: default export, window.ANYPLOT_SIZE, skipAnimation' + comment: Default-exported component, skipAnimation, v9 composition API library_mastery: score: 7 max: 10 @@ -231,22 +233,28 @@ review: score: 4 max: 5 passed: true - comment: Composes ChartContainer + ScatterPlot correctly and stays within - the community API surface + comment: 'v9 composition: ChartsDataProvider + ChartsWrapper + ChartsSurface + + ChartsLegend with ScatterPlot, styled via the .MuiScatterChart-marker + class' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: useXScale/useYScale hooks used to align a custom SVG overlay with - the chart's internal scale — a distinctive, non-obvious technique + comment: Custom SVG polar grid drawn from the chart's own useXScale/useYScale + hooks inside ChartsSurface, keeping native scatter tooltips and legend verdict: APPROVED + model: claude-opus-5-5 + criteria_version: qc-899b86ffde.aqr-869a70087c.sg-c73b4641dd.lib-f7283eb626 + rendered_at: '2026-10-24T12:00:20Z' impl_tags: dependencies: [] techniques: - polar-projection - - custom-legend + - layer-composition - manual-ticks + - hover-tooltips + - html-export patterns: - data-generation - iteration-over-groups @@ -254,3 +262,4 @@ impl_tags: styling: - alpha-blending - grid-styling + - minimal-chrome