Create canonical chart profile mapping layer before Vega-Lite emission
Problem
Establish a dedicated mapping/profile boundary between the compiled Dataface chart contract and emitted Vega-Lite specs so renames, wraps, hidden fields, and logic-heavy divergences stop living ad hoc inside standard_renderer.
Context
- Background research:
ai_notes/refactors/DATAFACE_SPEC_PROFILE_AND_VEGA_LITE_STRATEGY.mdai_notes/research/DATAFACE_AS_A_DASHBOARD_SPEC_FROM_VEGA_LITE_OUTWARD.mdai_notes/refactors/CHART_DEFAULTS_AND_PRECEDENCE_AUDIT.mdai_notes/refactors/COMPILED_PRESENTATION_DEFAULTS_ARCHITECTURE.md- The long-term target is now clearer:
- Dataface should have a fully defined Pydantic language layer
- charts should start from a full Vega-Lite mirror surface
- Dataface should then layer its own profile over that surface:
- renames
- wraps/shorthand
- hidden or discouraged fields
- Dataface-owned defaults/divergences
- logic-heavy transforms where meaning is not a simple path rename
- That means the architecture needs a central boundary between:
- the compiled canonical Dataface chart contract
- the final emitted Vega-Lite spec
- Today that responsibility is still scattered across render-time code, especially:
dataface/core/render/chart/pipeline.pydataface/core/render/chart/presentation.pydataface/core/render/chart/standard_renderer.py- spec-building helpers that mix semantic shaping, default application, and Vega-Lite assembly
- This creates several long-term problems:
- renames such as
horizontal_axisvs Vega-Lite-native axis paths have no single mapping home - one-to-many or conditional transforms risk being hidden in renderer helpers
- compile/runtime boundaries stay blurry
- the final emitter is not mechanical because it also owns Dataface profile logic
- Desired end state:
- compile produces a stable canonical
CompiledChart - a dedicated profile-mapping layer converts canonical Dataface chart concepts into Vega-Lite-native concepts
standard_rendererbecomes a much thinner emitter over already-mapped chart state plus compiled defaults- Constraint:
- do not push data-dependent semantic enrichment into the emitter
- keep variable/query/layout/dashboard concepts outside the Vega-Lite chart grammar
- keep public state/input concepts unified under Dataface
variables, not Vega-Liteparams
Possible Solutions
- Keep evolving
standard_rendereras the de facto mapping layer. Fastest short term, but it guarantees more hidden divergence logic and makes future renames/profile changes difficult to audit. - Recommended: introduce an explicit chart profile-mapping layer between compiled Dataface charts and Vega-Lite emission. This creates one home for: - direct renames - path remaps - one-to-many expansions - conditional transforms - explicit disallow/hide decisions The renderer can then focus on mechanical emission.
- Skip the canonical mapping layer and map authored Dataface fields directly to Vega-Lite-native fields. Simpler initially, but it makes future divergence painful because the public API becomes tightly coupled to upstream naming.
- Build the full generated Vega-Lite Pydantic layer first and postpone mapping-boundary cleanup. Useful eventually, but it risks cementing today's mixed responsibilities before the architectural seam exists.
Plan
Selected: Option 2 — add a dedicated chart profile-mapping layer and slim standard_renderer into an emitter over compiled canonical chart state.
Files to modify:
1. dataface/core/render/chart/standard_renderer.py
2. dataface/core/render/chart/pipeline.py
3. dataface/core/render/chart/presentation.py
4. dataface/core/render/chart/spec_builders.py or a new dedicated profile module
5. dataface/core/compile/compiled_types.py or related canonical chart types as needed
6. tests covering compiled-chart -> profile-mapped-chart -> emitted Vega-Lite behavior
Implementation steps:
1. Inventory current Dataface-to-Vega-Lite mapping logic already embedded in renderer helpers.
2. Classify each case as:
- direct rename
- structural remap
- one-to-many expansion
- conditional transform
- default ownership issue that belongs earlier in compile/default layering
3. Introduce a dedicated profile-mapping module and explicit transform helpers.
4. Define the canonical internal contract the mapping layer consumes from CompiledChart.
5. Move Dataface profile logic out of standard_renderer into the new mapping layer.
6. Reduce standard_renderer to:
- consume mapped chart state
- apply already-compiled defaults
- emit Vega-Lite mechanically
7. Add regression tests that assert:
- authored Dataface field -> canonical compiled field
- canonical compiled field -> mapped Vega-Lite-native shape
- mapped shape -> final emitted spec
8. Document the rule that future Dataface/Vega-Lite divergences must be implemented in the profile-mapping layer, not ad hoc in renderer code.
Implementation Progress
- Created from architecture follow-up after the Dataface spec/backbone discussion.
- Current agreed stance for this task:
- treat the compiled canonical form as the stable internal contract
- make Dataface/Vega-Lite divergence explicit
- keep
standard_rendererfocused on emission rather than profile logic - [x] Inventoried Dataface→VL mapping logic in
standard_renderer.py - [x] Created
dataface/core/render/chart/profile.pyas the single home for all Dataface/VL divergence: CHART_TYPE_MAP— type alias renames (scatter→point, pie→arc, etc.)MappedChart— frozen dataclass holding VL-native mark, encoding, and layout hintsmap_to_vega_lite()— public API: ResolvedChart → MappedChart- Channel mapping:
_map_x_encoding,_map_y_encoding,_map_other_encoding - Structural transforms:
_apply_horizontal_bar_orientation,_apply_chart_sort,_apply_bar_axis_defaults - Layout measurement:
_measure_categorical_label_width - [x] Refactored
standard_renderer.pyto consumeMappedChartfor the single-metric path — no more inline profile logic - [x] Updated
build_layered_series_specto use profile channel mappers instead of the oldbuild_channel_encoding - [x] Added 18 unit tests in
tests/core/test_chart_profile.pycovering type mapping, channel encoding, orientation swap, sort, label width, format/zero propagation, and end-to-end regression - [x] Updated
DESIGN.mdfile layout table to documentprofile.py - [x] All 2882 core tests pass (0 failures, 31 skipped, 3 xfailed)
QA Exploration
- [x] N/A — pure internal refactor, no UI changes
Review Feedback
- [ ] Review cleared