Figures: order-stable system colours and markers
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
10 changed files +42 −2
modified
paper/figures/FIGURE_PROVENANCE.md
+1 −0
@@ -2,3 +2,4 @@ | ||
| 2 | 2 | |
| 3 | 3 | - Smoke: generated from `artifacts/metrics/vs_smoke/report.json` (created 2026-11-09T17:33:43Z, git 43899c8924172a93eb7fdb44567c22c0c6bb1e47) and runs ['artifacts/runs/vs_smoke-m1-nano'] |
| 4 | 4 | - PilotVal: generated from `artifacts/metrics/pilot_val_exploratory/report.json` (created 2026-11-09T19:23:50Z, git c310982a0a448cfba546579ee1883679f6565058) and runs ['artifacts/runs/pilot_m1_tiny', 'artifacts/runs/pilot_m2_tiny', 'artifacts/runs/pilot_m1_tiny_denoiseinit'] |
| 5 | +- PilotVal: generated from `artifacts/metrics/pilot_val_exploratory/report.json` (created 2026-11-09T19:23:50Z, git c310982a0a448cfba546579ee1883679f6565058) and runs ['artifacts/runs/pilot_m1_tiny', 'artifacts/runs/pilot_m2_tiny', 'artifacts/runs/pilot_m1_tiny_denoiseinit'] | |
modified
paper/figures/fig_by_discipline_pilotval.pdf
+0 −0
Binary file not shown.
modified
paper/figures/fig_by_era_pilotval.pdf
+0 −0
Binary file not shown.
modified
paper/figures/fig_by_role_pilotval.pdf
+0 −0
Binary file not shown.
modified
paper/figures/fig_by_variant_pilotval.pdf
+0 −0
Binary file not shown.
modified
paper/figures/fig_overall_pilotval.pdf
+0 −0
Binary file not shown.
modified
paper/figures/fig_stylometry_pilotval.pdf
+0 −0
Binary file not shown.
modified
paper/figures/fig_training_pilotval.pdf
+0 −0
Binary file not shown.
modified
scripts/build_figures.py
+6 −1
@@ -86,7 +86,9 @@ def fig_stylometry(report: dict, out: Path) -> None: | ||
| 86 | 86 | m = means["rewrite"] if r == "rewrite" else sty[r]["feature_means"] |
| 87 | 87 | # standardised mean shift (in SDs of the originals; floor avoids blow-ups on near-constant features) |
| 88 | 88 | vals = [(m[f] - orig[f]) / max(sds.get(f, 1.0), 0.05 * (abs(orig[f]) + 1e-9), 1e-6) for f in FEATURES] |
| 89 | − ax.plot(range(len(FEATURES)), vals, marker="o", color=("#52514e" if r == "rewrite" else system_color(r)), label=("LLM rewrite" if r == "rewrite" else pretty(r)), linewidth=1.2) | |
| 89 | + from htr.evaluation.plotstyle import system_marker | |
| 90 | + | |
| 91 | + ax.plot(range(len(FEATURES)), vals, marker=("x" if r == "rewrite" else system_marker(r, systems)), color=("#52514e" if r == "rewrite" else system_color(r)), label=("LLM rewrite" if r == "rewrite" else pretty(r)), linewidth=1.2) | |
| 90 | 92 | ax.axhline(0, color="#c3c2b7", linewidth=0.8) |
| 91 | 93 | ax.set_xticks(range(len(FEATURES))) |
| 92 | 94 | ax.set_xticklabels([f.replace("_", " ") for f in FEATURES], rotation=60, ha="right", fontsize=7) |
@@ -162,6 +164,9 @@ def main() -> None: | ||
| 162 | 164 | a = ap.parse_args() |
| 163 | 165 | apply() |
| 164 | 166 | rep = json.loads(Path(a.report).read_text()) |
| 167 | + from htr.evaluation.plotstyle import assign_system_colors | |
| 168 | + | |
| 169 | + assign_system_colors(list(rep["systems"])) | |
| 165 | 170 | figs, tabs = ROOT / "paper/figures", ROOT / "paper/tables" |
| 166 | 171 | figs.mkdir(exist_ok=True) |
| 167 | 172 | tabs.mkdir(exist_ok=True) |
modified
src/htr/evaluation/plotstyle.py
+35 −1
@@ -16,13 +16,47 @@ SYSTEM_SLOT = {"B0_identity": 0, "B1_generic_paraphrase": 1, "B2": 3, "M1": 2, " | ||
| 16 | 16 | SEQUENTIAL_BLUE = ["#cde2fb", "#9ec5f4", "#6da7ec", "#3987e5", "#256abf", "#1c5cab", "#104281"] |
| 17 | 17 | |
| 18 | 18 | |
| 19 | +_ASSIGNED: dict[str, str] = {} | |
| 20 | +MARKERS = ["o", "s", "D", "^", "v", "P"] | |
| 21 | + | |
| 22 | + | |
| 23 | +def assign_system_colors(systems: list[str]) -> dict[str, str]: | |
| 24 | + """Fixed, order-stable assignment: baselines keep their slots (B0 blue, B1 orange, B2 yellow); other systems take | |
| 25 | + the remaining slots in the order given. Hues are never generated; beyond 6 systems the extra ones reuse a slot and | |
| 26 | + must be distinguished by marker (see MARKERS) and direct labels.""" | |
| 27 | + fixed = {"B0_identity": 0, "B1_generic_paraphrase": 1} | |
| 28 | + used = set() | |
| 29 | + out: dict[str, str] = {} | |
| 30 | + for s in systems: | |
| 31 | + if s in fixed: | |
| 32 | + out[s] = CATEGORICAL[fixed[s]] | |
| 33 | + used.add(fixed[s]) | |
| 34 | + elif s.startswith("B2"): | |
| 35 | + out[s] = CATEGORICAL[3] | |
| 36 | + used.add(3) | |
| 37 | + free = [i for i in range(len(CATEGORICAL)) if i not in used] | |
| 38 | + k = 0 | |
| 39 | + for s in systems: | |
| 40 | + if s not in out: | |
| 41 | + out[s] = CATEGORICAL[free[k % len(free)]] if free else INK2 | |
| 42 | + k += 1 | |
| 43 | + _ASSIGNED.update(out) | |
| 44 | + return out | |
| 45 | + | |
| 46 | + | |
| 19 | 47 | def system_color(name: str) -> str: |
| 48 | + if name in _ASSIGNED: | |
| 49 | + return _ASSIGNED[name] | |
| 20 | 50 | for key, slot in SYSTEM_SLOT.items(): |
| 21 | − if name == key or name.lower().startswith(key.lower()) or (key in ("M1", "M2", "M3") and f"-{key.lower()}" in name.lower()): | |
| 51 | + if name == key or name.lower().startswith(key.lower()): | |
| 22 | 52 | return CATEGORICAL[slot] |
| 23 | 53 | return INK2 |
| 24 | 54 | |
| 25 | 55 | |
| 56 | +def system_marker(name: str, systems: list[str]) -> str: | |
| 57 | + return MARKERS[systems.index(name) % len(MARKERS)] if name in systems else "o" | |
| 58 | + | |
| 59 | + | |
| 26 | 60 | def apply() -> None: |
| 27 | 61 | mpl.rcParams.update({ |
| 28 | 62 | "figure.facecolor": SURFACE, "axes.facecolor": SURFACE, "savefig.facecolor": SURFACE, |
| 29 | 63 | |