Skip to content

feat(palette): variants v1 + grid token harmonization to 0.15#7692

Open
MarkusNeusinger wants to merge 6 commits into
mainfrom
feat/palette-variants-v1
Open

feat(palette): variants v1 + grid token harmonization to 0.15#7692
MarkusNeusinger wants to merge 6 commits into
mainfrom
feat/palette-variants-v1

Conversation

@MarkusNeusinger
Copy link
Copy Markdown
Owner

Summary

Two related changes bundled (separate commits for review):

1. Pipeline grid token harmonization (10 prompt files)

  • The style-guide had two contradicting opacity values for grid/rule: Theme-adaptive Chrome table at 0.10 vs Grid Guidelines section saying "15-25%, very subtle"
  • Harmonize to 0.15 (lower edge of the Grid Guidelines band) and propagate to every library prompt that referenced the old 0.10 token
  • Seaborn's hardcoded ax.yaxis.grid(…, alpha=0.2) already followed the verbal rule, leave as-is; its rcParams default bumped 0.10 → 0.15 for spec-alignment
  • Existing implementations under plots/*/implementations/ keep their baked 0.10 values until the next regen run (same staleness pattern as palette changes)

2. Palette variants v1 — new diagnostic under docs/reference/palette-variants-v1/

v0 (variants A–F) compared against Okabe-Ito; v1 measures everything against live D, the palette currently shipping in core/images.py.

Five-card lineup:

Key Strategy What it tests
D-baseline live anyplot palette the bar every candidate must clear
D1 tight-chroma C ∈ [24, 32], position-1 pinned to red band [15°, 35°] does a narrower paper-ink corridor read more cohesively? Yields crimson #AE3030 within corridor
D3 expand-8 live D's 7 hues + 1 freely picked 8th hue indigo #7981FD lands diametrically opposite tan #BA843E — both wheel gaps filled, no swap needed
T tetradic 4 anchors 90° apart, brand-green anchored does hard-coded 90° spacing beat max-min ΔE? (it doesn't: ΔE 10.94 vs D's 15.61)
W warm-pole D's max-min + warm-pole scoring bonus (55° ± 30°), #B71D27 pinned for charts dominated by warm categorical data

Key finding: live D's first-4 worst-CVD ΔE = 15.61 is at the Petroff "optimal" ceiling — every D-family micro-tweak lands at the same metric. D3 expand-8 is the only candidate that adds capability without a metric trade-off (strict superset of live D); adopting it in core/images.py would be a non-breaking append, separate PR.

New v1 generator (scripts/palette-variants-v1.py):

  • Extends v0's select_palette with forbidden_hue_bands + warm_bonus knobs
  • New strategy branches (d-tight-chroma, d-expand-8, tetradic, warm-pole) in _strategy_bands
  • New n_hues parameter on Variant (D3 uses 8)
  • New render_color_wheel — pre-rendered CAM02-UCS PNG disk (perceptually honest chroma fade from neutral grey at centre to L=60/C=40 at rim, no slice seams), with palette dots placed at their actual (C, H) coords. Chroma-corridor toggle, overlay-live-D toggle on detail pages, hero wheel with candidate-overlay buttons on index

Shared rendering helper (scripts/_palette_common.py):

  • Sample-chart gridlines bumped 0.06 → 0.15 (catch-up to the new style-guide token from commit 1)
  • PAGE_CSS --rule variable bumped 0.10 → 0.15 (same consistency)

v0 untouchedscripts/palette-variants.py and docs/reference/palette-variants/ preserved as historical record. Replaces what was previously parked on feature/color-optimization (old branch deleted).

Test plan

  • Browse docs/reference/palette-variants-v1/index.html: hero wheel renders, candidate-overlay buttons swap overlay dots correctly, Δ-vs-D coloring shows green/red against the baseline
  • Open D3-expand-8.html: 8 palette dots + chroma-corridor rings visible on the wheel, overlay-live-D toggle shows live D dots as outlined circles, theme toggle (◐ dark) still works
  • Open D1-tight-chroma.html: red #AE3030 sits clearly inside the corridor rings, every dot inside corridor
  • grep -nE "0\.10" prompts/library/*.md prompts/plot-generator.md prompts/default-style-guide.md returns no grid-context hits (only seaborn's panel_grid_minor or non-grid uses)
  • Sample-chart gridlines (line/bar/scatter panels in any variant page) are clearly readable but don't compete with the data lines

🤖 Generated with Claude Code

MarkusNeusinger and others added 2 commits May 24, 2026 00:20
The style-guide had two contradicting values for grid/rule opacity: the
Theme-adaptive Chrome token table at 0.10 vs the dedicated Grid Guidelines
section "opacity 15-25%, very subtle". Per-library prompts (matplotlib,
plotly, bokeh, makie, highcharts, altair, plotnine, seaborn rcParams) all
inherited the 0.10 token; seaborn's hardcoded ax.yaxis.grid call already
used 0.2 (following the verbal rule).

Harmonize the spec at 0.15 — the lower edge of the Grid Guidelines band —
so the token table and the verbal rule agree, and propagate to every
library prompt that referenced the 0.10 token. Generated plots stay subtle
but become clearly readable against the warm paper-ink surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… color wheel

Second round of palette exploration (#5817 follow-up). v0 (variants A-F)
compared against Okabe-Ito; v1 (this) measures everything against live D,
which has shipped as ANYPLOT_PALETTE since the v0 round.

New deliverable at docs/reference/palette-variants-v1/:

  - D-baseline.html   — live anyplot palette rendered with the same
                        template as candidates, so it sits in the lineup
                        as the bar to beat
  - D1 tight-chroma   — C ∈ [24, 32], position-1 pinned to red band
                        [15°, 35°] so the corridor still yields a true
                        crimson (#AE3030) rather than a rust pick
  - D3 expand-8       — live D's 7 hues + 1 freely-picked 8th hue
                        (indigo #7981FD), diametrically opposite tan;
                        fills both remaining wheel gaps without forcing
                        a swap
  - T  tetradic       — 4 hue anchors 90° apart starting at brand green,
                        3 mid-quadrant fillers
  - W  warm-pole      — D's max-min selection plus a warm-pole scoring
                        bonus centred at 55°, with #B71D27 pinned for
                        semantic-red availability
  - index.html        — hero color wheel with candidate-overlay toggles +
                        baseline-card layout flip + Δ-vs-D coloring
  - compare.html      — side-by-side card grid with D as the reference row

New generator scripts/palette-variants-v1.py extends the v0 algorithm with:
  - forbidden_hue_bands / warm_bonus knobs in select_palette
  - tetradic + d-* strategy branches in _strategy_bands
  - n_hues parameter on Variant (D3 uses 8)
  - render_color_wheel — pre-rendered CAM02-UCS PNG disk (perceptually
    honest chroma fade, no slice seams) with palette dots placed at their
    actual (C, H) coords, optional chroma-corridor toggle, overlay live-D
    comparison

scripts/_palette_common.py: bump v1 sample-chart gridlines from 0.06 to
0.15 (catch-up to the new style-guide token), bump PAGE_CSS --rule from
0.10 to 0.15 for the same consistency. v0 (palette-variants/) untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 22:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR (1) harmonizes the “grid/rule” opacity design token to 0.15 across generator prompts/style guide, and (2) adds a new v1 palette-variant diagnostic generator under scripts/ intended to produce docs/reference/palette-variants-v1/* HTML pages comparing candidates against the live variant D palette in core/images.py.

Changes:

  • Update grid/rule opacity token from 0.100.15 across the default style guide, plot generator prompt, and multiple per-library prompt snippets.
  • Add scripts/palette-variants-v1.py, a new palette-variants v1 generator (baseline = live D) including a CAM02-UCS color wheel visualization.
  • Align the palette diagnostics shared CSS/sample-chart grid rendering in scripts/_palette_common.py to the new 0.15 rule/grid token.

Reviewed changes

Copilot reviewed 12 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
scripts/palette-variants-v1.py New v1 palette diagnostic generator (baseline vs live D) with variant pages, index, compare page, and color wheel rendering.
scripts/_palette_common.py Updates diagnostic sample-chart gridline opacity and CSS --rule token to 0.15 for consistency.
prompts/default-style-guide.md Updates the canonical grid/rule token to 0.15 and keeps reference snippet consistent.
prompts/plot-generator.md Updates matplotlib-style grid alpha token to 0.15 in the generator guidance snippet.
prompts/library/seaborn.md Updates seaborn rcParams grid.alpha guidance token to 0.15.
prompts/library/plotnine.md Updates plotnine panel_grid_major alpha to 0.15.
prompts/library/plotly.md Updates Plotly GRID rgba token to 0.15.
prompts/library/matplotlib.md Updates matplotlib ax.grid alpha token to 0.15.
prompts/library/makie.md Updates Makie grid RGBA alpha to 0.15 in examples.
prompts/library/highcharts.md Updates Highcharts GRID rgba token to 0.15.
prompts/library/bokeh.md Updates Bokeh grid line alpha guidance to 0.15.
prompts/library/altair.md Updates Altair gridOpacity guidance token to 0.15.

Comment on lines +19 to +27
Five new candidates explore "refine vs. rethink":

D1 — d-tight-chroma (D's max-min but C ∈ [24, 32] — narrower paper-ink)
D2 — d-wide-spread (D's max-min with 60° pairwise hue spread target)
D3 — d-swap-tan (D's max-min but hue band [50°, 90°] banned at pos 6
— forces an alternative to the live tan #BA843E)
T — tetradic (4 anchors 90° apart, brand-green anchored, 3 fillers)
W — warm-pole (D's max-min plus a warm-hue scoring bonus 30°–80°)

Comment on lines +358 to +362
"""Pick 7 hues for a variant. Greedy max-min ΔE selection under all 4
CVD conditions, with per-position hue bands and the per-variant chroma
corridor as candidate masks. If no candidate matches the strictest band,
the band half-width is widened in 10° steps until something fits.

Comment on lines +367 to +373
v1 additions:
- ``forbidden_hue_bands``: a list of (center_deg, half_width_deg) bands
to EXCLUDE from every position globally. Used by D3 (d-swap-tan) to
ban the tan band [50°, 90°] so a different 7th hue gets picked.
- ``warm_bonus``: (center_deg, half_width_deg, weight) — a soft additive
score bonus for candidates whose hue is within (half-width) of the
center. Used by W (warm-pole) to bias picks toward 30°–80°.
Comment on lines +1138 to +1142
Variant(
"D1", "tight-chroma", "d-tight-chroma",
"d-tight-chroma",
"live D's max-min ΔE selection but with the paper-ink chroma corridor narrowed to C ∈ [24, 32] — predicts cleaner co-existence in dense charts at the cost of some headroom. live D's semantic red #B71D27 is pinned at position 1 so loss/error/bad can map to the expected colour rather than a tight-corridor brown",
),
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Callable
@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

MarkusNeusinger and others added 3 commits May 24, 2026 11:25
D1-8 mirrors D3's expand-8 approach for the tight-chroma corridor: D1's
7 picks leave a 75° purple→red back-gap, the 8th slot is greedy-picked
there for a matte rosé (#954477) that bridges purple and red while
staying inside C ∈ [24, 32].

Also introduces ``reorder_pure_cvd_greedy`` (opt-in via
USE_PURE_CVD_REORDER) for D1-8 — the original wheel-gap-first heuristic
in ``reorder_first_4`` was picking a 60°-valid but CVD-weak first-4
like {green, blue, tan, mauve} whenever the 8th hue opened new
gap-valid quadruples. Pure-CVD greedy keeps the worst-pair curve high:
n=4 lifts from 10.70 → 17.44 (now beats D3's 15.61 at the same n).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ortings

palette-variants-v2 narrows the field from v1's 5 candidates to the two
real contenders (vivid-8 = D3, muted-8 = D1-8) and adds tooling to
compare slot orderings side-by-side:

  - hero with both color wheels (chroma corridor always visible) and
    hue-sorted strips for direct hue-by-hue comparison between palettes
  - sticky TOC linking the 4 sortings: pure-CVD greedy, wheel-gap-first,
    hue-order (rainbow), every-other-hue (interleaved)
  - per-sorting scorecard with per-n winner strips for CVD + normal
    vision (green = vivid, blue = muted, grey = tie)
  - per-cell chart stack: light theme block + dark theme block, each
    with lines, bars (all 8), pie (first 4), stocks (first 4),
    edge-cluster overlap scatter (centre mix)
  - column-major pair grid so strips/tables align between palettes
    even when intros wrap to different line counts
  - per-n worst-pair ΔE table with normal-vision row + CVD-min row

Reuses v1 utilities via importlib (hyphen in filename).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 24, 2026 22:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 22 changed files in this pull request and generated 4 comments.

Comment on lines +82 to +90
_V1_SPEC = importlib.util.spec_from_file_location(
"palette_variants_v1", REPO_ROOT / "scripts" / "palette-variants-v1.py"
)
assert _V1_SPEC is not None and _V1_SPEC.loader is not None
v1 = importlib.util.module_from_spec(_V1_SPEC)
# Register before exec_module so @dataclass can resolve cls.__module__ during
# class construction (otherwise dataclasses.py raises NoneType.__dict__).
sys.modules["palette_variants_v1"] = v1
_V1_SPEC.loader.exec_module(v1)
Comment on lines +46 to +48
1. pure-CVD greedy max-min (slowest possible per-n ΔE degradation;
pos 0 fixed brand-green; pos 1 fixed at
muted-8's semantic red for stability)
Comment on lines +11 to +13
"""Palette variants v2 — head-to-head: vivid-8 (D3) vs muted-8 (D1-8).

v1 produced five candidate variants (D-baseline, D1, D1-8, D3, T, W). Two
Comment on lines +2077 to +2082
WARM_BONUS: dict[str, tuple[float, float, float]] = {
# W — additive bonus centred at 55° (warm orange-red), half-width 30°,
# weight 3.0 ΔE units at the centre. Strong enough to nudge selection
# toward warms without overriding the no-clash gap mask.
"warm-pole": (55.0, 30.0, 3.0),
}
- Documented unanimous expert verdict favoring muted-8 over vivid-8
- Highlighted recurring themes and critiques from five independent reviews
- Recommended next steps for palette optimization and semantic picking
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants