Skip to content

d3 scale instances fail the ConfiguredScaleLike type despite being the documented pattern #34

Description

@dschoent

Version: @tanstack/charts 0.4.0, d3-scale 4.0.2, TypeScript 5.x/6.x

Repro

The scales reference documents passing a configured instance for fixed domains ("Pass a scale instance when the domain is fixed application state: scale: scaleLinear().domain([0, 100])"). With some mark unions this fails to typecheck:

const definition = defineChart({
  marks, // e.g. lineY marks whose x is Date | string, plus custom marks
  x: { scale: scaleTime().domain([start, end]) }, // TS2769
  y: { scale: scaleLinear },
});
Type 'ScaleTime<number, number, never>' is not assignable to type
'ChartScale | ChartScaleInput<NonNullable<string | Date>>'.
  Type 'ScaleTime<number, number, never>' is not assignable to type
  'ConfiguredScaleLike<NonNullable<string | Date>>'.
    The types returned by 'range(...)' are incompatible between these types.
      Type 'number[]' is missing the following properties from type
      'ConfiguredScaleLike<NonNullable<string | Date>>': copy, domain, range

Two structural mismatches:

  1. d3's range() is overloaded (range(): Range[] / range(range): this); when TS checks assignability against the single signature range: (values: Iterable<number>) => ConfiguredScaleLike<TValue>, it can select the zero-arg overload and fail on the return type.
  2. When the marks union widens the axis TValue to include string, ScaleTime's callable (value: Date | number) => number no longer covers the value union even though runtime usage is fine.

Runtime works in all these cases (the resolver detects instances via .copy and copies them), so this is types-only; consumers end up writing as unknown as ConfiguredScaleLike<...>.

Expected

ChartScaleInput accepts real d3 scale instances without casts — e.g. loosening range to an overload-tolerant shape and/or making instance detection a separate structural type ({ copy(): unknown }-based) that skips the callable/range checks the resolver doesn't rely on.

Happy to open a PR for the types if the direction sounds right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions