import React, { useEffect, useRef, useReducer, useMemo, useCallback } from 'react';
import { forceLink, forceSimulation, forceCenter, forceCollide, forceManyBody, forceRadial } from 'd3-force';
import { Graph } from '@visx/network';
import { Tooltip, useTooltip, defaultStyles } from '@visx/tooltip';
import { Zoom } from '@visx/zoom';
import * as colors from '@material-ui/core/colors';
const kNodes = Symbol('nodes');
const kLinks = Symbol('links');
const kUpdate = Symbol('update');
const background = '#101020';
const nodeStyle = {
default: {
fill: colors.pink[400],
r: 20
},
root: {
fill: colors.blue[400],
r: 25
},
adjacent: {
fill: colors.red[400],
r: 20
},
detach: {
fill: colors.grey[400],
r: 15
}
};
const tooltipStyles = {
...defaultStyles,
backgroundColor: 'rgba(53,71,125,0.9)',
color: 'white',
padding: 12,
zIndex: 1000
};
const useForceUpdate = () => useReducer(x => !x, false)[1];
const useForce = ({ width, height, graph }) => {
const forceUpdate = useForceUpdate();
const d3force = useRef(null);
if (d3force.current) {
const oldNodes = d3force.current[kNodes];
const newNodes = graph.nodes.map(n => n.id);
let update = newNodes.filter(id => !oldNodes.includes(id)).length !== oldNodes.filter(id => !newNodes.includes(id)).length;
if (!update) {
const oldLinks = d3force.current[kLinks];
const newLinks = graph.links.map(l => l.id);
update = newLinks.filter(id => !oldLinks.includes(id)).length !== oldLinks.filter(id => !newLinks.includes(id)).length;
}
if (update) {
d3force.current[kUpdate] = true;
}
}
useEffect(() => {
let restart = true;
if (!d3force.current) {
restart = false;
d3force.current = forceSimulation(graph.nodes)
.force('link', forceLink().id(d => d.id).links(graph.links))
.force('charge', forceManyBody().strength(-2000))
.force('collision', forceCollide().strength(1));
d3force.current.on('tick', () => {
forceUpdate();
});
}
d3force.current
.force('center', forceCenter(width / 2, height / 2).strength(0))
.force('r', forceRadial(200).strength(1));
d3force.current[kNodes] = graph.nodes.map(n => n.id);
d3force.current[kLinks] = graph.links.map(l => l.id);
if (!restart) {
return;
}
d3force.current.nodes(graph.nodes);
d3force.current.force('link').links(graph.links);
if (d3force.current[kUpdate]) {
d3force.current[kUpdate] = false;
d3force.current.alpha(1).restart();
} else {
d3force.current.restart();
}
}, [width, height, graph]);
};
const useNode = (handlers) => useMemo(() => function Node ({ node }) {
const { label, type = 'default', ...positions } = node;
const style = nodeStyle[type] || {};
const handleMouseEnter = useCallback((e) => handlers.onMouseEnter(node, e), [node]);
const handleMouseLeave = useCallback((e) => handlers.onMouseLeave(node, e), [node]);
return (