skip to Main Content

I’m struggling to utilize d3’s .extent() function in typescript.

I’ve got some code here that creates Date objects out of dates

 const dates:(Date | null)[] = data.map(row => d3.timeParse("%Y-%m-%d")(row.Date))

When I console log the dates variable, it looks something like this

[Sat Jun 24 2023 00:00:00 GMT-0700 (Pacific Daylight Time), Sat Jun 24 2023 00:00:00 GMT-0700 (Pacific Daylight Time), Fri Jun 23 2023 00:00:00 GMT-0700 (Pacific Daylight Time), Fri Jun 23 2023 00:00:00 GMT-0700 (Pacific Daylight Time), Fri Jun 23 2023 00:00:00 GMT-0700 (Pacific Daylight Time), ...]

Therefore, I know my dates variable is full of Date objects, like expected…

However, when I try and use the d3.extent() function to find the highest and lowest dates

const xScale = d3
      .scaleTime()
      .domain(d3.extent(dates))
Argument of type '[undefined, undefined] | [string, string]' is not assignable to parameter of type 'Iterable<Date | NumberValue>'.

I’m confused as to why this might be, though, as my dates variable is in fact exclusively dates, and not, as is expressed in the error, ‘[undefined, undefined] | [string, string]’.

What is wrong about what I am passing in? How should I be passing in dates to the extent() function in typescript?

2

Answers


  1. The issue you’re facing is related to the fact that the map() function can potentially return null values in your dates array. This is because the d3.timeParse() function might return null if it fails to parse a date string.

    To fix this issue, you can filter out the null values from the dates array before passing it to the d3.extent() function. Here’s an example of how you can do it:

    const validDates = dates.filter(date => date !== null);
    const xScale = d3.scaleTime().domain(d3.extent(validDates));
    

    By using the filter() function, you remove any null values from the dates array, ensuring that only valid Date objects are passed to the d3.extent() function.

    This should resolve the error you’re encountering and allow you to correctly calculate the extent of your dates.

    Login or Signup to reply.
  2. Filtering for null instances is a good approach, but you get:
    Type '(Date | null)[]' is not assignable to type 'Date[]'. Type 'Date | null' is not assignable to type 'Date'. Type 'null' is not assignable to type 'Date'.

    That is because TypeScript is still considering the possibility of null values in your array even after the filter operation.

    You should include a type predicate date is Date in the filter function. That explicitly tells TypeScript that the result of the filter will only include Date objects, and no null values.

    const dates:(Date | null)[] = data
        .map(row => d3.timeParse("%Y-%m-%d")(row.Date))
        .filter((date): date is Date => date !== null);
    

    That does create a filter function that not only checks if the date is not null, but also uses a type guard to tell TypeScript that every item that passes this filter is a Date.
    (See also "Type predicates: Filtering arrays in Typescript" from Shreejit Rajbanshi)

    Followed with:

    const xScale = d3
        .scaleTime()
        .domain(d3.extent(dates) as [Date, Date]);
    

    It uses as [Date, Date] to tell TypeScript to trust us that the domain will be a tuple of two Date objects.

    The as [Date, Date] is a type assertion, it is not changing the actual value or operation of the code, but rather just informing TypeScript of the type. That will not protect you from null or undefined values at runtime, it is purely for TypeScript’s static type checking. Hence, why we did the null check before during the filter operation.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search