Migration is a semantic exercise, not a component-name translation. First
describe what the existing chart means and how users operate it. Then express
that behavior with data preparation, scales, marks, and host options.
Record:
- input rows and derived fields;
- sorting, grouping, bins, stacks, and normalization;
- x, y, color, size, and grouping semantics;
- domain, baseline, padding, curve, and missing-value policy;
- axes, labels, legends, annotations, and margins;
- tooltip grouping and formatting;
- pointer, keyboard, selection, zoom, and playback behavior;
- responsive breakpoints and first-render size;
- animation identity and interruption behavior;
- accessible name, summary, and table;
- current bundle and performance measurements.
Screenshots alone do not capture these decisions.
Keep proven application, server, SQL, or D3 transforms for the first migration.
Pass their output to marks directly. Rewriting analytical logic at the same
time makes it difficult to tell whether a visual difference is a renderer
regression or a changed calculation.
Move or simplify transforms only after parity is measured. The dependency
boundary is explained in Scales and D3.
Map each visible layer independently:
- line or area series;
- bars, rectangles, or cells;
- dots or hexagons;
- rules, links, ticks, arrows, or vectors;
- text and frame annotations;
- facet panels.
Then assign explicit scales and guides. Complex charts are usually several
ordinary marks sharing a coordinate system, not one specialized chart type.
See Marks and Layering and the
Example Gallery.
Use the same frozen data and dimensions on both implementations. Compare:
- prepared values and scale domains;
- representative geometry and baselines;
- bar bandwidth and alignment;
- line gaps and curve crossings;
- axis ticks, rotation, titles, and automatic margins;
- tooltip rows, colors, and formatted values;
- focus, selection, and keyboard paths;
- light and dark output;
- update, resize, and reorder state preservation;
- production bundle and render/update measurements.
Prefer numeric and behavioral assertions. Use screenshot diffs for the
remaining painted details.
A reliable order is:
- Render a static, fixed-size chart.
- Match scales, marks, and guides.
- Add responsive sizing and automatic margins.
- Match tooltip and keyboard focus.
- Match selection and controlled viewport state.
- Capture live values in framework-memoized definitions.
- Add animation.
- Measure production bundles and update performance.
- Remove the old renderer after the parity suite passes.
Keep a temporary renderer switch only as a migration verification tool with a
defined removal gate. It should not become permanent application architecture.
Do not preserve accidental internals:
- generated DOM structure;
- private renderer hooks;
- broad package imports;
- pixel constants compensating for clipped labels;
- unstable array-index keys;
- manual tooltips that duplicate the default focus model.
Preserve user-visible meaning and behavior. Replace implementation accidents
with the documented TanStack Charts boundary.
Before deleting the old path:
- all chart modes and empty states have parity coverage;
- accessibility and reduced motion pass;
- representative production data has been exercised;
- the new path meets explicit bundle and performance budgets;
- rollback is a version or commit, not two live renderers;
- newly discovered API friction is resolved or documented at the correct
layer.
See Testing and Debugging and
Bundle Size and Performance.