Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use SimulationLinkDatum and SimulationNodeDatum in d3

I'm having a problem using the SimulationLinkDatum Type. I made two classes Node and Link that implement SimulationNodeDatum and SimulationLinkDatum. When I go to try and use SimulationLinkDatum typescript says that d.source.x, d.targe.x, etc typescript says that "Property 'x' does not exist on type 'string | number | Node'."

I understand that the SimulationLinkDatum accepts number | string | <T> for the source and target node properties and that if you use number or string that it will be mutated into a SimulationNodeDatum. Is this just something that we have to live with or is there a better way of using these interfaces?

Thanks

class D3Component {
    private createPathString(d: SimulationLinkDatum<Node>) {
        return 'M' + d.source.x + ',' + d.source.y + 'L' + d.target.x + ',' + d.target.y;
    }
}
class Node implements SimulationNodeDatum {
    public x: number;
    public y: number;
    constructor (public id: number) {}
}

class Link implements SimulationLinkDatum<Node> {
    constructor (public source: Node, public target: Node)  {}
}
like image 810
Dan Cavanagh Avatar asked Dec 05 '25 09:12

Dan Cavanagh


1 Answers

As you correctly point out, the TypeScript definitions for d3-force reflect the approach to mutating the node and link data structures inherent in the actual JS implementation.

Specifically, for the SimulationLinkDatum<NodeDatum extends SimulationNodeDatum> interface, the suggested practice is to use a custom type guard, e.g.:

function isNodeObject<T>(node: number | string| T): node is T {
    return typeof node !== 'number' && typeof node !== 'string';
}

Then the type narrowing of TypeScript can be used in as usual, e.g. in your case above:

private createPathString(d: SimulationLinkDatum<Node>) {
  if (isNodeObject(d.source) && isNodeObject(d.target)) {
    return 'M' + d.source.x + ',' + d.source.y + 'L' + d.target.x + ',' + d.target.y;
  } else {
    return '';
  }
}

While this is the most reusable approach, the following two alternatives may be more suitable in some situations:

(1) If it is known that a given code segment will only be invoked on links that have already been initialized, then one may simply cast (<SimNode>d.source).x. Using local variables for the cast node objects may improve downstream readability.

(2) Use the type narrowing directly in an if-clause without creating a re-usable custom type guard.

Given the way the interfaces are currently defined, these are the key ways to access the objects in the mutated properties.

like image 93
tomwanzek Avatar answered Dec 07 '25 01:12

tomwanzek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!