Fix the audit's five P0s: facet by= length, object-numeric columns, colormap stop order, category-axis type_, tick labels past 1e6
Fixes the five P0 (silent wrong output) findings from the 0.0.7 framework audit. Each was reproduced on origin/main before the change and has a regression test after it.
| # | Was | Now |
|---|---|---|
| 1 | facet_chart(by=['a','b'], data=df) with 3 rows: every panel drew all 3 rows | ValueError: facet_chart by= has 2 values but data has 3 rows … (chart- and mark-level data=) |
| 2 | scatter(x=[1, None, 3]), np.array([1,2,3], dtype=object), Decimals → categorical axis '1', '(missing)', '3' at 0,1,2 | numeric axis with NaN holes, same rule as the color channel (_object_array_is_real_numeric); strings/bytes/bools/mixed stay categorical |
| 3 | colormap=[(1,'red'),(0,'blue')] → 255 red texels + 1 blue (CSS-style clamp) | ValueError: colormap stop positions must be non-decreasing … |
| 4 | bar(x=['a','b']) + x_axis(type_='time') → kind: time, 1970 epoch ticks; type_='log' → category 0 off-axis | build-time ValueError: x axis is categorical (2 categories …) and cannot be a time axis (G3) |
| 5 | ticks 1e6…1.3e6 step 5e4 labelled 1.0e6, 1.1e6, 1.1e6, 1.2e6, 1.2e6, 1.3e6, 1.3e6; 1,250,000 → 1.2e6 — browser, SVG, PNG, PDF alike | mantissa digits follow the step: 1.00e6, 1.05e6, 1.10e6 …, 1.25e6; Python _exp_digits and TS expDigits are the same function, asserted equal by a node parity test |
Notes
- Mapping
data=keeps its documented pass-through for scalars and short config lists (existingtest_subset_data_masks_only_row_aligned_columns); the DataFrame case is the one that silently duplicated data. - Missing values in object columns become NaN through a shared
columns.object_missing_to_nan, which also handlespd.NA(NumPy'sastype(float)refuses it). - The CSS gradient-string colormap form is unchanged: CSS clamping is that grammar's rule. Only the
(position, color)sequence form now rejects decreasing positions. - SI/offset axis labelling (
1.25M) is the better long-term fix for #5 and is listed as an improvement in the audit; this change removes the duplicate-label defect without changing the label style.
Spec
spec/design/chart-grammar.md G3 and the facet section, spec/api/styling.md colormap stops, spec/design/renderer-architecture.md §6.2.
Tests
New: test_object_numeric_columns.py, test_axis_type_conflicts.py, test_tick_label_precision.py (Python + node parity + exported SVG); additions to test_facets.py and test_custom_ramps_and_palette.py. Full suite, ruff check/format, ty check (no new diagnostics vs main) run locally.
Summary by CodeRabbit
-
Bug Fixes
- Exponential tick labels retain enough precision to remain distinct on very large or small axes.
- Numeric object columns with missing values are handled as continuous data with NaN gaps.
- Invalid facet arrays now raise clear errors when lengths do not match the data.
- Reversed colormap stops and incompatible scale types on categorical axes now raise clear errors.
- Browser and static chart exports format tick labels consistently.
-
Documentation
- Clarified colormap stop ordering, scale compatibility, numeric value detection, and facet array length requirements.
Before / after (live client, headless Chrome, same chart and view)
Fix 5, a 50,000-step axis from 1,000,000 to 1,300,000. Left: v0.0.7 labels. Right: this branch.

The other four fixes replace silent wrong output with a ValueError (1, 3, 4) or change axis classification (2), so their evidence is the regression tests rather than a picture.