Broken, but checkpoint editor/graph/layout split

This commit is contained in:
Ian Gulliver
2019-07-03 18:13:11 +00:00
parent 4cc0b09d9b
commit b91b92eef3
8 changed files with 197 additions and 450 deletions

View File

@@ -70,10 +70,8 @@ class Architype {
onmessage(serialized);
localStorage.setItem('currentState', JSON.stringify(serialized));
this.graph_ = this.buildGraph();
this.buildGrid(this.graph_);
this.updateTargets(this.graph_);
this.fixSizes(this.graph_.nodes);
//this.updateTargets(this.graph_);
//this.fixSizes(this.graph_.nodes);
}
onKeyDown(e) {
@@ -137,300 +135,6 @@ class Architype {
}
}
buildGraph() {
let graph = {
nodesByLabel: new Map(),
nodesByPageRank: new Map(),
nodesByPos: new Map(),
nodesBySubgraph: new Map(),
groups: [],
links: [],
nodes: [],
};
// Order here is important, as each step carefully builds on data
// constructed by the previous
this.buildGraphInt(graph, this.editor_.getEntries());
this.trimSoftNodes(graph);
this.processLinks(graph);
this.processGroups(graph);
this.manifestNodes(graph);
this.setPageRank(graph);
this.bucketByPageRank(graph);
this.bucketBySubgraph(graph);
this.setInitialPositions(graph);
this.setAffinity(graph);
while (this.iterate(graph));
this.fixOrigin(graph);
return graph;
}
buildGraphInt(graph, entries) {
for (let entry of entries) {
if (entry instanceof EditorNode) {
this.buildGraphNode(graph, entry);
} else if (entry instanceof EditorGroup) {
this.buildGraphGroup(graph, entry);
} else if (entry instanceof EditorLink) {
this.buildGraphLink(graph, entry);
}
}
}
buildGraphNode(graph, node) {
node.clear();
if (node.getLabel() == '') {
return;
}
let targets = graph.nodesByLabel.get(node.getLabel());
if (!targets) {
targets = [];
graph.nodesByLabel.set(node.getLabel(), targets);
}
targets.push(node);
}
buildGraphGroup(graph, group) {
group.clear();
graph.groups.push(group);
this.buildGraphInt(graph, group.getNodes());
}
buildGraphLink(graph, link) {
link.clear();
graph.links.push(link);
this.buildGraphInt(graph, [link.getFrom(), link.getTo()]);
}
trimSoftNodes(graph) {
for (let entries of graph.nodesByLabel.values()) {
for (let i = entries.length - 1; i >= 0 && entries.length > 1; --i) {
if (entries[i] instanceof EditorNode && entries[i].isSoft()) {
entries.splice(i, 1);
}
}
}
}
processLinks(graph) {
for (let link of graph.links) {
// Re-resolve each from/to reference by label, so we skip soft nodes and
// handle multiple objects with the same label
link.from = graph.nodesByLabel.get(link.getFrom().getLabel()) || [];
link.to = graph.nodesByLabel.get(link.getTo().getLabel()) || [];
for (let from of link.from) {
for (let to of link.to) {
from.links.push(to);
}
}
}
}
processGroups(graph) {
for (let group of graph.groups) {
group.nodes = [];
for (let member of group.getNodes()) {
let nodes = graph.nodesByLabel.get(member.getLabel()) || [];
for (let node of nodes) {
group.nodes.push(node);
node.groups.push(group);
}
}
}
}
manifestNodes(graph) {
for (let entries of graph.nodesByLabel.values()) {
for (let entry of entries) {
if (entry instanceof EditorNode) {
graph.nodes.push(entry);
}
}
}
}
setPageRank(graph) {
for (let link of graph.links) {
for (let to of link.to) {
this.setPageRankInt(to, new Set());
}
}
for (let group of graph.groups) {
// All members of a group get the rank of the maximum member, so the
// initial positions will put them all near each other
let maxRank = 0;
for (let member of group.nodes) {
maxRank = Math.max(maxRank, member.pageRank);
}
for (let member of group.nodes) {
member.pageRank = maxRank;
}
}
}
setPageRankInt(node, visited) {
if (visited.has(node)) {
// Loop detection
return;
}
++node.pageRank;
visited.add(node);
for (let out of node.links) {
this.setPageRankInt(out, visited);
}
visited.delete(node);
}
bucketByPageRank(graph) {
for (let node of graph.nodes) {
let bucket = graph.nodesByPageRank.get(node.pageRank);
if (!bucket) {
bucket = [];
graph.nodesByPageRank.set(node.pageRank, bucket);
}
bucket.push(node);
}
let cmp = (a, b) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
};
for (let bucket of graph.nodesByPageRank.values()) {
bucket.sort(cmp);
}
}
bucketBySubgraph(graph) {
let nodes = new Set();
let ranks = Array.from(graph.nodesByPageRank.keys());
ranks.sort((a, b) => a - b);
for (let rank of ranks) {
for (let node of graph.nodesByPageRank.get(rank)) {
nodes.add(node);
}
}
for (let subgraph = 0; nodes.size; ++subgraph) {
let node = nodes.values().next().value;
let subgraphArr = [];
graph.nodesBySubgraph.set(subgraph, subgraphArr);
this.recurseSubgraph(subgraph, subgraphArr, node, nodes);
}
}
recurseSubgraph(subgraph, subgraphArr, node, nodes) {
if (node.subgraph !== null) {
return;
}
node.subgraph = subgraph;
subgraphArr.push(node);
nodes.delete(node);
for (let to of node.links) {
this.recurseSubgraph(subgraph, subgraphArr, to, nodes);
}
for (let group of node.groups) {
for (let member of group.nodes) {
this.recurseSubgraph(subgraph, subgraphArr, member, nodes);
}
}
}
setInitialPositions(graph) {
const SPACING = 4;
let maxRankNodes = 0;
for (let nodes of graph.nodesByPageRank.values()) {
maxRankNodes = Math.max(maxRankNodes, nodes.length);
}
let ranks = Array.from(graph.nodesByPageRank.keys());
ranks.sort((a, b) => a - b);
for (let r = 0; r < ranks.length; ++r) {
let nodes = graph.nodesByPageRank.get(ranks[r]);
for (let n = 0; n < nodes.length; ++n) {
let node = nodes[n];
let pos = [
r * SPACING,
Math.floor((nodes.length / 2) * SPACING) + (n * SPACING) +
(node.subgraph * SPACING * maxRankNodes),
];
node.moveTo(graph, pos);
}
}
}
fixOrigin(graph) {
let min = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
let max = [Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];
for (let node of graph.nodes) {
for (let i of [0, 1]) {
min[i] = Math.min(min[i], node.pos[i]);
max[i] = Math.max(max[i], node.pos[i]);
}
}
// Offset is negative minimum, e.g min -1 means +1 to all values
for (let node of graph.nodes) {
for (let i of [0, 1]) {
node.pos[i] -= min[i];
}
}
graph.size = [
max[0] - min[0] + 1,
max[1] - min[1] + 1,
];
}
setAffinity(graph) {
const INF = 999999;
for (let node of graph.nodes) {
for (let other of graph.nodes) {
// Weak affinity full mesh
// Keep unassociated subgroups together
this.addAffinity(node, other, d => d);
if (node.subgraph != other.subgraph) {
this.addAffinity(node, other, d => d < 1.5 ? -INF : 0);
}
}
for (let to of node.links) {
// Stronger affinity for links
// Prefer to move toward the target instance
this.addAffinity(node, to, d => d < 1.5 ? -INF : d * 11);
this.addAffinity(to, node, d => d < 1.5 ? -INF : d * 9);
}
for (let group of node.groups) {
for (let member of group.nodes) {
// Even stronger affinity for groups
// Other nodes will reference this one and take care of the full
// group mesh
this.addAffinity(node, member, d => d * 100);
}
let members = new Set(group.nodes);
for (let other of graph.nodes) {
if (members.has(other)) {
continue;
}
// Nodes not in this group run away
this.addAffinity(other, node, d => d < 1.5 ? -INF : 0);
}
}
}
}
addAffinity(node, other, func) {
if (node == other) {
return;
}
node.affinity.push({
node: other,
distanceToWeight: func,
});
}
buildGrid(graph) {
this.grid_.innerHTML = '';
@@ -453,67 +157,6 @@ class Architype {
return lines;
}
iterate(graph) {
let nodes = Array.from(graph.nodes);
this.sortByMostTension(nodes);
for (let group of graph.groups) {
nodes.push(group.getCollection());
}
for (let subgraph of graph.nodesBySubgraph.values()) {
nodes.push(new Collection(subgraph));
}
let newOffset = null;
let newTension = this.getTotalTension(nodes);
for (let node of nodes) {
let origPos = node.pos;
let offsets = new Map();
let addOffset = (x, y) => {
if (!offsets.has([x, y].toString())) {
offsets.set([x, y].toString(), [x, y]);
}
};
for (let dir of [-1, 0, 1]) {
addOffset(Math.sign(node.vec[0]), dir);
addOffset(dir, Math.sign(node.vec[1]));
}
for (let offset of offsets.values()) {
if (node.offsetCollides(graph, offset)) {
continue;
}
node.savePos();
node.moveBy(graph, offset);
let testTension = this.getTotalTension(nodes);
node.restorePos(graph);
if (testTension < newTension) {
newOffset = offset;
newTension = testTension;
}
}
if (newOffset) {
node.moveBy(graph, newOffset);
return true;
}
}
return false;
}
getTotalTension(nodes) {
let total = 0;
for (let node of nodes) {
node.setTension();
total += node.tension;
}
return total;
}
sortByMostTension(nodes) {
for (let node of nodes) {
node.setTension();
}
nodes.sort((a, b) => b.tension - a.tension);
}
drawGridNodes(graph) {
for (let node of graph.nodes) {
node.gridElem = document.createElement('div');
@@ -539,7 +182,6 @@ class Architype {
}
}
<!--# include file="Collection.js" -->
<!--# include file="Editor.js" -->
<!--# include file="EditorEntryBase.js" -->