Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 (v4) tree layout and typescript: Property 'x' does not exist on type 'HierarchyNode<IOrgChartNode>'

I am trying to use proper ts types while using a d3.hierarchy root with a tree layout, e.g.:

interface MyCustomGraphType {
  id: string;
  children?: MyCustomGraphType[]
}
let treeData: MyCustomGraphType = {/*...*/};
let root = d3.hierarchy(treeData);
let layout = d3.tree().size([500,500])(root);
let nodes = layout.descendants();

// BIND DATA (Node)
let node = this.nodesGroup.selectAll('g.node')
  .data(nodes, d => d.data.person.email); // <--- compile-time error
   // ^^^ Property 'data' does not exist on type '{} | HierarchyNode<MyCustomGraphType>'.

// ... later:
node.enter()
  .append('circle')
  .attr('transform', d => `translate(${d.x}, ${d.y})`); // <--- compile-time error
   // ^^^ Property 'y' does not exist on type '{} | HierarchyNode<MyCustomGraphType>'.

Clearly the first error is because for whatever reason the union type '{} | HierarchyNode<MyCustomGraphType>' is inferred in the key function. The second error is due to the fact that d3.tree adds properties that were previously not defined there.

What's a clean way to approach this while keeping type-safety?

Thanks!

P.S. I am using d3 version 4

like image 512
AndyPerlitch Avatar asked Jul 27 '17 16:07

AndyPerlitch


1 Answers

There are a few things going on here, which should be readily resolvable:

(1) To clarify, I assume that your actual data structure is something more like this:

interface MyCustomGraphType {
  id: string;
  person: {
    email: string;
    /*Other Properties*/
  };
  children?: MyCustomGraphType[];
}

This would explain your accessing the person.email proprty of a node in the selection.data(...) key function.

(2) The D3 definitions make extensive use of generic type parameters. In some cases type inference will serve them up readily. In others, they cannot be readily inferred.

  • Use d3.tree<MyCustomGraphType>().size([500,500])(root); This will return a tree layout root point of type HierarchyPointNode<MyCustomGraphType>. By implication nodes will now be an HierarchyPointNode<MyCustomGraphType>[] array.
  • select, selectAll, append and data from the d3-selection module have extensive JSDoc comments regarding the various overloaded signatures' generics. They should be available as mouseover hints or similar in the code editor (e.g. VS Code).

(3) The reason why the key accessor in the data method call errors out, is as follows: The key accessor is used to match old and new data entries. The old data type is based on the preceding selectAll(...) statement. Given that the generic types of the selected elements and their "old" data type cannot be inferred from a string-based selector, they must be set explicitly. Otherwise, the "old" data type defaults to {}. That is why you see the union data type {} | HierarchyPointNode<MyCustomGraphType>. Care must be taken that the "old" data type of the selected elements is in synch between the actual selected elements and the key accessor. The key function should have a way to handle edge cases, if needed.

(4) As for the missing properties x or y, I do not seem to able to replicate this issue. For me they are present, as the data type of d in

attr('transform', d => `translate(${d.x}, ${d.y})`)

is correctly inferred as HierarchyPointNode<MyCustomGraphType>.

Hopefully this explains.

like image 131
tomwanzek Avatar answered Sep 18 '22 19:09

tomwanzek