running algo each time an edge is added

This commit is contained in:
Connor Turland 2017-02-01 17:47:45 +00:00
parent fc2604376a
commit 08d2cbb00d
5 changed files with 238 additions and 82 deletions

View file

@ -0,0 +1,201 @@
// an array of synapses
// an array of topics
// a focal node
/*
step 1
generate an object/array that represents the intended layout
step 2
generate x,y coordinates for every topic in the layout object
step 3
set end states for every topic
Step 4
animate
*/
// synapses = [{ topic1_id: 4, topic2_id: 5, direction: 'from-to' }]
export const generateLayoutObject = (topics, synapses, focalTopicId) => {
let layout = [] // will be the final output
const usedTopics = {} // will store the topics that have been placed into islands
let newRoot
let currentTopic
const addParentsAndChildren = (topic) => {
if (!topic.id) return topic
usedTopics[topic.id] = true
topic.parents = []
topic.children = []
let filteredParentIds = synapses.filter(synapse => {
return synapse.topic2_id === topic.id
&& !usedTopics[synapse.topic1_id]
&& synapse.category === 'from-to'
}).map(synapse => synapse.topic1_id)
let filteredChildrenIds = synapses.filter(synapse => {
return synapse.topic1_id === topic.id
&& !usedTopics[synapse.topic2_id]
&& synapse.category === 'from-to'
}).map(synapse => synapse.topic2_id)
filteredParentIds.forEach(parentId => {
let parent = {
id: parentId
}
topic.parents.push(addParentsAndChildren(parent))
})
filteredChildrenIds.forEach(childId => {
let child = {
id: childId
}
topic.children.push(addParentsAndChildren(child))
})
return topic
}
// start with the focal node, and build its island
currentTopic = topics.find(t => t.id === focalTopicId)
if (!currentTopic) {
console.log('you didnt pass a valid focalTopicId')
return layout
}
newRoot = {
id: currentTopic.id
}
layout.push(addParentsAndChildren(newRoot))
//
// go through the the topics again, and build the island for the first topic that isn't
// yet in the usedTopics object (in any island). recurse
topics.forEach(topic => {
if (topic && topic.id && !usedTopics[topic.id]) {
newRoot = {
id: topic.id
}
layout.push(addParentsAndChildren(newRoot))
}
})
return layout
}
export const generateObjectCoordinates = (layoutObject, focalTopicId, focalCoords) => {
const coords = {}
const traverseIsland = (island, func, parent, child) => {
func(island, parent, child)
if (island.parents) {
island.parents.forEach(p => traverseIsland(p, func, null, island))
}
if (island.children) {
island.children.forEach(c => traverseIsland(c, func, island, null))
}
}
const positionTopic = (topic, parent, child) => {
if (topic.id === focalTopicId) {
// set the focalCoord to be what it already was
coords[topic.id] = focalCoords
} else if (!parent && !child) {
coords[topic.id] = {x: 0, y: 150}
} else if (parent) {
coords[topic.id] = {
x: coords[parent.id].x + 250,
y: coords[parent.id].y
}
} else if (child) {
coords[topic.id] = {
x: coords[child.id].x - 250,
y: coords[child.id].y
}
}
}
// lay all of them out as if there were no other ones
layoutObject.forEach(island => {
traverseIsland(island, positionTopic)
})
// calculate the bounds of each island
// reposition the islands according to the bounds
return coords
}
export const getLayoutForData = (topics, synapses, focalTopicId, focalCoords) => {
return generateObjectCoordinates(generateLayoutObject(topics, synapses, focalTopicId), focalTopicId, focalCoords)
}
// if we've placed a node into an island, we need to NOT place it in any other islands
// Every node should only appear in one island
// the pseudo-focal node
// the top level array represents islands
// every island has some sort of 'focal' node
/*
var example = [
// the island that contains the focal node
{
id: 21,
parents: [
{
id: 25,
parents: []
},
{
id: 25,
parents: []
}
],
children: [{
id: 26,
children: []
}]
},
// all other islands should not contain children on the top level node
{
id: 21,
// parents may contain children
parents: [
{
id: 100,
parents: [
{
id: 101,
parents: [],
children: [
{
id: 103,
children: []
}
]
}
]
},
{
id: 102,
parents: []
}
]
},
{
id: 21,
parents: []
},
]
*/

View file

@ -23,7 +23,6 @@ const Control = {
node.selected = true node.selected = true
node.setData('dim', 30, 'current') node.setData('dim', 30, 'current')
Selected.Nodes.push(node) Selected.Nodes.push(node)
Engine.setNodeSleeping(node.getData('body_id'), true)
}, },
deselectAllNodes: function() { deselectAllNodes: function() {
var l = Selected.Nodes.length var l = Selected.Nodes.length
@ -40,7 +39,6 @@ const Control = {
// remove the node // remove the node
Selected.Nodes.splice( Selected.Nodes.splice(
Selected.Nodes.indexOf(node), 1) Selected.Nodes.indexOf(node), 1)
Engine.setNodeSleeping(node.getData('body_id'), false)
}, },
deleteSelected: function() { deleteSelected: function() {
if (!Active.Map) return if (!Active.Map) return

View file

@ -1,7 +1,8 @@
import Matter, { Vector, Sleeping, World, Constraint, Composite, Runner, Common, Body, Bodies, Events } from 'matter-js' //import Matter, { Vector, Sleeping, World, Constraint, Composite, Runner, Common, Body, Bodies, Events } from 'matter-js'
import { last, sortBy, values } from 'lodash' import { last, sortBy, values } from 'lodash'
import $jit from '../patched/JIT' import $jit from '../patched/JIT'
import { getLayoutForData } from '../ConvoAlgo'
import Active from './Active' import Active from './Active'
import Create from './Create' import Create from './Create'
@ -11,58 +12,46 @@ import JIT from './JIT'
import Visualize from './Visualize' import Visualize from './Visualize'
const Engine = { const Engine = {
focusBody: null,
newNodeConstraint: null,
newNodeBody: Bodies.circle(Mouse.newNodeCoords.x, Mouse.newNodeCoords.y, 1),
init: (serverData) => { init: (serverData) => {
Engine.engine = Matter.Engine.create()
Events.on(Engine.engine, 'afterUpdate', Engine.callUpdate)
if (!serverData.ActiveMapper) Engine.engine.world.gravity.scale = 0
else {
Engine.engine.world.gravity.y = 0
Engine.engine.world.gravity.x = -1
Body.setStatic(Engine.newNodeBody, true)
}
}, },
run: init => { run: init => {
if (init) { if (init) {
if (Active.Mapper) World.addBody(Engine.engine.world, Engine.newNodeBody)
Visualize.mGraph.graph.eachNode(Engine.addNode)
DataModel.Synapses.each(s => Engine.addEdge(s.get('edge')))
if (Active.Mapper && Object.keys(Visualize.mGraph.graph.nodes).length) { if (Active.Mapper && Object.keys(Visualize.mGraph.graph.nodes).length) {
Engine.setFocusNode(Engine.findFocusNode(Visualize.mGraph.graph.nodes)) Engine.setFocusNode(Engine.findFocusNode(Visualize.mGraph.graph.nodes))
Engine.runLayout(true)
} }
} }
Engine.runner = Matter.Runner.run(Engine.engine)
}, },
endActiveMap: () => { endActiveMap: () => {
Engine.runner && Runner.stop(Engine.runner)
Matter.Engine.clear(Engine.engine)
}, },
setNodePos: (id, x, y) => { runLayout: init => {
const body = Composite.get(Engine.engine.world, id, 'body') const synapses = DataModel.Synapses.map(s => s.attributes)
Body.setPosition(body, { x, y }) const topics = DataModel.Topics.map(t => t.attributes)
Body.setVelocity(body, Vector.create(0, 0)) const focalNodeId = Create.newSynapse.focusNode.getData('topic').id
Body.setAngularVelocity(body, 0) const focalCoords = init ? { x: 0, y: 0 } : Create.newSynapse.focusNode.pos
Body.setAngle(body, 0) const layout = getLayoutForData(topics, synapses, focalNodeId, focalCoords)
}, Visualize.mGraph.graph.eachNode(n => {
setNodeSleeping: (id, isSleeping) => { let calculatedCoords = layout[n.id]
const body = Composite.get(Engine.engine.world, id, 'body') if (!calculatedCoords) {
Sleeping.set(body, isSleeping) calculatedCoords = {x: 0, y: 0}
if (!isSleeping) { }
Body.setVelocity(body, Vector.create(0, 0)) const endPos = new $jit.Complex(calculatedCoords.x, calculatedCoords.y)
Body.setAngularVelocity(body, 0) n.setPos(endPos, 'end')
Body.setAngle(body, 0) })
} Visualize.mGraph.animate({
modes: ['linear'],
transition: $jit.Trans.Elastic.easeOut,
duration: 200,
onComplete: () => {}
})
}, },
addNode: node => { addNode: node => {
let body = Bodies.circle(node.pos.x, node.pos.y, 100) //Engine.runLayout()
body.node_id = node.id
node.setData('body_id', body.id)
World.addBody(Engine.engine.world, body)
}, },
removeNode: node => { removeNode: node => {
//Engine.runLayout()
}, },
findFocusNode: nodes => { findFocusNode: nodes => {
return last(sortBy(values(nodes), n => new Date(n.getData('topic').get('created_at')))) return last(sortBy(values(nodes), n => new Date(n.getData('topic').get('created_at'))))
@ -70,50 +59,19 @@ const Engine = {
setFocusNode: node => { setFocusNode: node => {
if (!Active.Mapper) return if (!Active.Mapper) return
Create.newSynapse.focusNode = node Create.newSynapse.focusNode = node
const body = Composite.get(Engine.engine.world, node.getData('body_id'), 'body') Mouse.focusNodeCoords = node.pos
Engine.focusBody = body Mouse.newNodeCoords = {
let constraint x: node.x + 200,
if (Engine.newNodeConstraint) { y: node.y
Engine.newNodeConstraint.bodyA = body
}
else {
constraint = Constraint.create({
bodyA: body,
bodyB: Engine.newNodeBody,
length: JIT.ForceDirected.graphSettings.levelDistance,
stiffness: 0.2
})
World.addConstraint(Engine.engine.world, constraint)
Engine.newNodeConstraint = constraint
} }
Create.newSynapse.updateForm()
Create.newTopic.position()
}, },
addEdge: edge => { addEdge: edge => {
const bodyA = Composite.get(Engine.engine.world, edge.nodeFrom.getData('body_id'), 'body') Engine.runLayout()
const bodyB = Composite.get(Engine.engine.world, edge.nodeTo.getData('body_id'), 'body')
let constraint = Constraint.create({
bodyA,
bodyB,
length: JIT.ForceDirected.graphSettings.levelDistance,
stiffness: 0.2
})
edge.setData('constraint_id', constraint.id)
World.addConstraint(Engine.engine.world, constraint)
}, },
removeEdge: synapse => { removeEdge: edge => {
//Engine.runLayout()
},
callUpdate: () => {
Engine.engine.world.bodies.forEach(b => {
const node = Visualize.mGraph.graph.getNode(b.node_id)
const newPos = new $jit.Complex(b.position.x, b.position.y)
node && node.setPos(newPos, 'current')
})
if (Active.Mapper) {
if (Engine.focusBody) Mouse.focusNodeCoords = Engine.focusBody.position
Create.newSynapse.updateForm()
Create.newTopic.position()
}
Visualize.mGraph.plot()
} }
} }

View file

@ -1024,7 +1024,6 @@ const JIT = {
n.pos.setp(theta, rho) n.pos.setp(theta, rho)
} else { } else {
n.pos.setc(x, y) n.pos.setc(x, y)
Engine.setNodePos(n.getData('body_id'), x, y)
} }
if (Active.Map) { if (Active.Map) {

View file

@ -97,7 +97,7 @@ const Visualize = {
}) })
//const startPos = new $jit.Complex(0, 0) //const startPos = new $jit.Complex(0, 0)
const endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')) const endPos = new $jit.Complex(0, 0)
//n.setPos(startPos, 'start') //n.setPos(startPos, 'start')
//n.setPos(endPos, 'end') //n.setPos(endPos, 'end')
n.setPos(endPos, 'current') n.setPos(endPos, 'current')