You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 13.26.DF Commit 2: N-Channel Framework implementation (Phase B)
PHASE_13_26_DF_v1_0_END candidate
Implements Algorithm A (Phase 13.26.DF v1.2 §3.1, §5.2): automatic
visual-channel assignment for N data channels (vector, group_by,
quantiles). Phase B activates the framework introduced as scaffolding
in Commit 1 (0df4c00).
Files changed (6 files):
M UTILS/dfextensions/dfdraw/channels.py (+~310 LOC, stub→impl)
M UTILS/dfextensions/dfdraw/drawer.py (+~85 LOC)
M UTILS/dfextensions/dfdraw/plots/profile.py (+~145 LOC)
M UTILS/dfextensions/dfdraw/tests/test_channel_assignment.py
(50 stubs → 50 real tests)
M UTILS/dfextensions/dfdraw/tests/test_quantiles_profile.py
(1 test renamed + docstring)
M UTILS/dfextensions/dfdraw/docs/STYLING_FRAMEWORK_DECISIONS.md
(AD-60 + Commit 2 audit trail)
Files NOT changed in Commit 2 (locked in Commit 1):
- style.py, tests/feature_taxonomy.py
Implementation highlights
=========================
1. channels.py: assign_channels() body — full Algorithm A resolution chain
(per-call kwarg > style.default > EXPLICIT_RULES > greedy fallback) plus
collision check and capacity check. build_factored_legend() implements
sum-not-product legend layout (AD-59).
2. drawer.py: _draw_vector() calls assign_channels() once at top, replacing
the hardcoded `vector_style = 'linestyle' if group_by else 'color'` logic.
Cycle constants (_LINESTYLE_CYCLE, _MARKER_CYCLE) replaced with style-key
lookups everywhere they were used. quantile_style added to
_PROFILE_FORWARDED_NAMES and to profile() signature.
3. plots/profile.py: nested-band detection (Option A, AD-57) +
channel-aware discrete rendering. New _render_quantile_nested_band()
function with max-3 bands, alpha-stacked outer-to-inner.
4. tests/test_channel_assignment.py: 50 real test bodies across 11 classes
per v1.2 §9. Class 10 production-pattern tests verify backward-compat
for makeSmoothMapsWithTPC.py kwarg surface.
5. tests/test_quantiles_profile.py: test_multi_pair_returns_discrete
renamed to test_multi_pair_returns_nested_band per AD-57; new
PolyCollection assertion verifies nested_band actually renders.
6. docs/STYLING_FRAMEWORK_DECISIONS.md: AD-60 documents the FIX2
visual-elements channel-aware preservation rationale. Behavior-change
record for nested_band auto-detection on symmetric 4+ entries.
Test profile
============
Architect MacOS env (verified 2026-05-06): 627 passed, 1 skipped, 0 failed.
Behavior change notice
======================
Symmetric quantile lists with >= 4 non-0.5 entries now auto-detect as
'nested_band' instead of 'discrete'. Per architect amendment chat
2026-05-05 ("We did not use quentiles yet. We do not need to be back
compatible. for quentiles") and v1.2 §8.3 greenfield. To restore old
behavior: pass quantile_mode='discrete' explicitly. Production usage
(line 5511 of makeSmoothMapsWithTPC.py: quantiles=[0.16, 0.84]) routes
to error_bars — unchanged.
Provenance
==========
Drafter (Commit 2): Claude49Coder
Proposal: v1.2 (Approved 2026-05-05)
Parent commit: 0df4c00 (PHASE_13_26_DF_v1_0_BEGIN)
Decisions: docs/STYLING_FRAMEWORK_DECISIONS.md AD-44..AD-60
- Files modified: `dfdraw/channels.py` (stub → ~310 LOC implementation), `dfdraw/drawer.py` (Algorithm A wiring + cycle constants → style-key lookups + `quantile_style` forwarding), `dfdraw/plots/profile.py` (nested-band detection + channel-aware discrete rendering + `_render_quantile_nested_band`), `dfdraw/tests/test_channel_assignment.py` (50 skip stubs → 50 real test bodies)
203
+
- Test count: 627 passed + 1 skipped + 0 failed (verified on architect MacOS env, 2026-05-06; +50 new tests vs Commit 1 baseline of 577)
204
+
- No regressions in any pre-existing test
205
+
- Pre-commit tag candidate: `PHASE_13_26_DF_v1_0_END`
206
+
207
+
#### AD-60: FIX2 visual elements — channel-aware preservation
208
+
209
+
**Decision:** Per v1.2 §11.3 directive ("Coder may preserve, redesign, or drop FIX2 elements"), Commit 2 **preserves** the FIX2 visual elements (on-line percentage annotations + linestyle cycle for discrete quantiles) as the **channel-aware default for `quantile_style='linestyle'`**, with two structural changes to align with the channel framework:
210
+
211
+
1.**Cycle source:** the FIX2 hardcoded local `_ls_cycle = ['--', '-.', ':', (0, (3, 1, 1, 1))]` at `profile.py:559` is replaced with `get_style_value("channels.cycles.linestyle", default)[1:]`. The `[1:]` slice preserves the FIX2 invariant that **solid linestyle remains reserved for the central line** (now controlled by the first entry of `channels.cycles.linestyle`). Default cycle is `['-', '--', '-.', ':']`, so the discrete-quantile cycle is `['--', '-.', ':']` — one entry shorter than FIX2's hardcoded 4-entry cycle (the dash-dot-dot pattern `(0, (3, 1, 1, 1))` is dropped). For 4+ symmetric quantiles users are now routed to `nested_band` mode (AD-57) anyway, so the missing 4th linestyle is rarely needed; users requiring it can `set_style({'channels.cycles.linestyle': ['-', '--', '-.', ':', (0, (3, 1, 1, 1))]})`.
212
+
213
+
2.**Annotation scope:** FIX2's on-line percentage annotations (`profile.py:564-577`) are preserved when `quantile_style='linestyle'` (the default), and **suppressed** when `quantile_style='marker'` or `quantile_style='color'`. Rationale: marker- and color-distinguished quantile lines need no on-line text to disambiguate; the legend handles it. Linestyle-distinguished lines benefit from on-line annotations because subtle linestyle differences are harder to read against a legend at a distance.
#### Behavior change recorded for transparency (per v1.2 §8.3 greenfield)
218
+
219
+
Symmetric quantile lists with **>= 4 non-0.5 entries** now auto-detect as `nested_band` mode (AD-57, Option A) instead of `discrete`. This is a deliberate change to the auto-detection rule and is per architect amendment chat 2026-05-05: *"We did not use quentiles yet. We do not need to be back compatible. for quentiles"*.
220
+
221
+
Affected examples:
222
+
-`[0.05, 0.25, 0.5, 0.75, 0.95]` (5 entries with central): was `discrete`, now `nested_band` (2 alpha-stacked filled regions + central line)
223
+
-`[0.05, 0.25, 0.75, 0.95]` (4 entries no central): was `discrete`, now `nested_band` (2 alpha-stacked filled regions)
224
+
-`[0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99]` (3 pairs + central): was `discrete`, now `nested_band` (3 filled regions + central line)
225
+
226
+
**To restore old behavior** on a per-call basis, pass `quantile_mode='discrete'` explicitly. The `stats_dict['quantiles_per_bin']` signature is preserved for both modes, so existing tests that only check the stats dict signature still pass.
227
+
228
+
**Existing test affected:**`tests/test_quantiles_profile.py::TestQuantileMode::test_multi_pair_returns_discrete` — used to test that `[0.05, 0.25, 0.5, 0.75, 0.95]` returns discrete mode. Renamed to `test_multi_pair_returns_nested_band` and docstring updated in Commit 2 to reflect new contract (assertion still passes either way because `quantiles_per_bin` is set for both modes; only the mode name and intent change).
229
+
199
230
---
200
231
201
232
*This document is authoritative for dfdraw architectural decisions. Modifications to AD entries require architect approval; appending new ADs follows the standard phase-decision workflow per `Organization-structure.md`. Governance principles (GP-N) are extracted from review-cycle lessons and bind future phases unless explicitly overridden by architect.*
@@ -205,3 +236,4 @@ Extracted from Phase 13.25.DF + Phase 13.26.DF review cycles. These should bind
0 commit comments