metamaps--metamaps/frontend/src/ConvoAlgo/index.js

190 lines
No EOL
4.4 KiB
JavaScript

// 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, getParents, getChildren) => {
if (!topic.id) return topic
usedTopics[topic.id] = true
if (getParents) {
topic.parents = []
synapses.filter(synapse => {
return synapse.topic2_id === topic.id
&& !usedTopics[synapse.topic1_id]
&& synapse.category === 'from-to'
})
.map(synapse => synapse.topic1_id)
.forEach(parentId => topic.parents.push(addParentsAndChildren({id: parentId}, true, false)))
}
if (getChildren) {
topic.children = []
synapses.filter(synapse => {
return synapse.topic1_id === topic.id
&& !usedTopics[synapse.topic2_id]
&& synapse.category === 'from-to'
})
.map(synapse => synapse.topic2_id)
.forEach(childId => topic.children.push(addParentsAndChildren({id: childId}, false, true)))
}
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, true, true))
// do the rest
topics.forEach(topic => {
if (topic && topic.id && !usedTopics[topic.id]) {
newRoot = {
id: topic.id
}
layout.push(addParentsAndChildren(newRoot, true, true))
}
})
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: 250}
} else if (parent) {
coords[topic.id] = {
x: coords[parent.id].x + 250,
y: coords[parent.id].y - (parent.id === focalTopicId ? 250 : 0)
}
} 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: []
},
]
*/