Adjust animation on topology

This commit is contained in:
JUDIT GRESKOVITS 2017-02-14 11:30:57 +00:00
parent 43569aeb43
commit 705aa32e73
4 changed files with 230 additions and 81 deletions

View File

@ -24,6 +24,7 @@
"lodash.first": "^3.0.0", "lodash.first": "^3.0.0",
"lodash.flatten": "^4.4.0", "lodash.flatten": "^4.4.0",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.isfunction": "^3.0.8", "lodash.isfunction": "^3.0.8",
"lodash.isstring": "^4.0.1", "lodash.isstring": "^4.0.1",
"lodash.isundefined": "^3.0.1", "lodash.isundefined": "^3.0.1",

View File

@ -0,0 +1,130 @@
const d3 = require('d3');
const hypotenuse = (a, b) =>
Math.sqrt(a*a + b*b);
const rectRadius = (size) => {
const {
width,
height
} = size;
return Math.round(hypotenuse(width, height)/2);
};
const createSimulation = (
nodes,
links,
nodeSize,
svgSize,
onTick,
onEnd
) => {
// This is not going to work given that as well as the d3 layout stuff, other things might be at play too
// We should pass two objects to the components - one for positioning and one for data
const mappedNodes = nodes.map((node, index) => ({
id: node.id,
index: index
}));
const mappedLinks = links.map((link, index) => ({
...link
}));
const {
width,
height
} = svgSize;
const nodeRadius = rectRadius(nodeSize);
return d3.forceSimulation(mappedNodes)
.force('link', d3.forceLink(mappedLinks).id(d => d.id))
.force('collide', d3.forceCollide(nodeRadius))
.force('center', d3.forceCenter(width/2, height/2))
.on('tick', onTick)
.on('end', onEnd);
};
// TODO we need to kill the previous simulation
const updateSimulation = (
simulation,
nodes,
links,
nextNodes,
nextLinks,
nodeSize,
svgSize,
onTick,
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 node = nodes.reduce((acc, n, i) =>
nextNode.id === n.id ? n : acc ? null : acc);
return node ? {
id: node.id,
fx: node.x,
fy: node.y,
index: index
} : {
id: nextNode.id,
index: index
};
});
const mappedLinks = nextLinks.map((nextLink, index) => {
const link = links.reduce((acc, l, i) =>
nextLink.source === l.source && nextLink.target === l.target ?
l : acc ? null : acc);
return link ? {
...link
} : {
...nextLink
};
});
const {
width,
height
} = svgSize;
const nodeRadius = rectRadius(nodeSize);
return d3.forceSimulation(mappedNodes)
.force('link', d3.forceLink(mappedLinks).id(d => d.id))
.force('collide', d3.forceCollide(nodeRadius))
.force('center', d3.forceCenter(width/2, height/2))
.on('tick', onTick)
.on('end', onEnd);
};
module.exports = {
createSimulation,
updateSimulation
};
/*
const simulation = d3.forceSimulation(dataNodes)
// .alpha(1).alphaDecay(0.1)
// .force('charge', d3.forceManyBody())
.force('link', d3.forceLink(dataLinks)
//.distance(() => linkDistance)
.id(d => d.id))
.force('collide', d3.forceCollide(nodeRadius))
.force('center', d3.forceCenter(1024/2, 860/2))
.on('tick', () => {
console.log('SIMULATION TICK');
console.log('tickCounter = ', tickCounter);
tickCounter++;
this.forceUpdate();
})
.on('end', () => {
console.log('SIMULATION END');
console.log('tickCounter = ', tickCounter);
// this.forceUpdate();
})
*/

View File

@ -19,11 +19,6 @@ const StyledForm = styled.form`
margin: 5px; margin: 5px;
`; `;
const nodeSize = {
width: 180,
height: 156
};
class StoryHelper extends React.Component { class StoryHelper extends React.Component {
constructor(props){ constructor(props){
@ -100,7 +95,7 @@ class StoryHelper extends React.Component {
</Select> </Select>
<Input name='Add service' type='submit' /> <Input name='Add service' type='submit' />
</StyledForm> </StyledForm>
<TopologyGraph data={data} nodeSize={nodeSize} /> <TopologyGraph data={data} />
</div> </div>
); );
} }

View File

@ -1,6 +1,6 @@
const React = require('react'); const React = require('react');
const d3 = require('d3');
const Styled = require('styled-components'); const Styled = require('styled-components');
const GraphSimulation = require('./graph-simulation');
const GraphNode = require('./graph-node'); const GraphNode = require('./graph-node');
const GraphLink = require('./graph-link'); const GraphLink = require('./graph-link');
@ -8,27 +8,25 @@ const {
default: styled default: styled
} = Styled; } = Styled;
const {
createSimulation,
updateSimulation
} = GraphSimulation;
const StyledSvg = styled.svg` const StyledSvg = styled.svg`
width: 1024px; width: 1024px;
height: 860px; height: 860px;
border: 1px solid #ff0000; border: 1px solid #ff0000;
`; `;
/*const nodeSize = { const nodeSize = {
width: 180, width: 180,
height: 156 height: 156
};*/ };
const mapData = (data, withIndex=false) => { const svgSize = {
return data.map((d, index) => { width: 1024,
const r = { height: 860
...d
};
if(withIndex) {
r.index = index;
}
return r;
});
}; };
class TopologyGraph extends React.Component { class TopologyGraph extends React.Component {
@ -36,64 +34,101 @@ class TopologyGraph extends React.Component {
componentWillMount() { componentWillMount() {
const { const {
data, nodes,
nodeSize links
} = this.props; } = this.props.data;
this.setState( const simulation = createSimulation(
this.createSimulation(data, nodeSize) nodes,
links,
nodeSize,
svgSize,
() => this.forceUpdate(),
() => this.forceUpdate()
); );
const n = Math.ceil(
Math.log(
simulation.alphaMin()) / Math.log(
1 - simulation.alphaDecay()));
for (var i = 0; i < n; ++i) {
simulation.tick();
}
this.setState({
simulation: simulation
});
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { // either, we'll have more services
data, // or, we'll have less services
nodeSize // or, data of services had changed =>
} = nextProps; // do shallow check on objects and links, if no change, don't do rerender
// otherwise, redo them bitches = by what I mean to update the simulation
this.setState( // try freezing exisiting ones... then adding another
this.createSimulation(data, nodeSize)
);
}
createSimulation(data, nodeSize) {
const dataNodes = mapData(data.nodes, true);
const dataLinks = mapData(data.links);
const { const {
width, nodes: nextNodes,
height links: nextLinks
} = nodeSize; } = nextProps.data;
const nodeRadius = Math.round(Math.sqrt(width*width + height*height)/2);
// const linkDistance = nodeRadius*2 + 20; const {
// console.log('nodeRadius = ', nodeRadius); nodes,
// console.log('linkDistance = ', linkDistance); links
const simulation = d3.forceSimulation(dataNodes) } = this.props.data;
.force('charge', d3.forceManyBody())
.force('link', d3.forceLink(dataLinks) // this is tmp for the compare above
/*.distance(() => linkDistance)*/ if(nextNodes.length !== nodes.length || nextLinks.length !== links.length) {
.id(d => d.id)) const simulation = this.state.simulation;
.force('collide', d3.forceCollide(nodeRadius)) const nextSimulation = updateSimulation(
.force('center', d3.forceCenter(1024/2, 860/2)) simulation,
.on('tick', () => { nodes,
// console.log('SIMULATION TICK'); links,
this.forceUpdate(); nextNodes,
}) nextLinks,
.on('end', () => { nodeSize,
// console.log('SIMULATION END'); svgSize,
// this.forceUpdate(); () => this.forceUpdate(),
() => this.forceUpdate()
);
this.setState({
simulation: nextSimulation
}); });
return { const n = Math.ceil(
dataNodes, Math.log(
dataLinks, nextSimulation.alphaMin()) / Math.log(
simulation 1 - nextSimulation.alphaDecay())) - 200;
}; for (var i = 0; i < n; ++i) {
nextSimulation.tick();
}
}
} }
renderNodes(nodeSize) { render() {
return this.state.dataNodes.map((n, index) => (
const {
nodes,
links
} = this.props.data;
const simulationNodes = this.state.simulation.nodes();
const nodesData = nodes.map((node, index) => ({
...node,
...simulationNodes.reduce((acc, simNode, index) =>
simNode.id === node.id ? simNode : acc)
}));
const linksData = links.map((link, index) => ({
source: simulationNodes.reduce((acc, simNode, index) =>
simNode.id === link.source ? simNode : acc),
target: simulationNodes.reduce((acc, simNode, index) =>
simNode.id === link.target ? simNode : acc)
}));
const renderedNodes = nodesData.map((n, index) => (
<GraphNode <GraphNode
key={index} key={index}
data={n} data={n}
@ -101,10 +136,8 @@ class TopologyGraph extends React.Component {
size={nodeSize} size={nodeSize}
/> />
)); ));
}
renderLinks(nodeSize) { const renderedLinks = linksData.map((l, index) => (
return this.state.dataLinks.map((l, index) => (
<GraphLink <GraphLink
key={index} key={index}
data={l} data={l}
@ -112,20 +145,14 @@ class TopologyGraph extends React.Component {
nodeSize={nodeSize} nodeSize={nodeSize}
/> />
)); ));
}
render() {
const {
nodeSize
} = this.props;
return ( return (
<StyledSvg> <StyledSvg>
<g> <g>
{this.renderNodes(nodeSize)} {renderedNodes}
</g> </g>
<g> <g>
{this.renderLinks(nodeSize)} {renderedLinks}
</g> </g>
</StyledSvg> </StyledSvg>
); );
@ -136,10 +163,6 @@ TopologyGraph.propTypes = {
data: React.PropTypes.shape({ data: React.PropTypes.shape({
nodes: React.PropTypes.array, nodes: React.PropTypes.array,
links: React.PropTypes.array links: React.PropTypes.array
}),
nodeSize: React.PropTypes.shape({
width: React.PropTypes.number,
height: React.PropTypes.number
}) })
}; };