|
|
@@ -4,34 +4,38 @@
|
|
|
import * as React from 'react';
|
|
|
import * as d3 from "d3";
|
|
|
|
|
|
-export namespace d3Types {
|
|
|
- export type d3Node<NodeType> = {
|
|
|
- id: string,
|
|
|
- label: string,
|
|
|
- color: string,
|
|
|
- obj: NodeType;
|
|
|
- x?: number,
|
|
|
- y?: number,
|
|
|
- highlight: boolean,
|
|
|
- };
|
|
|
-
|
|
|
- export type d3Link<LinkType> = {
|
|
|
- source: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
|
|
|
- target: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
|
|
|
- label: string,
|
|
|
- color: string,
|
|
|
- bidirectional?: boolean,
|
|
|
- obj: LinkType;
|
|
|
- };
|
|
|
-
|
|
|
- export type d3Graph<NodeType,LinkType> = {
|
|
|
- nodes: d3Node<NodeType>[],
|
|
|
- links: d3Link<LinkType>[],
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-class Link<LinkType> extends React.Component<{ link: d3Types.d3Link<LinkType> }, {}> {
|
|
|
+export type D3NodeData<NodeType> = {
|
|
|
+ id: string,
|
|
|
+ label: string,
|
|
|
+ color: string,
|
|
|
+ obj: NodeType;
|
|
|
+ x?: number,
|
|
|
+ y?: number,
|
|
|
+ highlight: boolean,
|
|
|
+};
|
|
|
+
|
|
|
+export type D3LinkData<LinkType> = {
|
|
|
+ source: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
|
|
|
+ target: any, // initially string, but d3 replaces it by a d3Node<NodeType> (lol)
|
|
|
+ label: string,
|
|
|
+ color: string,
|
|
|
+ bidirectional?: boolean,
|
|
|
+ obj: LinkType;
|
|
|
+};
|
|
|
+
|
|
|
+export type D3GraphData<NodeType,LinkType> = {
|
|
|
+ nodes: D3NodeData<NodeType>[],
|
|
|
+ links: D3LinkData<LinkType>[],
|
|
|
+};
|
|
|
+
|
|
|
+export const emptyGraph: D3GraphData<any,any> = {
|
|
|
+ nodes: [],
|
|
|
+ links: [],
|
|
|
+};
|
|
|
+
|
|
|
+export const defaultGraphForces = {charge: -200, center: 0.2, link: 2};
|
|
|
+
|
|
|
+class D3Link<LinkType> extends React.Component<{ link: D3LinkData<LinkType> }, {}> {
|
|
|
ref: React.RefObject<SVGLineElement> = React.createRef<SVGLineElement>();
|
|
|
refLabel: React.RefObject<SVGTextElement> = React.createRef<SVGTextElement>();
|
|
|
|
|
|
@@ -73,8 +77,8 @@ class Link<LinkType> extends React.Component<{ link: d3Types.d3Link<LinkType> },
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[] }, {}> {
|
|
|
- links: Array<Link<LinkType> | null> = [];
|
|
|
+class D3Links<LinkType> extends React.Component<{ links: D3LinkData<LinkType>[] }, {}> {
|
|
|
+ links: Array<D3Link<LinkType> | null> = [];
|
|
|
|
|
|
ticked() {
|
|
|
this.links.forEach(l => l ? l.ticked() : null);
|
|
|
@@ -83,8 +87,8 @@ class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[
|
|
|
render() {
|
|
|
const nodeId = sourceOrTarget => sourceOrTarget.id ? sourceOrTarget.id : sourceOrTarget;
|
|
|
const key = link => nodeId(link.source)+nodeId(link.target)+link.label;
|
|
|
- const links = this.props.links.map((link: d3Types.d3Link<LinkType>, index: number) => {
|
|
|
- return <Link ref={link => this.links.push(link)} key={key(link)} link={link} />;
|
|
|
+ const links = this.props.links.map((link: D3LinkData<LinkType>, index: number) => {
|
|
|
+ return <D3Link ref={link => this.links.push(link)} key={key(link)} link={link} />;
|
|
|
});
|
|
|
|
|
|
return (
|
|
|
@@ -95,18 +99,18 @@ class Links<LinkType> extends React.Component<{ links: d3Types.d3Link<LinkType>[
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-interface NodeProps<NodeType> {
|
|
|
- node: d3Types.d3Node<NodeType>;
|
|
|
+interface D3NodeProps<NodeType> {
|
|
|
+ node: D3NodeData<NodeType>;
|
|
|
simulation: any;
|
|
|
mouseDownHandler: (event) => void;
|
|
|
mouseUpHandler: (event) => void;
|
|
|
}
|
|
|
-interface NodeState {
|
|
|
+interface D3NodeState {
|
|
|
dragging: boolean;
|
|
|
}
|
|
|
|
|
|
|
|
|
-class Node<NodeType> extends React.Component<NodeProps<NodeType>, NodeState> {
|
|
|
+class D3Node<NodeType> extends React.Component<D3NodeProps<NodeType>, D3NodeState> {
|
|
|
ref: React.RefObject<SVGCircleElement> = React.createRef<SVGCircleElement>();
|
|
|
|
|
|
constructor(props) {
|
|
|
@@ -176,16 +180,16 @@ class Node<NodeType> extends React.Component<NodeProps<NodeType>, NodeState> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-interface NodesProps<NodeType> {
|
|
|
- nodes: d3Types.d3Node<NodeType>[],
|
|
|
+interface D3NodesProps<NodeType> {
|
|
|
+ nodes: D3NodeData<NodeType>[],
|
|
|
simulation: any,
|
|
|
mouseDownHandler: (event, node) => void;
|
|
|
mouseUpHandler: (event, node) => void;
|
|
|
}
|
|
|
|
|
|
-class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
|
|
|
+class D3Nodes<NodeType> extends React.Component<D3NodesProps<NodeType>, {}> {
|
|
|
ref: React.RefObject<SVGGElement> = React.createRef<SVGGElement>();
|
|
|
- nodes: Array<Node<NodeType> | null> = [];
|
|
|
+ nodes: Array<D3Node<NodeType> | null> = [];
|
|
|
|
|
|
ticked() {
|
|
|
this.nodes.forEach(n => n ? n.ticked() : null);
|
|
|
@@ -193,8 +197,8 @@ class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
|
|
|
|
|
|
render() {
|
|
|
this.nodes = [];
|
|
|
- const nodes = this.props.nodes.map((node: d3Types.d3Node<NodeType>, index: number) => {
|
|
|
- return <Node
|
|
|
+ const nodes = this.props.nodes.map((node: D3NodeData<NodeType>, index: number) => {
|
|
|
+ return <D3Node
|
|
|
key={node.id}
|
|
|
ref={node => this.nodes.push(node)}
|
|
|
mouseDownHandler={event => this.props.mouseDownHandler(event, node)}
|
|
|
@@ -210,7 +214,7 @@ class Nodes<NodeType> extends React.Component<NodesProps<NodeType>, {}> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class Label<NodeType> extends React.Component<{ node: d3Types.d3Node<NodeType> }, {}> {
|
|
|
+class D3Label<NodeType> extends React.Component<{ node: D3NodeData<NodeType> }, {}> {
|
|
|
ref: React.RefObject<SVGTextElement>;
|
|
|
|
|
|
constructor(props) {
|
|
|
@@ -239,16 +243,16 @@ class Label<NodeType> extends React.Component<{ node: d3Types.d3Node<NodeType> }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class Labels<NodeType> extends React.Component<{ nodes: d3Types.d3Node<NodeType>[] }, {}> {
|
|
|
- labels: Array<Label<NodeType> | null> = [];
|
|
|
+class D3Labels<NodeType> extends React.Component<{ nodes: D3NodeData<NodeType>[] }, {}> {
|
|
|
+ labels: Array<D3Label<NodeType> | null> = [];
|
|
|
|
|
|
ticked() {
|
|
|
this.labels.forEach(l => l ? l.ticked() : null);
|
|
|
}
|
|
|
|
|
|
render() {
|
|
|
- const labels = this.props.nodes.map((node: d3Types.d3Node<NodeType>, index: number) => {
|
|
|
- return <Label ref={label => this.labels.push(label)} key={node.id} node={node} />;
|
|
|
+ const labels = this.props.nodes.map((node: D3NodeData<NodeType>, index: number) => {
|
|
|
+ return <D3Label ref={label => this.labels.push(label)} key={node.id} node={node} />;
|
|
|
});
|
|
|
|
|
|
return (
|
|
|
@@ -259,29 +263,25 @@ class Labels<NodeType> extends React.Component<{ nodes: d3Types.d3Node<NodeType>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export interface Forces {
|
|
|
+export interface D3Forces {
|
|
|
charge: number;
|
|
|
center: number;
|
|
|
link: number;
|
|
|
}
|
|
|
|
|
|
-export interface GraphProps<NodeType,LinkType> {
|
|
|
- graph: d3Types.d3Graph<NodeType,LinkType>;
|
|
|
- forces: Forces;
|
|
|
- mouseDownHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: d3Types.d3Node<NodeType>) => void;
|
|
|
- mouseUpHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: d3Types.d3Node<NodeType>) => void;
|
|
|
-}
|
|
|
-
|
|
|
-interface GraphState<NodeType,LinkType> {
|
|
|
- zoom: number;
|
|
|
+interface Props<NodeType,LinkType> {
|
|
|
+ graph: D3GraphData<NodeType,LinkType>;
|
|
|
+ forces: D3Forces;
|
|
|
+ mouseDownHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: D3NodeData<NodeType>) => void;
|
|
|
+ mouseUpHandler?: (e: React.SyntheticEvent, svgCoords: {x: number, y: number}, node?: D3NodeData<NodeType>) => void;
|
|
|
}
|
|
|
|
|
|
-export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeType,LinkType>, GraphState<NodeType,LinkType>> {
|
|
|
+export class D3Graph<NodeType,LinkType> extends React.Component<Props<NodeType,LinkType>, { zoom: number; }> {
|
|
|
simulation: any;
|
|
|
refSVG: React.RefObject<SVGSVGElement> = React.createRef<SVGSVGElement>();
|
|
|
- refNodes: React.RefObject<Nodes<NodeType>> = React.createRef<Nodes<NodeType>>();
|
|
|
- refLabels: React.RefObject<Labels<NodeType>> = React.createRef<Labels<NodeType>>();
|
|
|
- refLinks: React.RefObject<Links<LinkType>> = React.createRef<Links<LinkType>>();
|
|
|
+ refNodes: React.RefObject<D3Nodes<NodeType>> = React.createRef<D3Nodes<NodeType>>();
|
|
|
+ refLabels: React.RefObject<D3Labels<NodeType>> = React.createRef<D3Labels<NodeType>>();
|
|
|
+ refLinks: React.RefObject<D3Links<LinkType>> = React.createRef<D3Links<LinkType>>();
|
|
|
|
|
|
constructor(props) {
|
|
|
super(props);
|
|
|
@@ -339,9 +339,9 @@ export class Graph<NodeType,LinkType> extends React.Component<GraphProps<NodeTyp
|
|
|
</marker>
|
|
|
</defs>
|
|
|
|
|
|
- <Links ref={this.refLinks} links={graph.links} />
|
|
|
- <Labels ref={this.refLabels} nodes={graph.nodes} />
|
|
|
- <Nodes ref={this.refNodes} nodes={graph.nodes} simulation={this.simulation}
|
|
|
+ <D3Links ref={this.refLinks} links={graph.links} />
|
|
|
+ <D3Labels ref={this.refLabels} nodes={graph.nodes} />
|
|
|
+ <D3Nodes ref={this.refNodes} nodes={graph.nodes} simulation={this.simulation}
|
|
|
mouseDownHandler={(e, node) => clientToSvgCoords(e, (e,coords) => this.props.mouseDownHandler?.(e,coords,node))}
|
|
|
mouseUpHandler={(e, node) => clientToSvgCoords(e, (e,coords) => this.props.mouseUpHandler?.(e,coords,node))}
|
|
|
/>
|