Fix the audit's five P0s: facet by= length, object-numeric columns, colormap stop order, category-axis type_, tick labels past 1e6

AL
4d ago
+584-15

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.

#WasNow
1facet_chart(by=['a','b'], data=df) with 3 rows: every panel drew all 3 rowsValueError: facet_chart by= has 2 values but data has 3 rows … (chart- and mark-level data=)
2scatter(x=[1, None, 3]), np.array([1,2,3], dtype=object), Decimals → categorical axis '1', '(missing)', '3' at 0,1,2numeric axis with NaN holes, same rule as the color channel (_object_array_is_real_numeric); strings/bytes/bools/mixed stay categorical
3colormap=[(1,'red'),(0,'blue')] → 255 red texels + 1 blue (CSS-style clamp)ValueError: colormap stop positions must be non-decreasing …
4bar(x=['a','b']) + x_axis(type_='time') → kind: time, 1970 epoch ticks; type_='log' → category 0 off-axisbuild-time ValueError: x axis is categorical (2 categories …) and cannot be a time axis (G3)
5ticks 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 alikemantissa 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 (existing test_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 handles pd.NA (NumPy's astype(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.

tick labels before and after

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.