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.setData('dim', 30, 'current')
Selected.Nodes.push(node)
Engine.setNodeSleeping(node.getData('body_id'), true)
},
deselectAllNodes: function() {
var l = Selected.Nodes.length
@ -40,7 +39,6 @@ const Control = {
// remove the node
Selected.Nodes.splice(
Selected.Nodes.indexOf(node), 1)
Engine.setNodeSleeping(node.getData('body_id'), false)
},
deleteSelected: function() {
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 $jit from '../patched/JIT'
import { getLayoutForData } from '../ConvoAlgo'
import Active from './Active'
import Create from './Create'
@ -11,58 +12,46 @@ import JIT from './JIT'
import Visualize from './Visualize'
const Engine = {
focusBody: null,
newNodeConstraint: null,
newNodeBody: Bodies.circle(Mouse.newNodeCoords.x, Mouse.newNodeCoords.y, 1),
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 => {
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) {
Engine.setFocusNode(Engine.findFocusNode(Visualize.mGraph.graph.nodes))
Engine.runLayout(true)
}
}
Engine.runner = Matter.Runner.run(Engine.engine)
},
endActiveMap: () => {
Engine.runner && Runner.stop(Engine.runner)
Matter.Engine.clear(Engine.engine)
},
setNodePos: (id, x, y) => {
const body = Composite.get(Engine.engine.world, id, 'body')
Body.setPosition(body, { x, y })
Body.setVelocity(body, Vector.create(0, 0))
Body.setAngularVelocity(body, 0)
Body.setAngle(body, 0)
},
setNodeSleeping: (id, isSleeping) => {
const body = Composite.get(Engine.engine.world, id, 'body')
Sleeping.set(body, isSleeping)
if (!isSleeping) {
Body.setVelocity(body, Vector.create(0, 0))
Body.setAngularVelocity(body, 0)
Body.setAngle(body, 0)
}
runLayout: init => {
const synapses = DataModel.Synapses.map(s => s.attributes)
const topics = DataModel.Topics.map(t => t.attributes)
const focalNodeId = Create.newSynapse.focusNode.getData('topic').id
const focalCoords = init ? { x: 0, y: 0 } : Create.newSynapse.focusNode.pos
const layout = getLayoutForData(topics, synapses, focalNodeId, focalCoords)
Visualize.mGraph.graph.eachNode(n => {
let calculatedCoords = layout[n.id]
if (!calculatedCoords) {
calculatedCoords = {x: 0, y: 0}
}
const endPos = new $jit.Complex(calculatedCoords.x, calculatedCoords.y)
n.setPos(endPos, 'end')
})
Visualize.mGraph.animate({
modes: ['linear'],
transition: $jit.Trans.Elastic.easeOut,
duration: 200,
onComplete: () => {}
})
},
addNode: node => {
let body = Bodies.circle(node.pos.x, node.pos.y, 100)
body.node_id = node.id
node.setData('body_id', body.id)
World.addBody(Engine.engine.world, body)
//Engine.runLayout()
},
removeNode: node => {
//Engine.runLayout()
},
findFocusNode: nodes => {
return last(sortBy(values(nodes), n => new Date(n.getData('topic').get('created_at'))))
@ -70,50 +59,19 @@ const Engine = {
setFocusNode: node => {
if (!Active.Mapper) return
Create.newSynapse.focusNode = node
const body = Composite.get(Engine.engine.world, node.getData('body_id'), 'body')
Engine.focusBody = body
let constraint
if (Engine.newNodeConstraint) {
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
Mouse.focusNodeCoords = node.pos
Mouse.newNodeCoords = {
x: node.x + 200,
y: node.y
}
Create.newSynapse.updateForm()
Create.newTopic.position()
},
addEdge: edge => {
const bodyA = Composite.get(Engine.engine.world, edge.nodeFrom.getData('body_id'), 'body')
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)
Engine.runLayout()
},
removeEdge: synapse => {
},
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()
removeEdge: edge => {
//Engine.runLayout()
}
}

View file

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

View file

@ -97,7 +97,7 @@ const Visualize = {
})
//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(endPos, 'end')
n.setPos(endPos, 'current')