feat(palette): adopt variant D as the anyplot palette#7617
Merged
Conversation
Adds scripts/palette-analysis.py and docs/reference/palette-analysis.html as the measurement anchor for the color-optimization work tracked in #5817. The diagnostic reads the canonical palette from core.images, computes pairwise ΔE in CAM02-UCS (Luo et al. 2006) under normal vision and three 100%-severity CVD conditions (Machado et al. 2009 via colorspacious), and renders: - sample line charts on the real bg-page surfaces (light + dark) - swatch table for the 7 Okabe-Ito hues + 2 adaptive neutrals - "first-n" cumulative worst-pair table (the practical question: at what palette size does the weakest pair drop below comfort) - paired ΔE matrices: normal vision | worst of 3 cvd - homepage hero mockup pair with live WCAG contrast badges - surface & chrome token analysis for both themes - 4-step CAM02-UCS scale citing Petroff (2021): <5 confusable, 5-10 marginal, 10-15 okay, ≥15 optimal (Petroff comfort target) Baseline finding: Okabe-Ito is optimal under normal vision at every palette size but green×blue drops to 11.7 under tritanopia from n=3 onwards. Five hue pairs land in the 10-15 "okay" zone in worst-CVD. This is the gap variant work will try to close in the next commit. Run: uv run --script scripts/palette-analysis.py Output: docs/reference/palette-analysis.html (self-contained, 92.7 kB) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generates six candidate replacement palettes inspired by Anselmoo's dracula-palette generator (https://anselmoo.github.io/dracula-palette/). All anchored at brand green #009E73 and selected by greedy max-min ΔE in CAM02-UCS under normal vision + 3 CVD conditions (deuteranopia, protanopia, tritanopia at 100% severity). Variants: A — analogous hues clustered within ±90° of brand B — triadic three hue anchors 120° apart C — split-complementary green + two flanking complements D — balanced tightest chroma corridor, no hue rule E — okabe-tuned within ±22° of each Okabe-Ito hue F — okabe-shifted hue-preserving projection onto the corridor E and F preserve Okabe-Ito's canonical position order (green/vermillion/blue/purple/orange/sky/yellow). A-D reorder positions 2-4 by max-min worst-CVD ΔE so the first 4 are the "most beautiful subset" (since 95% of plots use 2-4 series). Each variant gets its own self-contained HTML in docs/reference/palette-variants/ with the same sample-chart + first-N + ΔE matrix pair + hero mockup + continuous colormap pipeline as the baseline diagnostic, so direct comparison is easy. An index.html links all six with the first-4 worst-CVD ΔE prominently displayed. Paper-ink lever: the chroma corridor (CAM02-UCS C ∈ [22, 50]) plus a strict gamut filter (tol 0.001). Caligo sits at C ≈ 60-90, Okabe-Ito at C ≈ 40-75 — capping C is what keeps the picks away from neon. Per-variant chroma sub-ranges differentiate D (most muted, C ∈ [22, 36]) from E (okabe-honest, C ∈ [30, 50]). First-4 worst-CVD min ΔE achieved (target ≥ 15 per Petroff 2021): A: 16.34 B: 20.21 C: 22.73 D: 22.59 E: 15.99 F: 10.07* baseline (Okabe-Ito): 11.73 * F dips below baseline because the minimal-shift projection mutes several Okabe hues without picking a different position; it is the most conservative option, not the optimal one. Refactor: extracted ~1000 LOC of rendering helpers from scripts/palette-analysis.py into scripts/_palette_common.py so both scripts share the same pipeline. palette-analysis.py is now ~150 LOC of layout assembly + main. Run: uv run --script scripts/palette-variants.py Output: docs/reference/palette-variants/{index, A..F-*.html} Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a full-width baseline card at the top of the variants grid so the current Okabe-Ito palette is the first thing seen — the bar every variant tries to clear. The card carries the same hex-strip + first-4 worst-CVD ΔE + all-pairs normal ΔE layout as the variant cards, so direct comparison stays one glance away. Dashed border + "current" pill mark it as the reference rather than another candidate. Clicking opens the full baseline diagnostic at docs/reference/palette-analysis.html. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variants B (triadic) and C (split-complementary) were cycling through
3 hue anchors, which meant positions 1/4, 2/5, 3/6 all landed in the
same hue band — three purples in a row was the most obvious symptom.
Replace the anchor cycle with 7 distinct per-position hue targets so
each pick lives in its own hue region:
triadic : brand · +120° · +240° · +60° · +180° · +300° · +30°
(3 primaries + 3 midpoints + 1 warm fill)
split-comp : brand · +150° · +210° · +90° · +270° · +60° · +300°
(brand + 2 split anchors + 4 gap-fillers)
analogous : brand · ±30° · ±60° · ±90°
(spread across the ±90° band rather than clustering)
For the balanced strategy (no hue band by design) add a hue-diversity
penalty at score time: subtract 0.3 × max(0, 50° − min_hue_dist_to_any_selected)
from the ΔE score so successive picks stay ≥50° apart in hue. Without
this, greedy max-min lands on three nearly-identical purples again.
Also drop variant F (okabe-shifted). The minimal hue-preserving
projection was the most conservative option but scored 10.07 worst-CVD
— below the Okabe-Ito baseline of 11.73, since muting some hues
without picking a different position made the worst pair slightly
tighter rather than wider. Variant E already covers the "respect
Okabe-Ito" case while staying above the baseline.
Achieved first-4 worst-CVD min ΔE after restructure:
A: 15.03 B: 19.77 C: 17.99 D: 22.08 E: 15.99
(baseline Okabe-Ito = 11.73)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…5817) After re-reading Caligo's actual sources (OKLCH harmony modes + intent layers, syntax C ≈ 0.04-0.14 — not neon as previously assumed), three concrete upgrades to the variant generator: 1. **Hard min-hue-gap mask** in `select_palette`: every variant now enforces a minimum pairwise hue spacing on the colour wheel (60% of the strategy's diversity target). Per-position bands and the diversity penalty stay as soft guides, but the gap mask is never relaxed — even when the band fallback drops the hue rule, no two picks can land within the gap of each other. This fixes the previous run's 4 sub-30° clashes (triadic two-azures, split-comp two-blues, okabe-tuned two-blues + two-greens). 2. **New variant F — harmonic**: same max-min ΔE selection as balanced but with the paper-ink chroma corridor widened to C ∈ [22, 60]. Tests whether more chroma headroom yields more pleasing hue choices without crossing into Caligo-style territory. Continuous colormap green → blue → magenta. 3. **E renamed okabe-tuned → okabe-spread + harmony upgrade**: instead of just nudging Okabe-Ito's native hues into the paper-ink corridor, snap them onto an even 7-slot lattice anchored at brand green (~51° apart), then reorder for max first-4 worst-CVD ΔE. Removes the canonical-order preservation that locked the previous E into Okabe's blue/sky/purple cluster. Achieved scores (first-4 worst-CVD min ΔE, baseline Okabe-Ito 11.73): A analogous: 15.03 (+3.30) B triadic: 19.30 (+7.57) C split-comp: 15.53 (+3.81) D balanced: 21.89 (+10.16) E okabe-spread: 21.89 (+10.16) F harmonic: 19.77 (+8.04) All 6 variants now have minimum pairwise hue gap ≥ 30° except A-analogous (29.7°), which is the geometric limit for 7 picks inside a ±90° wedge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…5817) - Remove variant okabe-spread: with reorder_first_4 enabled it converged on the same hex set as balanced (both 21.89 first-4 worst-CVD ΔE), so the redundancy was not earning its slot. harmonic now occupies E. - Sample charts in both palette-variants.html and palette-analysis.html show the first 4 colours (was 3) — matching the "first-4 most beautiful" subset semantics of the variant reorder and the practical n=4 case for line charts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#5817) After picking D-balanced as the front-runner, two aesthetic concerns remained: pos 2 #9B4A00 read as brown (not yellow), and pos 3 #99B314 was a second green-family colour next to brand green. Two targeted fixes: 1. CandidatePool.build now drops "muddy" warm picks — sub-band specific: • red-orange (H ∈ [30, 65)) requires J' ≥ 52 • yellow-lime (H ∈ [65, 100]) requires J' ≥ 62 Caligo's OKLCH syntax defaults set Yellow at L 0.72 (≈ J' 70) for the same reason: yellows below that read as olive. The split-threshold spares deep reds (real reds at J' 45) while still killing #9B4A00. 2. reorder_first_4 now enforces a ≥60° pairwise hue gap among the four chosen positions (degrading 5° at a time if the 7-hue pool can't satisfy it). Stops green×lime and purple×magenta from co-occupying the top-4. Achieved first-4 worst-CVD min ΔE (baseline Okabe-Ito 11.73): A analogous 15.03 (top-4 hue gap 51° — analogous wedge bound) B triadic 15.61 (was 19.30, traded for hue uniqueness) C split-comp 15.53 D balanced 15.61 (was 21.89, top-4 now green/purple/red/azure) E harmonic 15.61 (was 19.77) All variants stay above Petroff's 15 ΔE comfort threshold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After tightening triadic/split-comp bands to 12°, B-triadic and D-balanced still converged on the same purple/red picks because both bands fell on the CVD-optimal hue. Three changes: 1. Tighten triadic/split-comp PRIMARY bands to 4° (filler positions stay at 12°). At H_STEP=5° this snaps to ±1 grid hue — triadic locks at ~290°/45° (true purple + amber-red) instead of drifting to 305°/25° where balanced lives. 2. reorder_first_4 now accepts a `pinned` parameter. Triadic and split-comp pin positions 1-2 (the strategy primaries) so the reorder only searches for the best 4th slot. Without pinning the reorder would silently move the strategy anchors out of the top-4. 3. New variant F-okabe-anchored: pos 0 = brand-green and pos 1 = vermillion (#D55E00) both pinned. Both are already paper-ink-compliant (J=58.8/59.3, C=25.1/33.5) so no chroma reduction needed. select_palette gained an `extra_seeds` parameter for this. Continuous colormap green → near-neutral → vermillion (diverging). Final top-4 hue layout per variant (pos 1, pos 2, pos 3): A analogous lime 115° blue 245° amber 55° B triadic purple 290° amber 45° azure 230° C split-comp magenta 315° red 20° lime 85° D balanced purple 305° red 25° azure 230° E harmonic purple 305° red 30° azure 230° (higher C) F okabe-anchored vermillion 51° purple 300° azure 230° Scores (first-4 worst-CVD min ΔE, baseline Okabe-Ito 11.73): A 15.03 B 15.61 C 17.84 D 15.61 E 15.61 F 15.61 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- B-triadic: one_liner now says "purple · amber-red" (was "magenta · azure") matching the tightened triadic anchors at H=290°/45°. - B continuous_label "green ↔ purple diverging" (was "green ↔ magenta"). - C-split-comp: clearer one_liner naming the 150°/210° flanks; cmap label "green ↔ red diverging" (was "vermillion"). - F-okabe-anchored: drop the pin on pos 1. Vermillion stays in top-4 because it satisfies the ≥60° hue-gap to brand-green and gives strong CVD distance, but reorder_first_4 may now push it to pos 5-7 if a future change favours a different 4-set. - HTML strategy summary shows the per-variant C corridor instead of the global [22, 50]. Per-variant ranges: A [24,40], B [26,42], C [26,42], D [22,36], E [22,60], F [22,42]. The ranges visibly differ between variants and explain why E feels more vivid than D. No palette hex values changed (run-output identical). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In B-triadic and C-split-comp, position 6 was hardcoded to brand+30° as
the "extra" filler beyond the 3-primaries-plus-3-midpoints layout. For
brand-green at H=166° this landed at H=196° (cyan), which:
- Sat only 29° from brand-green (visually same teal-cyan family)
- Clashed with the brand+60°=226° azure at pos 3 (only 35° apart)
- Had worst-CVD ΔE 3.48 against azure → "confusable" under tritanopia
Drop the hardcoded target; position 6 now uses None (no per-position
band), so the greedy max-min ΔE pass — already running under the ≥27°
hue-gap mask — fills the largest remaining hue gap. New picks:
B-triadic pos 6: #C18A10 (warm yellow, H=80°, between amber 45° and
lime 115° — the previously empty 70° gap)
worst-CVD ΔE 7.54 (vs 3.48 before)
C-split-comp pos 6: #22AFC8 (cyan-azure, H=215° — fills the cool gap)
worst-CVD ΔE 8.46 (was 5.40)
No changes to top-4 of either variant; only the trailing filler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variant pages and the baseline diagnostic now render each continuous colormap applied to MATLAB's peaks bivariate function — a 240×144 PNG inlined as a base64 data URI. The 1D gradient strips hide perceptual issues (banding, lightness inversions, hue swerves) that jump out on real 2D data. Implementation in render_cmap_demo (uses Pillow which was already in the dep stack). File-size impact per variant: +21 kB (peaks rendered once per cmap, PNG-optimised). palette-analysis.html grew 93→162 kB (three cmaps). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#5817) Four enhancements addressing the "what else can we look at" question: 1. **Continuous cmaps now derived from the palette.** Previously each variant had one hardcoded cmap (A: green→teal hardcoded, B/C: ended at palette[1]/palette[2], D/E: both endpoints hardcoded). Now every variant has TWO cmaps, both palette-derived: - **sequential**: brand-green → dark version of the palette member whose hue sits closest to 240° (the blue zone). J' 22 / C 35 at the terminus gives a monotonic lightness ramp regardless of where the source palette member sits in J/C. - **diverging**: warmest palette pick (hue closest to 30°) ↔ near- neutral mid ↔ coolest palette pick (closest to 240°). Both poles normalised to J' 45 / C 38 for symmetric weight. Labels are auto-generated from the matched palette members (e.g. "amber-red ↔ azure diverging"). 2. **Sample charts: lines + bars + scatter.** render_sample_charts now emits 3 chart types × 2 themes = 6 SVGs per variant, using the first-4 palette colours. Bars test categorical reading, scatter tests the palette in dense small marks. 3. **Cmap peaks demo on both surfaces.** render_cmap_demo wraps the PNG in a light-bg AND dark-bg frame side by side so the user sees the cmap against both production surfaces without toggling. 4. **compare.html** — one-page side-by-side of all 6 variants. Each card shows: header + score · all 9 swatches · sequential cmap strip + mini peaks · diverging cmap strip + mini peaks · link to the full variant page. Links in nav from index and variant pages. File-size impact: variant pages ~150 kB each (was 76 kB), compare.html ~250 kB, baseline diagnostic ~250 kB (was 163 kB). Higher byte cost buys decision-readiness in one view. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
palette-analysis.html had no link back to the variants — easy to land on, hard to escape. Added a variant-nav with links to the variants grid and the side-by-side compare page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every page (baseline diagnostic, grid index, compare, each variant) now shows the same top-level nav with: grid · compare · ★ baseline · A · B · C · D · E · F. Current page highlighted via .current class. Lets the user hop between baseline and any variant from any page without walking back through the grid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .variant-nav button styling was only inlined in render_variant_page, so the baseline diagnostic and the grid/compare pages rendered plain text links instead of the button row. Moved the rules into PAGE_CSS so every page that uses the shared stylesheet gets the same nav strip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename the categorical palette from Okabe-Ito to "anyplot palette" and
swap positions 2–7 to variant D ("balanced") from the palette exploration
in #5817. Position 1 (#009E73) stays — brand identity is preserved.
## Categorical palette (positions 2–7 changed)
| Pos | Before | After |
|-----|-----------------|-----------------|
| 1 | #009E73 (green) | #009E73 (green) |
| 2 | #D55E00 | #9418DB |
| 3 | #0072B2 | #B71D27 |
| 4 | #CC79A7 | #16B8F3 |
| 5 | #E69F00 | #99B314 |
| 6 | #56B4E9 | #D359A7 |
| 7 | #F0E442 | #BA843E |
Selection: Petroff-style max-min ΔE under the paper-ink corridor (J' ∈
[45,72], C ∈ [22,36] in CAM02-UCS). First-4 worst-CVD ΔE improves from
11.73 (Okabe-Ito) to 15.61 (variant D).
## Continuous colormaps — only two are now allowed
- anyplot_seq (single-polarity): #009E73 → #003D94
- anyplot_div (diverging): #BB0D22 ↔ #A2A598 ↔ #007AD9
All other cmaps are forbidden: viridis, cividis, BrBG, Reds, Blues,
Greens, jet, hsv, rainbow. Palette identity now carries through to
continuous data instead of breaking at the categorical/continuous edge.
## Semantic exception
The default rule "use positions 1→N in order" gets a deliberate softener:
when category labels carry strong real-world color cues (grass→green,
wood→tan, blood→red), the AI may reassign positions to palette members
that match. Colors must still come from the anyplot palette — no custom
hexes — and the semantic mapping must be obvious from the data labels.
## Scope
- core/images.py: OK_* → ANYPLOT_*, hex values updated, LIBRARY_COLORS
remapped by position. Dead legacy aliases (ANYPLOT_BLUE/_YELLOW) dropped.
- 18 prompt files updated: style guide, plot generator, all 10 library
prompts, all QA + workflow prompts, plus agentic/commands/regen.md.
- scripts/style-variants.yaml palette_tableau patches updated to find
new hexes.
- scripts/palette-{analysis,variants}.py: Okabe-Ito hexes inlined so the
baseline-comparison research scripts keep working post-rename.
- Tests + docstrings: cosmetic Okabe-Ito → anyplot palette renames.
- Merges feature/color-optimization (#5817) for the palette analysis
scripts and diagnostic HTML pages under docs/reference/palette-variants/.
## Intentionally NOT updated in this PR
- app/src/styles/tokens.css + app/src/components/{PaletteStrip,CodeShowcase}.tsx:
React app UI still uses Okabe-Ito for code highlighting and palette-strip
marketing. Test phase only changes generated plots; UI rebrand is a
separate decision after the user reviews the new plots.
- docs/reference/style-guide.md: marketing-site brand document, also held
back until UI rebrand decision.
- Existing plots/*/implementations/*: generated artifacts that will be
rewritten by daily-regen with the new palette.
## Test plan
- [x] core.images imports clean, ANYPLOT_PALETTE has 7 hex values
- [x] tests/unit/prompts/test_prompts.py — 87 passed
- [x] tests/unit/core/test_images.py + tests/unit/agentic/regen/ — 116 passed
- [ ] CI green
- [ ] daily-regen picks up new palette on next run
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Auto-format pass after the variant D palette adoption — ruff caught column / spacing drift around the new constant block. No behavior change.
User confirmed: "bad quality red, good quality green" is a valid case for reassigning anyplot palette positions away from canonical order. Generalise the exception in default-style-guide.md from real-world objects only (grass/wood/blood/sky) to also cover conventional status codes (bad/error/loss→red, good/ok/profit→green, neutral/warning→tan or pink). Constraints unchanged — match must come from the anyplot palette (no custom hexes), and the semantic mapping must be obvious from the data labels (legend literally says Pass/Fail, Profit/Loss, OK/Error, etc.). Abstract categories (groups A/B/C, anonymous bins) still default to canonical order.
User pushback: the prior wording read as a definitive enum of allowed
cases (real-world objects + status), but there are many more — stock
chart up/down bars, P&L deltas, sentiment polarity, traffic lights,
temperature hot/cold, etc. Reframe so:
- The examples are explicitly illustrative ("some examples (not
exhaustive)"), not a closed list.
- The test is "would a typical reader expect this category to look
like this color?", not membership in any enum.
- Constraints stay tight: must be from the anyplot palette, must be
obvious from labels, abstract categories still default to canonical.
Stock-chart bullish/bearish bars are now called out explicitly so
finance charts don't get penalised for breaking canonical order.
Contributor
There was a problem hiding this comment.
Pull request overview
Adopts palette variant D (“balanced”) as the new canonical anyplot categorical palette (preserving brand green #009E73) and updates the prompt/style-guide ecosystem so future implementation generation, repair, and review enforce the new categorical order and the new “only two allowed” continuous colormaps (anyplot_seq, anyplot_div).
Changes:
- Switch
core.imagescategorical constants and library accent mapping to the anyplot palette (variant D). - Update default style guide + per-library prompts + workflow prompts to reference the anyplot palette and new continuous colormap restrictions.
- Add palette analysis/variant-generation scripts and reference HTML pages to document/compare palette variants.
Reviewed changes
Copilot reviewed 28 out of 36 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/prompts/test_prompts.py | Updates prompt-color test wording to “anyplot” branding while keeping the #009E73 check. |
| tests/unit/agentic/regen/test_metadata.py | Updates fixture text to reference “anyplot palette” instead of “Okabe-Ito”. |
| scripts/style-variants.yaml | Updates the “Tableau sanity-check” patch mapping to target the new palette hexes. |
| scripts/palette-variants.py | Adds a generator for candidate palettes + continuous cmaps and renders HTML comparison pages. |
| scripts/palette-analysis.py | Adds a baseline diagnostic HTML generator (currently hardcoded to Okabe-Ito as baseline). |
| scripts/_palette_common.py | Adds shared color-math + HTML-rendering utilities used by the palette scripts. |
| prompts/workflow-prompts/impl-similarity-claude.md | Updates “mandated palette” language to anyplot palette. |
| prompts/workflow-prompts/impl-repair-claude.md | Updates palette/cmap compliance guidance to anyplot palette + anyplot cmaps only. |
| prompts/workflow-prompts/impl-generate-claude.md | Updates generation requirements for categorical order + continuous cmap rules. |
| prompts/workflow-prompts/ai-quality-review.md | Updates review instructions for palette/cmap compliance (VQ-07) to new rules. |
| prompts/quality-evaluator.md | Updates evaluation examples and VQ-07 phrasing to anyplot palette/cmaps. |
| prompts/quality-criteria.md | Updates VQ-07 definition/scoring to anyplot palette + anyplot continuous cmaps. |
| prompts/plot-generator.md | Updates base generator guidance/comments to anyplot palette terminology. |
| prompts/library/seaborn.md | Updates categorical palette snippet and replaces continuous cmap guidance with anyplot cmaps only. |
| prompts/library/pygal.md | Updates palette constants and revises continuous guidance to anyplot-stop interpolation. |
| prompts/library/plotnine.md | Updates categorical palette usage and provides anyplot-stop gradient examples for continuous. |
| prompts/library/plotly.md | Updates discrete palette and provides anyplot continuous scale stop lists. |
| prompts/library/matplotlib.md | Updates palette constants and provides anyplot LinearSegmentedColormap examples. |
| prompts/library/makie.md | Updates categorical palette constants and restricts continuous colormaps to anyplot stops only. |
| prompts/library/letsplot.md | Updates categorical/continuous scale guidance to anyplot palettes/stops. |
| prompts/library/highcharts.md | Updates series colors and restricts color_axis gradients to anyplot stops only. |
| prompts/library/ggplot2.md | Updates palette constants and replaces viridis/brewer guidance with anyplot-stop gradients. |
| prompts/library/bokeh.md | Updates palette and provides anyplot-stop interpolation examples for continuous mappers. |
| prompts/library/altair.md | Updates categorical scale range and provides anyplot-stop continuous scale examples. |
| prompts/default-style-guide.md | Renames Okabe-Ito → anyplot palette, updates canonical order, adds semantic exception + anyplot cmaps-only rule. |
| docs/reference/palette-variants/index.html | Adds/updates the rendered “grid” page for variant comparison. |
| core/images.py | Replaces Okabe-Ito constants with anyplot palette constants and updates library accent mapping. |
| agentic/commands/regen.md | Updates regen guidance to reference anyplot palette as the mandated data colors. |
Comment on lines
+13
to
+17
| Generates 5 candidate replacement palettes inspired by Anselmoo's | ||
| ``dracula-palette`` generator (https://anselmoo.github.io/dracula-palette/), | ||
| all anchored at the brand green ``#009E73`` and selected by max-min ΔE in | ||
| CAM02-UCS under normal vision + 3 CVD conditions (deuteranomaly, | ||
| protanomaly, tritanomaly, each at 100% severity). |
Comment on lines
+21
to
+26
| A — analogous (harmony over distinctness; hues clustered) | ||
| B — triadic (three hue anchors 120° apart) | ||
| C — split-complementary (green plus two flanking complements) | ||
| D — balanced (Petroff-style max-min, paper-ink chroma) | ||
| F — harmonic (balanced rule but relaxed C corridor) | ||
|
|
Comment on lines
+39
to
+40
| Output: ``docs/reference/palette-variants/{A..E}-<name>.html`` plus an | ||
| ``index.html`` linking the five. |
Comment on lines
+1044
to
+1063
| # Baseline card — current Okabe-Ito palette as the reference everyone | ||
| # compares against. Linked to the full diagnostic for drill-down. | ||
| baseline_first4 = measure_first_4(OKABE_PALETTE) | ||
| baseline_normal = measure_all_normal_min(OKABE_PALETTE) | ||
| baseline_chip_top = "".join( | ||
| f'<span class="big" style="background:{hx}" title="{hx}"></span>' | ||
| for hx in OKABE_PALETTE[:4] | ||
| ) | ||
| baseline_chip_tail = "".join( | ||
| f'<span class="small" style="background:{hx}" title="{hx}"></span>' | ||
| for hx in OKABE_PALETTE[4:] | ||
| ) | ||
| baseline_score_class = cell_class(baseline_first4) | ||
| baseline_card = f""" | ||
| <a class="variant-card baseline-card" href="../palette-analysis.html"> | ||
| <div class="card-head"> | ||
| <span class="key">★</span> | ||
| <h3>baseline — okabe-ito <em>(current)</em></h3> | ||
| </div> | ||
| <p class="one-liner">today's plot palette. every variant below tries to clear this bar — the bar is the green×blue tritanopia collapse at ΔE 11.73.</p> |
Comment on lines
+70
to
+72
| NEUTRAL_LIGHT = "#1A1A17" | ||
| NEUTRAL_DARK = "#F0EFE8" | ||
|
|
Comment on lines
+623
to
+629
| <section class="intro"> | ||
| <p>five candidate palettes inspired by Anselmoo's <code>dracula-palette</code> generator — | ||
| all anchored at brand green <code>#009E73</code>, generated by greedy max-min ΔE | ||
| selection in CAM02-UCS under normal vision + 3 CVD conditions (deuteranopia, protanopia, | ||
| tritanopia at 100% severity). all colours sit inside a paper-ink corridor | ||
| (J' ∈ [45, 72], C ∈ [22, 50]) — that's the lever that | ||
| keeps the palette away from caligo-style neon while staying perceptually uniform.</p> |
Comment on lines
+640
to
+642
| <h3>baseline — okabe-ito <em>(current)</em></h3> | ||
| </div> | ||
| <p class="one-liner">today's plot palette. every variant below tries to clear this bar — the bar is the green×blue tritanopia collapse at ΔE 11.73.</p> |
| <span class="key">F</span> | ||
| <h3>okabe-anchored</h3> | ||
| </div> | ||
| <p class="one-liner">Okabe-Ito's vermillion (#D55E00) seeded into the 7-hue pool alongside brand-green — both already paper-ink-compliant. reorder_first_4 then chooses top-4 freely; vermillion stays in if it earns the spot..</p> |
Comment on lines
+13
to
+19
| Generates a single self-contained HTML page that visualises the current anyplot | ||
| palette across three domains, each evaluated for normal vision and three | ||
| 100%-severity CVD simulations (deuteranopia, protanopia, tritanopia): | ||
|
|
||
| A. Categorical plot palette (Okabe-Ito 7 + 2 adaptive neutrals) | ||
| B. Continuous plot colormaps (viridis, cividis, BrBG) | ||
| C. Website surface & chrome tokens — including homepage hero mockup |
8 of 9 Copilot suggestions on #7617 applied — all concern docstring / diagnostic-page text that drifted out of sync as variant F was added or as variant D was promoted from candidate to active palette: - scripts/palette-variants.py: docstring said "5 variants" / "F=harmonic" / "{A..E}"; code defines 6 (A-F) with E=harmonic, F=okabe-anchored. Fixed count, alphabetical mapping, and output path. - scripts/palette-variants.py: index-page baseline card said okabe-ito is "(current)" / "today's plot palette" — now "(legacy)" with a pointer that variant D has been adopted as the active palette. - scripts/palette-analysis.py: docstring claimed the script visualises the "current anyplot palette", but the implementation is hardcoded to Okabe-Ito as the baseline reference. Reworded to say so plainly and point to D-balanced.html for the active palette. - docs/reference/palette-variants/index.html: same three updates as above ("five candidate palettes" → "six", baseline card "(current)" → "(legacy)", spotted "spot.." double-period typo). Skipped (1 of 9): scripts/_palette_common.py NEUTRAL_LIGHT/NEUTRAL_DARK disagree with the style-guide "adaptive neutral" position-8 hex. The analysis scripts use the theme ink (#1A1A17 / #F0EFE8) but the style guide specifies a slightly different adaptive neutral (#1A1A1A / #E8E8E0). This is a pre-existing inconsistency in the research branch, affects only the position-8 swatch in the diagnostic HTML (categorical positions 1–7 are unaffected), and aligning would require regenerating all the variant HTML pages. Defer to a follow-up if variant D is confirmed adopted.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced May 23, 2026
MarkusNeusinger
added a commit
that referenced
this pull request
May 26, 2026
Hard switch from variant-D (PR #7617) to imprint — the v3 hybrid-v3 muted-8 palette designed in feat/palette-variants-v1. Full rationale: docs/reference/palette-variants-v3/decision-rationale.md. Changes - NEW core/palette.py: single source of truth for the 8 categorical hues, 3 semantic anchors (amber + 2 theme-adaptive neutrals), named API (palette.green, palette.semantic.bad, ...), sequential cmap (imprint_seq: green→blue), and diverging cmap (imprint_div_light / imprint_div_dark: red↔neutral↔blue with theme-adaptive midpoint). - core/images.py: drop the 7 variant-D hex constants and ANYPLOT_PALETTE list; re-import from core.palette. ANYPLOT_GREEN keeps its hex (#009E73); ANYPLOT_RED changes from #B71D27 to #AE3030; PURPLE/SKY/ PINK/TAN are gone (replaced by LAVENDER/BLUE/CYAN/OCHRE — exposed with ANYPLOT_* prefix for symmetry). - LIBRARY_COLORS: rebuilt with library-brand-match strategy (matplotlib keeps red, plotly stays blue, bokeh stays purple, seaborn picks rose for its warm statistical mood, highcharts picks cyan to stay distinct from plotly's blue). - matplotlib cmap registration runs at core.images import time. Not touched (intentional, for follow-up PRs) - 7 plot implementations in plots/**/python/*.py hardcode the old variant-D hex list locally — they keep working but show the old palette until regenerated via /regen. - Frontend app/src/pages/PalettePage.tsx and PaletteStrip.tsx still show Okabe-Ito — separate frontend PR. - Prompt system (prompts/default-style-guide.md, prompts/library/*.md, prompts/plot-generator.md) still references the old palette — Phase 2 of the rollout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9 tasks
MarkusNeusinger
added a commit
that referenced
this pull request
May 26, 2026
…frontend rebuild (#7692) ## Summary Replaces the previous **variant D** palette (PR #7617) with **imprint** — anyplot's new 8-hue colourblind-safe categorical palette + 3 semantic anchors (amber for warning, theme-adaptive neutral and muted). Tuned for warm-paper rendering, validated against deuteranopia / protanopia / tritanopia, sits in the academic-publishing family next to Okabe-Ito, Paul Tol "muted", and ColorBrewer Set2. Full design rationale: [`docs/reference/palette-variants-v3/decision-rationale.md`](https://github.com/MarkusNeusinger/anyplot/blob/feat/palette-variants-v1/docs/reference/palette-variants-v3/decision-rationale.md). Earlier variants (v1, v2, expert reviews) preserved under `docs/reference/palette-variants-*/` for design archaeology. ## What's in this PR **Phase 1 — Backend foundation (`core/`)** - NEW `core/palette.py` — single source of truth: 8 categorical hexes in hybrid-v3 sort order, 3 semantic anchors, named API (`palette.green`, `palette.semantic.bad`, …), sequential cmap `imprint_seq` (green→blue) + theme-adaptive diverging cmap `imprint_div_{light,dark}` (red↔midpoint↔blue), matplotlib registration. - `core/images.py` refactored — drops old 7 ANYPLOT_* constants, re-imports from `core/palette.py`. `LIBRARY_COLORS` rebuilt with library-brand-match strategy. **Phase 2 — Prompt system (18 files)** - `prompts/default-style-guide.md` — full Color Philosophy rewrite with 8-hex hybrid-v3 table, new Semantic anchors section, series-count guidance with concrete ΔE_CVD numbers, optional outline pattern. - 11 library prompts (matplotlib / seaborn / plotly / bokeh / altair / plotnine / pygal / highcharts / ggplot2 / letsplot / makie) — `ANYPLOT_PALETTE` lists updated to 8 hexes + `ANYPLOT_AMBER`, cmaps renamed with new endpoints. - Quality + workflow prompts — VQ-07 scoring extended to positions 1-8 + 3 anchors, legacy variant-D hexes listed as auto-reject signals. **Phase 3 — Style guide doc** - `docs/reference/style-guide.md` Section 4.1 rewritten as "The imprint Palette" with hue table, semantic-anchors table, hybrid-v3 sort rationale. Status colours (§4.5) and plot-only colours (§4.6) remapped onto imprint. CSS variables + Python implementation reference updated. **Phase 5a — Frontend palette page (`/palette`)** - `app/src/pages/PalettePage.tsx` completely rebuilt across 5 iterations: real chroma wheel (CSS conic-gradient OKLCH + chroma fade) with hover tooltips, compare-with toggle (Okabe-Ito / Tol muted / ColorBrewer Set2 / anyplot previous), compact 4×2 slot grid with **click-to-copy hex**, sort toggle (imprint default ↔ CVD-optimal max-min) with inline trade-off info, semantic anchors cards, continuous cmaps gradient strips, copy snippets in 4 languages (Python / R / Julia / JS) with OKLCH toggle, collapsible WCAG audit + palette history. - `theme/index.ts` — `colors.okabe` → `colors.imprint`, semantic colours remapped. - `styles/tokens.css` — `--ok-*` → `--imprint-*`, code-syntax theme rebuilt for both light and dark. - `components/PaletteStrip.tsx` — 8-hex imprint set, optional `hexes` prop. **Phase 5b — Frontend consumers** - `LandingPage.tsx` — `CLUSTER_PALETTE` + 7-Okabe swatch row swapped (now click-to-copy), `palette_okabe_ito` analytics event gone. - `MapPage.tsx` — `CLUSTER_COLORS` rebuilt with 8 imprint hexes. - `AboutPage.tsx`, `ScienceNote.tsx` — palette prose reframed around imprint. - `CodeShowcase.tsx`, `CodeHighlighter.tsx` — syntax hexes updated, `okabeItoTheme` → `imprintTheme`. - `LandingPage.test.tsx` — drops the gone Okabe-Ito tracking-event test. **Final audit sweep** - `docs/reference/plausible.md` (gone event row), `.serena/memories/style_guide.md` (Color section), `scripts/style-variants.yaml` (palette_tableau map), `scripts/palette-analysis.py` + `docs/reference/anyplot-landing-mockup.html` (HISTORICAL markers). Historical / comparison references (Okabe-Ito as family-neighbour, variant-D hexes as auto-reject signals in VQ-07, palette-variants exploration scripts) preserved intact. ## Intentionally NOT in this PR (Phase 4) 7 plot implementations under `plots/**/implementations/python/*.py` still hardcode the old variant-D palette inline. Per the design discussion these stay untouched and will pick up imprint via the next `/regen` cycle, when the new prompts kick in. ## Validation - `yarn tsc --noEmit` — clean - `yarn lint` — 0 errors (2 pre-existing warnings, neither palette-related) - `yarn test --run` — **507 / 507 passing** - Backend smoke-test confirms named-API + cmap registration - Visual smoke tests via chrome-devtools on `/`, `/palette`, `/map`, `/about` ## Test plan - [ ] Verify `/palette` page renders the chroma wheel, palette grid, semantic anchors, cmap previews, collapsibles - [ ] Try the compare-with toggle (Okabe-Ito / Tol muted / Set2 / anyplot previous) — overlay rings appear on the wheel - [ ] Try the sort toggle (imprint default ↔ CVD-optimal) — grid + strip + copy snippet all reorder - [ ] Click a swatch in the grid — copies hex to clipboard, swatch flips to "copied ✓" - [ ] Switch OKLCH toggle in the copy section — all 4 language snippets switch notation - [ ] Expand the WCAG audit and palette history collapsibles - [ ] Verify LandingPage palette section + map cluster preview render with imprint colours - [ ] Sanity-check dark mode on `/` and `/palette` - [ ] Watch Cloud Build: OG image regeneration should pick up the new LIBRARY_COLORS 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rename the categorical palette from Okabe-Ito to anyplot palette and swap positions 2–7 to variant D ("balanced") from #5817. Brand position 1 (
#009E73) is preserved.This is the test-phase adoption — daily-regen will start producing plots in the new palette so the user can review whether to keep it.
Categorical palette (positions 2–7 changed)
#009E73#009E73#D55E00#9418DB#0072B2#B71D27#CC79A7#16B8F3#E69F00#99B314#56B4E9#D359A7#F0E442#BA843ESelection: Petroff-style max-min ΔE in CAM02-UCS, paper-ink corridor
J' ∈ [45,72],C ∈ [22,36]. First-4 worst-CVD ΔE: 11.73 (Okabe-Ito) → 15.61 (variant D). Seedocs/reference/palette-variants/D-balanced.htmlfor the derivation, CVD analysis, and per-condition swatch tables.Continuous cmaps — only two allowed
anyplot_seq(single-polarity):#009E73 → #003D94anyplot_div(diverging):#BB0D22 ↔ #A2A598 ↔ #007AD9All other cmaps are forbidden: viridis, cividis, BrBG, Reds, Blues, Greens, jet, hsv, rainbow. Palette identity now carries through to continuous data.
Semantic exception
When categories carry strong real-world color cues (grass→green, wood→tan, blood→red), the AI may reassign positions to palette members that match. Colors must still come from the anyplot palette; mapping must be obvious from the labels. Default to canonical order for abstract categories.
What's NOT in this PR
tokens.css,PaletteStrip.tsx,CodeShowcase.tsx) still uses Okabe-Ito — UI rebrand is a separate decision after the user reviews the new plots.docs/reference/style-guide.md(marketing-site brand doc) — held back for the same reason.plots/*/implementations/*files will be overwritten by daily-regen.Test plan
core.imagesimports clean,ANYPLOT_PALETTEhas 7 hex valuestests/unit/prompts/test_prompts.py— 87 passedtests/unit/core/test_images.py+tests/unit/agentic/regen/— 116 passed🤖 Generated with Claude Code