Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript expects undefined to be returned from helper function in d3.nest().rollup()

I am currently building a visualization in D3 (v4.4.0) and Typsecript, having the typings installed from @types/d3. For the visualization I need to nest my data. This I am trying to do with this snippet, that should count the number of entries per key:

d3.nest()
  .key(function(d: any) {return d.key;})
  .rollup(function(leaves: any){
    return d3.sum(leaves, function (d) {
      return 1;
    })
  })
  .entries(this.data);

For the helper function in the rollup method I get a type error:

severity: 'Error'
message: 'Argument of type '(leaves: any) => number' is not assignable to parameter of type '(values: {}[]) => undefined'.
Type 'number' is not assignable to type 'undefined'.'
at: '46,15'
source: 'ts'

So it seems tsc expects rollup() to return undefined, which does not make sense to me and this code worked fine in pure javascript. Just nesting the data without the rollup() also works.

Does anybody know what could have gone wrong here?

Thanks, Thomas

like image 744
tdraebing Avatar asked Dec 29 '16 21:12

tdraebing


2 Answers

In D3 v4 there are a couple of signatures for the d3.nest() factory. The only difference between them is how much control you can exercise with respect to the two generics which control data types constraining the returned Nest<Datum, RollupType> generator.

As completion of the JSDoc comments for d3-collection is still a tracked to-do, I will provide a brief explanation here in the interim.

In the Nest<Datum, RollupType> interface the first generic, Datum, refers to the data type of an element in the data array passed into the following methods of the Nest generator:

  • map(...)
  • object(...)
  • entries(...)

By the same token, it is the data type of the argument of the key accessor function and, as in your case, the data type of an element of the array passed into the roll-up function as an argument.

So it is always prudent to explicitly set the Datum when invoking the d3.nest() factory (see below).

The second generic constraining the Nest generator, RollupType is only relevant, if one intends to configure/use the rollup(...) accessor, as in your case. It controls the return type of the roll-up function.

So, with this being said, as you require the roll-up functionality, you should use something like the following, tailored to your source data type to be nested:

interface YourDatum {
  key: string;
  // any proprties related to the value of this element
}

let data: YourDatum[];

data = [/* set your data */]

d3.nest<YourDatum, number>()
  .key(function (d) { return d.key; })
  .rollup(function (leaves) {
    return d3.sum(leaves, function (d) {
      return 1;
    })
  })
  .entries(data);

}

Note, that the accessor functions passed into key and rollup are constrained by the interface definition and the specified types for the generics.

If you do not use the generics when simply calling d3.nest(), defaults will mean you are returned a Nest<{}, undefined>. This forces the error you experienced as a little nudge to ensure the data types underlying the nesting correspond to the argument/return types in methods and accessor functions.

That being said, there is a longer comment related to the return types of the map(...), object(...) and entries(...) methods in the definition file. It sheds some light on the complexities of the nesting process, i.p. if roll-up is used. You can find it here.

The use of generics on factories, is now fairly extensive throughout the D3 v4 definitions.

As a final comment, I am also tracking an issue on DefinitelyTyped to validate d3-collection for strictNullChecks. Will do that jointly with the JSDoc comments, when I have a quiet moment...

Hope this helps explain.

like image 66
tomwanzek Avatar answered Sep 25 '22 23:09

tomwanzek


It seems to be an issue on the signature declaration of the rollup() method.

This error, and pretty much any other inconsistencies that you find in the d3 typings, can be ignored by simply casting the conflicting elements to the any type:

d3.nest()
  .key(function(d: any) {return d.key;})
  .rollup(function(leaves: any){
    return d3.sum(leaves, function (d) {
      return 1;
    }) as any
  })
  .entries(this.data);

The D3 API is huge so you're likely to find more errors like this in less popular features. You could help by searching if there is already an open issue related to the error you found and if not submit a new issue.

like image 32
kbtz Avatar answered Sep 25 '22 23:09

kbtz