Broken, but checkpoint editor/graph/layout split
This commit is contained in:
362
Architype.js
362
Architype.js
@@ -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" -->
|
||||
|
||||
@@ -31,9 +31,6 @@ class EditorEntryBase extends ListenUtils {
|
||||
this.elem_.xArchObj = null;
|
||||
}
|
||||
|
||||
clear() {
|
||||
}
|
||||
|
||||
wantFocus() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,6 @@ class EditorGroup extends EditorEntryBase {
|
||||
return lines;
|
||||
}
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
}
|
||||
|
||||
getNodes() {
|
||||
return this.nodes_.getEntries();
|
||||
}
|
||||
@@ -72,10 +68,6 @@ class EditorGroup extends EditorEntryBase {
|
||||
return this.elem_;
|
||||
}
|
||||
|
||||
getCollection() {
|
||||
return new Collection(this.nodes);
|
||||
}
|
||||
|
||||
onInputKeyDown(e) {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
|
||||
@@ -55,10 +55,6 @@ class EditorLink extends EditorEntryBase {
|
||||
return ret;
|
||||
};
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
}
|
||||
|
||||
getFrom() {
|
||||
return this.nodes_.getEntries()[0];
|
||||
}
|
||||
|
||||
@@ -33,15 +33,6 @@ class EditorNode extends EditorEntryBase {
|
||||
return ['"' + this.id + '" [label="' + this.getLabel() + '"];'];
|
||||
}
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
this.links = [];
|
||||
this.groups = [];
|
||||
this.affinity = [];
|
||||
this.pageRank = 0;
|
||||
this.subgraph = null;
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
return this.input_.value;
|
||||
}
|
||||
@@ -70,56 +61,6 @@ class EditorNode extends EditorEntryBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
setTension() {
|
||||
this.vec = [0, 0];
|
||||
this.tension = 0;
|
||||
for (let aff of this.affinity) {
|
||||
let vec = [], vecsum = 0;
|
||||
for (let i of [0, 1]) {
|
||||
vec[i] = aff.node.pos[i] - this.pos[i];
|
||||
vecsum += Math.abs(vec[i]);
|
||||
};
|
||||
let distance = Math.sqrt(Math.pow(vec[0], 2) + Math.pow(vec[1], 2));
|
||||
let weight = aff.distanceToWeight(distance);
|
||||
for (let i of [0, 1]) {
|
||||
this.vec[i] += (weight * vec[i]) / vecsum;
|
||||
}
|
||||
this.tension += Math.abs(weight);
|
||||
}
|
||||
}
|
||||
|
||||
offsetToPos(offset) {
|
||||
return [
|
||||
this.pos[0] + offset[0],
|
||||
this.pos[1] + offset[1],
|
||||
];
|
||||
}
|
||||
|
||||
offsetCollides(graph, offset) {
|
||||
let newPos = this.offsetToPos(offset);
|
||||
return graph.nodesByPos.get(newPos.toString());
|
||||
}
|
||||
|
||||
moveBy(graph, offset) {
|
||||
this.moveTo(graph, this.offsetToPos(offset));
|
||||
}
|
||||
|
||||
moveTo(graph, pos) {
|
||||
if (this.pos) {
|
||||
graph.nodesByPos.delete(this.pos.toString());
|
||||
}
|
||||
this.pos = pos;
|
||||
graph.nodesByPos.set(this.pos.toString(), this);
|
||||
}
|
||||
|
||||
savePos() {
|
||||
this.savedPos = this.pos;
|
||||
}
|
||||
|
||||
restorePos(graph) {
|
||||
this.moveTo(graph, this.savedPos);
|
||||
}
|
||||
|
||||
onInput() {
|
||||
this.input_.setAttribute('data-arch-value', this.input_.value);
|
||||
}
|
||||
|
||||
123
Layout.js
123
Layout.js
@@ -1,10 +1,15 @@
|
||||
class Layout {
|
||||
constructor(graph) {
|
||||
this.nodesByPos = new Map();
|
||||
|
||||
this.graph_ = graph;
|
||||
|
||||
this.nodesByPos_ = new Map();
|
||||
this.nodesByGraphNode_ = new Map();
|
||||
|
||||
this.setInitialPositions();
|
||||
this.resolveAffinity();
|
||||
this.resolveGroups();
|
||||
while (this.iterate());
|
||||
this.fixOrigin();
|
||||
}
|
||||
|
||||
setInitialPositions() {
|
||||
@@ -26,13 +31,119 @@ class Layout {
|
||||
Math.floor((nodes.length / 2) * SPACING) + (n * SPACING) +
|
||||
(node.subgraph * SPACING * maxRankNodes),
|
||||
];
|
||||
node.pos = pos;
|
||||
this.setNodePos(node, pos);
|
||||
this.nodesByGraphNode_.set(
|
||||
node,
|
||||
new LayoutNode(node, this.nodesByPos_, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNodePos(node, pos) {
|
||||
this.nodesByPos.set(pos.toString(), node);
|
||||
nodesFromGraphNodes(graphNodes) {
|
||||
let nodes = [];
|
||||
for (let graphNode of graphNodes) {
|
||||
nodes.push(this.nodesByGraphNode_.get(graphNode));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
resolveGroups() {
|
||||
this.groups_ = [];
|
||||
for (let group of this.graph_.groups) {
|
||||
let nodes = this.nodesFromGraphNodes(group.nodes);
|
||||
this.groups_.push(new LayoutGroup(nodes));
|
||||
}
|
||||
for (let subgraph of this.graph_.nodesBySubgraph.values()) {
|
||||
let nodes = this.nodesFromGraphNodes(subgraph);
|
||||
this.groups_.push(new LayoutGroup(nodes));
|
||||
}
|
||||
}
|
||||
|
||||
resolveAffinity() {
|
||||
for (let node of this.nodesByGraphNode_.values()) {
|
||||
node.resolveAffinity(this.nodesByGraphNode_);
|
||||
}
|
||||
}
|
||||
|
||||
iterate() {
|
||||
let objects = Array.from(this.nodesByPos_.values());
|
||||
objects.push(...this.groups_);
|
||||
this.setTension(objects);
|
||||
this.sortByMostTension(objects);
|
||||
|
||||
let newOffset = null;
|
||||
let newTension = this.getTotalTension(objects);
|
||||
for (let obj of objects) {
|
||||
let offsets = new Map();
|
||||
let addOffset = (x, y) => {
|
||||
if (x == 0 && y == 0) {
|
||||
return;
|
||||
}
|
||||
offsets.set([x, y].toString(), [x, y]);
|
||||
};
|
||||
for (let dir of [-1, 0, 1]) {
|
||||
addOffset(Math.sign(obj.vec[0]), dir);
|
||||
addOffset(dir, Math.sign(obj.vec[1]));
|
||||
}
|
||||
for (let offset of offsets.values()) {
|
||||
if (obj.offsetCollides(offset)) {
|
||||
continue;
|
||||
}
|
||||
obj.savePos();
|
||||
obj.moveBy(offset);
|
||||
let testTension = this.getTotalTension(objects);
|
||||
obj.restorePos();
|
||||
if (testTension < newTension) {
|
||||
newOffset = offset;
|
||||
newTension = testTension;
|
||||
}
|
||||
}
|
||||
if (newOffset) {
|
||||
obj.moveBy(newOffset);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setTension(objects) {
|
||||
for (let obj of objects) {
|
||||
obj.setTension();
|
||||
}
|
||||
}
|
||||
|
||||
sortByMostTension(objects) {
|
||||
objects.sort((a, b) => b.tension - a.tension);
|
||||
}
|
||||
|
||||
getTotalTension(objects) {
|
||||
let total = 0;
|
||||
for (let obj of objects) {
|
||||
total += obj.tension;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
fixOrigin() {
|
||||
let min = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
|
||||
let max = [Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];
|
||||
for (let node of this.nodesByPos_.values()) {
|
||||
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 this.nodesByPos_.values()) {
|
||||
for (let i of [0, 1]) {
|
||||
node.pos[i] -= min[i];
|
||||
}
|
||||
}
|
||||
this.size = [
|
||||
max[0] - min[0] + 1,
|
||||
max[1] - min[1] + 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
<!--# include file="LayoutGroup.js" -->
|
||||
<!--# include file="LayoutNode.js" -->
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
class Collection {
|
||||
class LayoutGroup {
|
||||
constructor(nodes) {
|
||||
this.nodes = new Set(nodes);
|
||||
this.tension = 0;
|
||||
}
|
||||
|
||||
setTension() {
|
||||
// Groups don't track tension, since we always want to sort last for total
|
||||
// tension
|
||||
this.vec = [0, 0];
|
||||
this.tension = 0;
|
||||
for (let node of this.nodes.values()) {
|
||||
node.setTension();
|
||||
for (let i of [0, 1]) {
|
||||
this.vec[i] += node.vec[i];
|
||||
};
|
||||
this.tension += node.tension;
|
||||
}
|
||||
}
|
||||
|
||||
offsetCollides(graph, offset) {
|
||||
offsetCollides(offset) {
|
||||
for (let node of this.nodes.values()) {
|
||||
let other = node.offsetCollides(graph, offset);
|
||||
let other = node.offsetCollides(offset);
|
||||
if (other && !this.nodes.has(other)) {
|
||||
return other;
|
||||
}
|
||||
@@ -31,20 +32,20 @@ class Collection {
|
||||
}
|
||||
}
|
||||
|
||||
restorePos(graph) {
|
||||
restorePos() {
|
||||
for (let node of this.nodes.values()) {
|
||||
node.restorePos(graph);
|
||||
node.restorePos();
|
||||
}
|
||||
}
|
||||
|
||||
moveBy(graph, offset) {
|
||||
moveBy(offset) {
|
||||
let nodes = new Set(this.nodes.values());
|
||||
while (nodes.size) {
|
||||
for (let node of nodes) {
|
||||
if (node.offsetCollides(graph, offset)) {
|
||||
if (node.offsetCollides(offset)) {
|
||||
continue;
|
||||
}
|
||||
node.moveBy(graph, offset);
|
||||
node.moveBy(offset);
|
||||
nodes.delete(node);
|
||||
}
|
||||
}
|
||||
67
LayoutNode.js
Normal file
67
LayoutNode.js
Normal file
@@ -0,0 +1,67 @@
|
||||
class LayoutNode {
|
||||
constructor(graphNode, nodesByPos, pos) {
|
||||
this.graphNode_ = graphNode;
|
||||
this.nodesByPos_ = nodesByPos;
|
||||
this.pos = pos;
|
||||
|
||||
this.nodesByPos_.set(this.pos.toString(), this);
|
||||
}
|
||||
|
||||
resolveAffinity(nodesByGraphNode) {
|
||||
this.affinity_ = [];
|
||||
for (let aff of this.graphNode_.affinity) {
|
||||
this.affinity_.push({
|
||||
node: nodesByGraphNode.get(aff.node),
|
||||
distanceToWeight: aff.distanceToWeight,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTension() {
|
||||
this.vec = [0, 0];
|
||||
this.tension = 0;
|
||||
for (let aff of this.affinity_) {
|
||||
let vec = [], vecsum = 0;
|
||||
for (let i of [0, 1]) {
|
||||
vec[i] = aff.node.pos[i] - this.pos[i];
|
||||
vecsum += Math.abs(vec[i]);
|
||||
};
|
||||
let distance = Math.sqrt(Math.pow(vec[0], 2) + Math.pow(vec[1], 2));
|
||||
let weight = aff.distanceToWeight(distance);
|
||||
for (let i of [0, 1]) {
|
||||
this.vec[i] += (weight * vec[i]) / vecsum;
|
||||
}
|
||||
this.tension += Math.abs(weight);
|
||||
}
|
||||
}
|
||||
|
||||
offsetToPos(offset) {
|
||||
return [
|
||||
this.pos[0] + offset[0],
|
||||
this.pos[1] + offset[1],
|
||||
];
|
||||
}
|
||||
|
||||
offsetCollides(offset) {
|
||||
let newPos = this.offsetToPos(offset);
|
||||
return this.nodesByPos_.get(newPos.toString());
|
||||
}
|
||||
|
||||
moveTo(pos) {
|
||||
this.nodesByPos_.delete(this.pos.toString());
|
||||
this.pos = pos;
|
||||
this.nodesByPos_.set(this.pos.toString(), this);
|
||||
}
|
||||
|
||||
moveBy(offset) {
|
||||
this.moveTo(this.offsetToPos(offset));
|
||||
}
|
||||
|
||||
savePos() {
|
||||
this.savedPos_ = this.pos;
|
||||
}
|
||||
|
||||
restorePos() {
|
||||
this.moveTo(this.savedPos_);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user