Add draggability to topology graph nodes
This commit is contained in:
parent
705aa32e73
commit
14a5433a70
@ -106,7 +106,7 @@ const GraphLink = ({
|
|||||||
const sourceAngle = getAngleFromPoints(source, target);
|
const sourceAngle = getAngleFromPoints(source, target);
|
||||||
const sourcePosition = getPosition(sourceAngle, positions, source);
|
const sourcePosition = getPosition(sourceAngle, positions, source);
|
||||||
const targetAngle = getAngleFromPoints(target, source);
|
const targetAngle = getAngleFromPoints(target, source);
|
||||||
const targetPosition = getPosition(targetAngle, positions, target, true);
|
const targetPosition = getPosition(targetAngle, positions, target); //, true);
|
||||||
const arrowAngle = getAngleFromPoints(sourcePosition, targetPosition);
|
const arrowAngle = getAngleFromPoints(sourcePosition, targetPosition);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -45,7 +45,8 @@ const HeartCircle = styled.circle`
|
|||||||
|
|
||||||
const GraphNode = ({
|
const GraphNode = ({
|
||||||
data,
|
data,
|
||||||
size
|
size,
|
||||||
|
onDragStart
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -79,9 +80,17 @@ const GraphNode = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// const titleBbBox = {x:100, y: 30 - halfHeight};
|
// const titleBbBox = {x:100, y: 30 - halfHeight};
|
||||||
|
const onStart = (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
onDragStart(evt, data.id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g transform={`translate(${data.x}, ${data.y})`}>
|
<g
|
||||||
|
transform={`translate(${data.x}, ${data.y})`}
|
||||||
|
onMouseDown={onStart}
|
||||||
|
onTouchStart={onStart}
|
||||||
|
>
|
||||||
<StyledShadowRect
|
<StyledShadowRect
|
||||||
x={-halfWidth}
|
x={-halfWidth}
|
||||||
y={3-halfHeight}
|
y={3-halfHeight}
|
||||||
@ -133,7 +142,8 @@ const GraphNode = ({
|
|||||||
|
|
||||||
GraphNode.propTypes = {
|
GraphNode.propTypes = {
|
||||||
data: React.PropTypes.object.isRequired,
|
data: React.PropTypes.object.isRequired,
|
||||||
size: PropTypes.Size,
|
onDragStart: React.PropTypes.func,
|
||||||
|
size: PropTypes.Size
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = GraphNode;
|
module.exports = GraphNode;
|
||||||
|
@ -38,37 +38,39 @@ const createSimulation = (
|
|||||||
|
|
||||||
const nodeRadius = rectRadius(nodeSize);
|
const nodeRadius = rectRadius(nodeSize);
|
||||||
|
|
||||||
return d3.forceSimulation(mappedNodes)
|
return ({
|
||||||
.force('link', d3.forceLink(mappedLinks).id(d => d.id))
|
simulation: d3.forceSimulation(mappedNodes)
|
||||||
.force('collide', d3.forceCollide(nodeRadius))
|
.force('link', d3.forceLink(mappedLinks).id(d => d.id))
|
||||||
.force('center', d3.forceCenter(width/2, height/2))
|
.force('collide', d3.forceCollide(nodeRadius))
|
||||||
.on('tick', onTick)
|
.force('center', d3.forceCenter(width/2, height/2))
|
||||||
.on('end', onEnd);
|
.on('tick', onTick)
|
||||||
|
.on('end', onEnd),
|
||||||
|
nodes: mappedNodes,
|
||||||
|
links: mappedLinks
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO we need to kill the previous simulation
|
// TODO we need to kill the previous simulation
|
||||||
const updateSimulation = (
|
const updateSimulation = (
|
||||||
simulation,
|
simulation,
|
||||||
nodes,
|
|
||||||
links,
|
|
||||||
nextNodes,
|
nextNodes,
|
||||||
nextLinks,
|
nextLinks,
|
||||||
|
simNodes,
|
||||||
|
simLinks,
|
||||||
nodeSize,
|
nodeSize,
|
||||||
svgSize,
|
svgSize,
|
||||||
onTick,
|
onTick,
|
||||||
onEnd
|
onEnd
|
||||||
) => {
|
) => {
|
||||||
// want to copy all the existing nodes that we still need and freeze them
|
|
||||||
// want to copy all the existing links we still need
|
|
||||||
// if we have any new nodes / links, we should add them
|
|
||||||
// this is going to be messy!!! maybe not so much!!! :D <3
|
|
||||||
const mappedNodes = nextNodes.map((nextNode, index) => {
|
const mappedNodes = nextNodes.map((nextNode, index) => {
|
||||||
const node = nodes.reduce((acc, n, i) =>
|
const simNode = simNodes.reduce((acc, n, i) => {
|
||||||
nextNode.id === n.id ? n : acc ? null : acc);
|
return nextNode.id === n.id ? n : acc;
|
||||||
return node ? {
|
}, null);
|
||||||
id: node.id,
|
|
||||||
fx: node.x,
|
return simNode ? {
|
||||||
fy: node.y,
|
id: simNode.id,
|
||||||
|
// fx: simNode.x,
|
||||||
|
// fy: simNode.y,
|
||||||
index: index
|
index: index
|
||||||
} : {
|
} : {
|
||||||
id: nextNode.id,
|
id: nextNode.id,
|
||||||
@ -77,11 +79,12 @@ const updateSimulation = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mappedLinks = nextLinks.map((nextLink, index) => {
|
const mappedLinks = nextLinks.map((nextLink, index) => {
|
||||||
const link = links.reduce((acc, l, i) =>
|
const simLink = simLinks.reduce((acc, l, i) => {
|
||||||
nextLink.source === l.source && nextLink.target === l.target ?
|
return nextLink.source === l.source && nextLink.target === l.target ?
|
||||||
l : acc ? null : acc);
|
l : acc;
|
||||||
return link ? {
|
}, {});
|
||||||
...link
|
return simLink ? {
|
||||||
|
...simLink
|
||||||
} : {
|
} : {
|
||||||
...nextLink
|
...nextLink
|
||||||
};
|
};
|
||||||
@ -94,12 +97,16 @@ const updateSimulation = (
|
|||||||
|
|
||||||
const nodeRadius = rectRadius(nodeSize);
|
const nodeRadius = rectRadius(nodeSize);
|
||||||
|
|
||||||
return d3.forceSimulation(mappedNodes)
|
return ({
|
||||||
.force('link', d3.forceLink(mappedLinks).id(d => d.id))
|
simulation: d3.forceSimulation(mappedNodes)
|
||||||
.force('collide', d3.forceCollide(nodeRadius))
|
.force('link', d3.forceLink(mappedLinks).id(d => d.id))
|
||||||
.force('center', d3.forceCenter(width/2, height/2))
|
.force('collide', d3.forceCollide(nodeRadius))
|
||||||
.on('tick', onTick)
|
.force('center', d3.forceCenter(width/2, height/2))
|
||||||
.on('end', onEnd);
|
.on('tick', onTick)
|
||||||
|
.on('end', onEnd),
|
||||||
|
nodes: mappedNodes,
|
||||||
|
links: mappedLinks
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -29,6 +29,12 @@ const svgSize = {
|
|||||||
height: 860
|
height: 860
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let dragInfo = {
|
||||||
|
dragging: false,
|
||||||
|
nodeId: null,
|
||||||
|
position: null
|
||||||
|
};
|
||||||
|
|
||||||
class TopologyGraph extends React.Component {
|
class TopologyGraph extends React.Component {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
@ -38,15 +44,17 @@ class TopologyGraph extends React.Component {
|
|||||||
links
|
links
|
||||||
} = this.props.data;
|
} = this.props.data;
|
||||||
|
|
||||||
const simulation = createSimulation(
|
const simulationData = createSimulation(
|
||||||
nodes,
|
nodes,
|
||||||
links,
|
links,
|
||||||
nodeSize,
|
nodeSize,
|
||||||
svgSize,
|
svgSize//,
|
||||||
() => this.forceUpdate(),
|
//() => this.forceUpdate(),
|
||||||
() => this.forceUpdate()
|
//() => this.forceUpdate()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const simulation = simulationData.simulation;
|
||||||
|
|
||||||
const n = Math.ceil(
|
const n = Math.ceil(
|
||||||
Math.log(
|
Math.log(
|
||||||
simulation.alphaMin()) / Math.log(
|
simulation.alphaMin()) / Math.log(
|
||||||
@ -55,9 +63,7 @@ class TopologyGraph extends React.Component {
|
|||||||
simulation.tick();
|
simulation.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState(simulationData);
|
||||||
simulation: simulation
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
@ -68,33 +74,34 @@ class TopologyGraph extends React.Component {
|
|||||||
// otherwise, redo them bitches = by what I mean to update the simulation
|
// otherwise, redo them bitches = by what I mean to update the simulation
|
||||||
// try freezing exisiting ones... then adding another
|
// try freezing exisiting ones... then adding another
|
||||||
|
|
||||||
|
const {
|
||||||
|
nodes: simNodes,
|
||||||
|
links: simLinks
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
nodes: nextNodes,
|
nodes: nextNodes,
|
||||||
links: nextLinks
|
links: nextLinks
|
||||||
} = nextProps.data;
|
} = nextProps.data;
|
||||||
|
|
||||||
const {
|
|
||||||
nodes,
|
|
||||||
links
|
|
||||||
} = this.props.data;
|
|
||||||
|
|
||||||
// this is tmp for the compare above
|
// this is tmp for the compare above
|
||||||
if(nextNodes.length !== nodes.length || nextLinks.length !== links.length) {
|
if(nextNodes.length !== simNodes.length ||
|
||||||
|
nextLinks.length !== simLinks.length) {
|
||||||
const simulation = this.state.simulation;
|
const simulation = this.state.simulation;
|
||||||
const nextSimulation = updateSimulation(
|
const nextSimulationData = updateSimulation(
|
||||||
simulation,
|
simulation,
|
||||||
nodes,
|
|
||||||
links,
|
|
||||||
nextNodes,
|
nextNodes,
|
||||||
nextLinks,
|
nextLinks,
|
||||||
|
simNodes,
|
||||||
|
simLinks,
|
||||||
nodeSize,
|
nodeSize,
|
||||||
svgSize,
|
svgSize,
|
||||||
() => this.forceUpdate(),
|
() => this.forceUpdate(),
|
||||||
() => this.forceUpdate()
|
() => this.forceUpdate()
|
||||||
);
|
);
|
||||||
this.setState({
|
|
||||||
simulation: nextSimulation
|
const nextSimulation = nextSimulationData.simulation;
|
||||||
});
|
// console.log('nextSimulationData.nodes = ', nextSimulationData.nodes);
|
||||||
|
|
||||||
const n = Math.ceil(
|
const n = Math.ceil(
|
||||||
Math.log(
|
Math.log(
|
||||||
@ -103,6 +110,13 @@ class TopologyGraph extends React.Component {
|
|||||||
for (var i = 0; i < n; ++i) {
|
for (var i = 0; i < n; ++i) {
|
||||||
nextSimulation.tick();
|
nextSimulation.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*this.state.simulation.nodes().forEach((node, index) => {
|
||||||
|
delete node.fx;
|
||||||
|
delete node.fy;
|
||||||
|
});*/
|
||||||
|
|
||||||
|
this.setState(nextSimulationData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,27 +127,81 @@ class TopologyGraph extends React.Component {
|
|||||||
links
|
links
|
||||||
} = this.props.data;
|
} = this.props.data;
|
||||||
|
|
||||||
const simulationNodes = this.state.simulation.nodes();
|
const simulationNodes = this.state.nodes;
|
||||||
|
|
||||||
|
const simulationNode = (nodeId) =>
|
||||||
|
simulationNodes.reduce((acc, simNode, index) => {
|
||||||
|
return simNode.id === nodeId ? simNode : acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
const nodesData = nodes.map((node, index) => ({
|
const nodesData = nodes.map((node, index) => ({
|
||||||
...node,
|
...node,
|
||||||
...simulationNodes.reduce((acc, simNode, index) =>
|
...simulationNode(node.id)
|
||||||
simNode.id === node.id ? simNode : acc)
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const linksData = links.map((link, index) => ({
|
const linksData = links.map((link, index) => ({
|
||||||
source: simulationNodes.reduce((acc, simNode, index) =>
|
source: simulationNode(link.source),
|
||||||
simNode.id === link.source ? simNode : acc),
|
target: simulationNode(link.target)
|
||||||
target: simulationNodes.reduce((acc, simNode, index) =>
|
|
||||||
simNode.id === link.target ? simNode : acc)
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const onDragStart = (evt, nodeId) => {
|
||||||
|
// it's this node's position that we'll need to update
|
||||||
|
dragInfo.dragging = true;
|
||||||
|
dragInfo.nodeId = nodeId;
|
||||||
|
dragInfo.position = {
|
||||||
|
x: evt.clientX,
|
||||||
|
y: evt.clientY
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragMove = (evt) => {
|
||||||
|
|
||||||
|
if(dragInfo.dragging) {
|
||||||
|
|
||||||
|
const offset = {
|
||||||
|
x: evt.clientX - dragInfo.position.x,
|
||||||
|
y: evt.clientY - dragInfo.position.y
|
||||||
|
};
|
||||||
|
|
||||||
|
const dragNodes = simulationNodes.map((simNode, index) => {
|
||||||
|
if(simNode.id === dragInfo.nodeId) {
|
||||||
|
return ({
|
||||||
|
...simNode,
|
||||||
|
x: simNode.x + offset.x,
|
||||||
|
y: simNode.y + offset.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ({
|
||||||
|
...simNode
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
nodes: dragNodes
|
||||||
|
});
|
||||||
|
|
||||||
|
dragInfo.position = {
|
||||||
|
x: evt.clientX,
|
||||||
|
y: evt.clientY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd = (evt) => {
|
||||||
|
dragInfo = {
|
||||||
|
dragging: false,
|
||||||
|
nodeId: null,
|
||||||
|
position: null
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const renderedNodes = nodesData.map((n, index) => (
|
const renderedNodes = nodesData.map((n, index) => (
|
||||||
<GraphNode
|
<GraphNode
|
||||||
key={index}
|
key={index}
|
||||||
data={n}
|
data={n}
|
||||||
index={index}
|
index={index}
|
||||||
size={nodeSize}
|
size={nodeSize}
|
||||||
|
onDragStart={onDragStart}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -147,7 +215,13 @@ class TopologyGraph extends React.Component {
|
|||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSvg>
|
<StyledSvg
|
||||||
|
onMouseMove={onDragMove}
|
||||||
|
onTouchMove={onDragMove}
|
||||||
|
onMouseUp={onDragEnd}
|
||||||
|
onTouchEnd={onDragEnd}
|
||||||
|
onTouchCancel={onDragEnd}
|
||||||
|
>
|
||||||
<g>
|
<g>
|
||||||
{renderedNodes}
|
{renderedNodes}
|
||||||
</g>
|
</g>
|
||||||
|
Loading…
Reference in New Issue
Block a user