chartjs / Chart.js

Simple HTML5 Charts using the <canvas> tag
https://www.chartjs.org/
MIT License
64.35k stars 11.89k forks source link

Typehinting ChartOptions seems broken for pie and doughnut #10896

Open DrowningElysium opened 1 year ago

DrowningElysium commented 1 year ago

Expected behavior

I type hint ChartOptions or ChartOptions<ChartType> for a function and then pass a typehint of ChartOptions<'pie'> or ChartOptions<'doughnut'> .

Donut.vue:

<template>
    <Base
        :height="height"
        :width="width"
        :chart-options="chartOptions"
        :chart-data="chartData"
        chart-type="doughnut"
    />
</template>

<script setup lang="ts">
import {
    ArcElement,
    Chart,
    ChartData,
    ChartOptions,
    DoughnutController,
    Legend,
    Tooltip,
} from 'chart.js';
import Base from './Base.vue';

Chart.register(DoughnutController, ArcElement, Legend, Tooltip);

interface Props {
    chartData: ChartData<'doughnut', unknown[]>;
    chartOptions: ChartOptions<'doughnut'>;
    width?: number;
    height?: number;
}

withDefaults(
    defineProps<Props>(),
    {
        width: 200,
        height: 200,
    },
);
</script>

The props typehinting in Base.vue:

import {
    Chart,
    ChartData,
    ChartOptions,
    ChartType,
} from 'chart.js';
import {onBeforeUnmount, onMounted, ref, watch} from 'vue';

interface Props {
    chartType: ChartType;
    chartData: ChartData<ChartType, unknown[]>;
    chartOptions: ChartOptions<ChartType>;
    width: number;
    height: number;
    chartId?: number;
}

const props = withDefaults(
    defineProps<Props>(),
    {
        chartId: () => {
            // @ts-ignore this way it's global
            if (!window.chartId) window.chartId = 0;
            // @ts-ignore this way it's global
            return ++window.chartId;
        },
    },
);

Current behavior

I get the following error in vue-tsc:

resources/js/components/chart/Donut.vue:5:9 - error TS2322: Type '_DeepPartialObject<CoreChartOptions<"doughnut"> & ElementChartOptions<"doughnut"> & PluginChartOptions<"doughnut"> & DatasetChartOptions<"doughnut"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
  Types of property 'animation' are incompatible.
    Type 'false | _DeepPartialObject<false & DoughnutAnimationOptions> | _DeepPartialObject<AnimationSpec<"doughnut"> & { onProgress?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; } & false> | _DeepPartialObject<...> | undefi...' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart$4<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.
      Type '_DeepPartialObject<false & DoughnutAnimationOptions>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart$4<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.

5         :chart-options="chartOptions"
          ~~~~~~~~~~~~~

  resources/js/components/chart/Base.vue:19:5
    19     chartOptions: ChartOptions<ChartType>;
           ~~~~~~~~~~~~
    The expected type comes from property 'chartOptions' which is declared here on type 'ComponentProps<ComponentPublicInstanceConstructor<{ $: ComponentInternalInstance; $data: {}; $props: Partial<{ chartId: number; }> & Omit<Readonly<ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps<Props>, { ...; }>>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, "chartId">; ... 10 more...'

Reproducible sample

Code provided in current behaviour

Optional extra steps/info to reproduce

No response

Possible solution

I couldn't find the reason why animation seems to not be compatible. However I did notice that in some areas animation is typehinted as:

Context

No response

chart.js version

v4.0.1

Browser name and version

No response

Link to your project

No response

JayOfferdahl commented 1 year ago

I ran into a similar issue today when upgrading typescript from 4.8.2 to 4.9.3:

(!) Plugin typescript: @rollup/plugin-typescript TS2322: Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
  Types of property 'animation' are incompatible.
    Type 'false | _DeepPartialObject<false & DoughnutAnimationOptions> | _DeepPartialObject<AnimationSpec<"pie"> & { onProgress?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; } & false> | _DeepPartialObject<...> | undefined' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart$4<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.
      Type '_DeepPartialObject<false & DoughnutAnimationOptions>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart$4<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart$4<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.
****.tsx: (54:13)

54             chart.current.options = chartOptions
               ~~~~~~~~~~~~~~~~~~~~~

(!) Plugin typescript: @rollup/plugin-typescript TS2322: Type 'Chart$4<"pie", number[], unknown>' is not assignable to type 'Chart$4<keyof ChartTypeRegistry, (number | ScatterDataPoint | [number, number] | BubbleDataPoint | null)[], unknown>'.
  Types of property 'config' are incompatible.
    Type 'ChartConfiguration<"pie", number[], unknown> | ChartConfigurationCustomTypesPerDataset<"pie", number[], unknown>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | ScatterDataPoint | [number, number] | BubbleDataPoint | null)[], unknown> | ChartConfigurationCustomTypesPerDataset<...>'.
      Type 'ChartConfiguration<"pie", number[], unknown>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | ScatterDataPoint | [number, number] | BubbleDataPoint | null)[], unknown> | ChartConfigurationCustomTypesPerDataset<...>'.
        Type 'ChartConfiguration<"pie", number[], unknown>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | ScatterDataPoint | [number, number] | BubbleDataPoint | null)[], unknown>'.
          Types of property 'options' are incompatible.
            Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions> | undefined' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>> | undefined'.
              Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
****.tsx: (57:13)

57             chart.current = new Chart(canvas.current, {
               ~~~~~~~~~~~~~
ianparkinson commented 1 year ago

I'm also seeing this with chart.js 4.1.2, when upgrading typescript from 4.8.4 to 4.9.4. I see the same error starting from typescript 4.9.1-beta.

I'm also using chartjs-plugin-datalabels. Code like:

    const chartElement: HTMLCanvasElement = ...
    const options: ChartOptions<"pie"> = {}
    this.pieChart = new Chart(chartElement, {
      type: 'pie',
      plugins: [ChartDataLabels],
      data: {datasets: []},
      options: options
    });

..fails with:

       TS2322: Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
  Types of property 'animation' are incompatible.
    Type 'false | _DeepPartialObject<false & DoughnutAnimationOptions> | _DeepPartialObject<AnimationSpec<"pie"> & { onProgress?: (this: Chart<...>, event: AnimationEvent) => void; onComplete?: (this: Chart<...>, event: AnimationEvent) => void; } & false> | _DeepPartialObject<...>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: (this: Chart<keyof ChartTypeRegistry, (number | ... 2 more ... | BubbleDataPoint)[], unknown>, event: AnimationEvent) => void; onComplete?: (this: Chart<...>, event: AnimationEvent) => void; }>'.
      Type '_DeepPartialObject<false & DoughnutAnimationOptions>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: (this: Chart<keyof ChartTypeRegistry, (number | ... 2 more ... | BubbleDataPoint)[], unknown>, event: AnimationEvent) => void; onComplete?: (this: Chart<...>, event: AnimationEvent) => void; }>'.

There seems to be some interaction with the plugin. If I provide an empty list, or remove the plugins: property altogether, then the problem goes away. The following builds without error:

    const options: ChartOptions<"pie"> = {}
    this.pieChart = new Chart(chartElement, {
      type: 'pie',
      plugins: [],
      data: {datasets: []},
      options: options
    });

The chart itself renders fine if the TS error is suppressed with @ts-ignore.

princemaple commented 1 year ago

Still seeing it on 4.2.1, with ts 4.9.5. Errors about config instead of animation in my case though.

error TS2322: Type 'Chart<"pie", number[], string>' is not assignable to type 'Chart<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown>'.
  Types of property 'config' are incompatible.
    Type 'ChartConfiguration<"pie", number[], string> | ChartConfigurationCustomTypesPerDataset<"pie", number[], string>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown> | ChartConfigurationCustomTypesPerDataset<...>'.
      Type 'ChartConfiguration<"pie", number[], string>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown> | ChartConfigurationCustomTypesPerDataset<...>'.
        Type 'ChartConfiguration<"pie", number[], string>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown>'.
          Types of property 'options' are incompatible.
            Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions> | undefined' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>> | undefined'.
              Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
                Types of property 'animation' are incompatible.
                  Type 'false | _DeepPartialObject<false & DoughnutAnimationOptions> | _DeepPartialObject<AnimationSpec<"pie"> & { onProgress?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; } & false> | _DeepPartialObject<...> | undefined' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.
                    Type '_DeepPartialObject<false & DoughnutAnimationOptions>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.
reintroducing commented 1 year ago

For me, I just converted a file where I define Chart defaults and this was one that was erroring:

ChartJs.defaults.animation.duration = 0;
Property 'duration' does not exist on type 'false | (AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; })'.

is there any workaround to this? I'm new to TS and this was a bummer.

KevinFabre-ods commented 1 year ago

This is also the case when you try to create a type guard

export function isDoughnutConfiguration(config: ChartConfiguration): config is ChartConfiguration<'doughnut'> {
    return (config as ChartConfiguration).type === 'doughnut';
}

v3.8.2

teodorachiosa commented 1 month ago

Same here, I'm trying to use the cutout property from the doughnut chart:

  chartOptions: ChartOptions<'doughnut'> = {
    cutout: 80
  };

Output:

error TS2322: Type '_DeepPartialObject<CoreChartOptions<"doughnut"> & ElementChartOptions<"doughnut"> & PluginChartOptions<"doughnut"> & DatasetChartOptions<"doughnut"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions & ElementChartOptions & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.

Types of property 'animation' are incompatible. Type 'false | _DeepPartialObject<false & DoughnutAnimationOptions> | _DeepPartialObject<AnimationSpec<"doughnut"> & { onProgress?: (this: Chart<...>, event: AnimationEvent) => void; onComplete?: (this: Chart<...>, event: AnimationEvent) => void; } & false> | _DeepPartialObject<...>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec & { onProgress?: (this: Chart<keyof ChartTypeRegistry, (number | ... 2 more ... | BubbleDataPoint)[], unknown>, event: AnimationEvent) => void; onComplete?: (this: Chart<...>, event: AnimationEvent) => void; }>'. Type '_DeepPartialObject<false & DoughnutAnimationOptions>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec & { onProgress?: (this: Chart<keyof ChartTypeRegistry, (number | ... 2 more ... | BubbleDataPoint)[], unknown>, event: AnimationEvent) => void; onComplete?: (this: Chart<...>, event: AnimationEvent) => void; }>'.

Version: 4.4.3

davidnaumann-bastian commented 1 month ago

Also seeing above error on version 4.4.3, minimal example this this occurs on below:

    return new Chart(this.elementRef, {
      type: 'pie',
      data: {
        labels: ["hi", "bye"],
        datasets: [
          {data: [1, 2]}
        ]
      }
    });
  }
davidnaumann-bastian commented 1 month ago
    Type 'ChartConfiguration<"pie", number[], string> | ChartConfigurationCustomTypesPerDataset<"pie", number[], string>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown> | ChartConfigurationCustomTypesPerDataset<...>'.
      Type 'ChartConfiguration<"pie", number[], string>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown> | ChartConfigurationCustomTypesPerDataset<...>'.
        Type 'ChartConfiguration<"pie", number[], string>' is not assignable to type 'ChartConfiguration<keyof ChartTypeRegistry, (number | [number, number] | Point | BubbleDataPoint | null)[], unknown>'.
          Types of property 'options' are incompatible.
            Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions> | undefined' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>> | undefined'.
              Type '_DeepPartialObject<CoreChartOptions<"pie"> & ElementChartOptions<"pie"> & PluginChartOptions<"pie"> & DatasetChartOptions<"pie"> & ScaleChartOptions<...> & DoughnutControllerChartOptions>' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
                Types of property 'animation' are incompatible.
                  Type 'false | _DeepPartialObject<false & DoughnutAnimationOptions> | _DeepPartialObject<AnimationSpec<"pie"> & { onProgress?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; } & false> | _DeepPartialObject<...> | undefined' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.
                    Type '_DeepPartialObject<false & DoughnutAnimationOptions>' is not assignable to type 'false | _DeepPartialObject<AnimationSpec<keyof ChartTypeRegistry> & { onProgress?: ((this: Chart<keyof ChartTypeRegistry, (number | ... 3 more ... | null)[], unknown>, event: AnimationEvent) => void) | undefined; onComplete?: ((this: Chart<...>, event: AnimationEvent) => void) | undefined; }> | undefined'.

Seeing same error as reported from user above for both 'pie' and 'doughnut'.

Typescript version: Version 5.4.5

// @ts-expect-error works and renders chart correctly so likely just related to typing.

davidnaumann-bastian commented 1 month ago

Also effects PolarAreaChart as well.