Skip to content

Add type helper function #7046

Description

@PavelVanecek

Discussion

So we have added (and not yet released) TypeScript generics on our charts and components so that we can have TS check that the data and dataKey are matching. Because TS and React are not 100% integrated, TS doesn't check for context and can't infer properly.

That means that to type properly we basically need everyone to set their generics on every component explicitly. This is a hassle and prone to problems, as there are some assumptions: for example the data type of numerical axis and graphical item must match. It's easy to make a mistake in that.

We could offer a helper function that will force the right types. Actually we need multiple functions. Why? Because the data types swap around. In horizontal chart, the data type of graphical item and YAxis must be the same. In vertical chart, it's graphical item and XAxis that should be the same! So we actually need 4 helper functions, one for each layout:

  • createHorizontalChart
  • createVerticalChart
  • createRadialChart
  • createCentricChart

And now it would be super helpful if we could also hardcode the layout prop for convenience, and also so that nobody can call createVerticalChart and then pass in layout='horizontal'. Which we can do by either:

  1. Just making a wrapper component (props) => <AreaChart {...props} layout='vertical' />

  2. Adding a new context provider, and selectors, and type override, and have the layout resolved from the outside and bypass the prop

  3. is a lot simpler but it means we have to import all of the charts and set the prop. Importing means we lose the opportunity to tree-shake and potentially we increase our bundle size a lot. How much is the increase actually? I had Copilot write a bunch of scripts to find out and discovered that the increase is ... 0.01%:

npx tsx scripts/treeshaking.ts Bar BarChart
┌─────────┬─────────────────┬─────────┬───────────────────┬──────────────────────────────┐
│ (index) │ stage           │ bytes   │ humanReadableSize │ reductionFromBaselinePercent │
├─────────┼─────────────────┼─────────┼───────────────────┼──────────────────────────────┤
│ 0       │ 'es6-folder'    │ 1099081 │ '1.05 MB'         │ 0                            │
│ 1       │ 'tree-shaken'   │ 458672  │ '447.92 KB'       │ 58.27                        │
│ 2       │ 'minified'      │ 159905  │ '156.16 KB'       │ 85.45                        │
│ 3       │ 'minified+gzip' │ 42652   │ '41.65 KB'        │ 96.12                        │
└─────────┴─────────────────┴─────────┴───────────────────┴──────────────────────────────┘
npx tsx scripts/treeshaking.ts Bar BarChart AreaChart ScatterChart LineChart FunnelChart ComposedChart
┌─────────┬─────────────────┬─────────┬───────────────────┬──────────────────────────────┐
│ (index) │ stage           │ bytes   │ humanReadableSize │ reductionFromBaselinePercent │
├─────────┼─────────────────┼─────────┼───────────────────┼──────────────────────────────┤
│ 0       │ 'es6-folder'    │ 1099081 │ '1.05 MB'         │ 0                            │
│ 1       │ 'tree-shaken'   │ 461274  │ '450.46 KB'       │ 58.03                        │
│ 2       │ 'minified'      │ 160913  │ '157.14 KB'       │ 85.36                        │
│ 3       │ 'minified+gzip' │ 42776   │ '41.77 KB'        │ 96.11                        │
└─────────┴─────────────────┴─────────┴───────────────────┴──────────────────────────────┘

It looks like because all the Cartesian charts are almost aliases, importing one chart is almost the same cost (in terms of kB use) as importing them all. Because to render a meaningful chart, one has to import at least one chart, I think it's safe to just forcefully bundle them all, for convenience.

On the other hand, bundling other components brings a significant size difference:

npx tsx scripts/treeshaking.ts Bar BarChart AreaChart ScatterChart LineChart FunnelChart ComposedChart XAxis YAxis Legend Tooltip CartesianGrid Legend Tooltip CartesianGrid Line Scatter Area Funnel
┌─────────┬─────────────────┬─────────┬───────────────────┬──────────────────────────────┐
│ (index) │ stage           │ bytes   │ humanReadableSize │ reductionFromBaselinePercent │
├─────────┼─────────────────┼─────────┼───────────────────┼──────────────────────────────┤
│ 0       │ 'es6-folder'    │ 1099455 │ '1.05 MB'         │ 0                            │
│ 1       │ 'tree-shaken'   │ 708485  │ '691.88 KB'       │ 35.56                        │
│ 2       │ 'minified'      │ 260155  │ '254.06 KB'       │ 76.34                        │
│ 3       │ 'minified+gzip' │ 65029   │ '63.50 KB'        │ 94.09                        │
└─────────┴─────────────────┴─────────┴───────────────────┴──────────────────────────────┘

So we would not bundle those by default. We can accept the list, or a record, of components the user wishes to use as the input.

Features

The functions should allow setting three different generics (data object type, categorical axis type, numerical axis type) and include these generics to all relevant components.

On top of that it should also set the chart layout (horizontal/vertical).

Example

I set my generics like this:

DataType = { foo: string, bar: number }
CategoricalAxisType = string
NumericalAxisType = number

And say that I want to use components Area, AreaChart, XAxis, YAxis. The outcome of calling this function should be that my components are typed as:

Area<DataType, number>
AreaChart<DataType>
XAxis<DataType, string>
YAxis<DataType, number>

Signature

Open question - how do we want this to look like? The signature must support tree-shaking, and should be convenient to use.

Metadata

Metadata

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