From 4855cdb6264bbb1c2e243e9ada473e208ac16814 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 3 Jul 2019 03:22:42 +0000 Subject: [PATCH] Split classes out of Graph.js --- Graph.js | 172 +------------------------------------------------- GraphGroup.js | 42 ++++++++++++ GraphLink.js | 32 ++++++++++ GraphNode.js | 93 +++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 169 deletions(-) create mode 100644 GraphGroup.js create mode 100644 GraphLink.js create mode 100644 GraphNode.js diff --git a/Graph.js b/Graph.js index 1685f7f..da3e17e 100644 --- a/Graph.js +++ b/Graph.js @@ -130,175 +130,9 @@ class Graph { } } -class GraphNode { - constructor() { - this.links = []; - this.linksIn = []; - this.groups = new Set(); - this.affinity = []; - this.pageRank = 0; - this.subgraph = null; - } - - incPageRank(visited) { - if (visited.has(this)) { - // Loop detection - return; - } - ++this.pageRank; - visited.add(this); - for (let to of this.links) { - to.incPageRank(visited); - } - visited.delete(this); - } - - setSubgraph(subgraph, nodes) { - // Flood fill - if (this.subgraph !== null) { - return; - } - this.subgraph = subgraph; - nodes.delete(this); - for (let to of this.links) { - to.setSubgraph(subgraph, nodes); - } - for (let from of this.linksIn) { - from.setSubgraph(subgraph, nodes); - } - for (let group of this.groups.values()) { - for (let node of group.nodes) { - node.setSubgraph(subgraph, nodes); - } - } - } - - setAffinity(nodes) { - const INF = 999999; - for (let node of nodes) { - // Weak affinity full mesh - // Keep unassociated subgroups together - this.addAffinity(node, d => d); - - // Keep one space between subgraphs - if (this.subgraph != node.subgraph) { - this.addAffinity(node, d => d < 1.5 ? -INF : 0); - } - - // Keep one space around groups - if (this.groups.size && !intersects(this.groups, node.groups)) { - node.addAffinity(this, d => d < 1.5 ? -INF : 0); - } - } - for (let to of this.links) { - // Stronger affinity for links - // Prefer to move toward the target instance - this.addAffinity(to, d => d < 1.5 ? -INF : d * 11); - to.addAffinity(this, d => d < 1.5 ? -INF : d * 9); - } - for (let group of this.groups.values()) { - for (let node of group.nodes) { - this.addAffinity(node, d => d * 100); - } - } - } - - addAffinity(node, distanceToWeight) { - if (this == node) { - return; - } - this.affinity.push({ - node: node, - distanceToWeight: distanceToWeight, - }); - } - - static process(item, soft=false) { - if (item.label == '') { - return null; - } - let node = new GraphNode(); - node.label = item.label; - node.soft = soft; - return node; - } -} - -class GraphGroup { - constructor() { - this.nodes = []; - } - - resolve(nodesByLabel) { - for (let label of this.nodeLabels.values()) { - for (let node of nodesByLabel.get(label)) { - this.nodes.push(node); - node.groups.add(this); - } - } - } - - setPageRank() { - // 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 node of this.nodes) { - maxRank = Math.max(maxRank, node.pageRank); - } - for (let node of this.nodes) { - node.pageRank = maxRank; - } - } - - static process(item) { - let group = new GraphGroup(); - group.label = item.label; - group.nodeLabels = new Set(); - for (let member of item.members) { - if (member.label == '') { - continue; - } - group.nodeLabels.add(member.label); - } - if (group.nodeLabels.size == 0) { - return null; - } - return group; - } -} - -class GraphLink { - constructor() { - } - - resolve(nodesByLabel) { - this.from = nodesByLabel.get(this.fromLabel); - this.to = nodesByLabel.get(this.toLabel); - for (let from of this.from) { - for (let to of this.to) { - from.links.push(to); - to.linksIn.push(from); - } - } - } - - setPageRank() { - for (let to of this.to) { - to.incPageRank(new Set()); - } - } - - static process(item) { - let link = new GraphLink(); - link.label = item.label; - link.fromLabel = item.from.label; - link.toLabel = item.to.label; - if (link.fromLabel == '' || link.toLabel == '') { - return null; - } - return link; - } -} + + + function onmessage(def) { new Graph(def); diff --git a/GraphGroup.js b/GraphGroup.js new file mode 100644 index 0000000..a1ac6ba --- /dev/null +++ b/GraphGroup.js @@ -0,0 +1,42 @@ +class GraphGroup { + constructor() { + this.nodes = []; + } + + resolve(nodesByLabel) { + for (let label of this.nodeLabels.values()) { + for (let node of nodesByLabel.get(label)) { + this.nodes.push(node); + node.groups.add(this); + } + } + } + + setPageRank() { + // 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 node of this.nodes) { + maxRank = Math.max(maxRank, node.pageRank); + } + for (let node of this.nodes) { + node.pageRank = maxRank; + } + } + + static process(item) { + let group = new GraphGroup(); + group.label = item.label; + group.nodeLabels = new Set(); + for (let member of item.members) { + if (member.label == '') { + continue; + } + group.nodeLabels.add(member.label); + } + if (group.nodeLabels.size == 0) { + return null; + } + return group; + } +} diff --git a/GraphLink.js b/GraphLink.js new file mode 100644 index 0000000..de240f1 --- /dev/null +++ b/GraphLink.js @@ -0,0 +1,32 @@ +class GraphLink { + constructor() { + } + + resolve(nodesByLabel) { + this.from = nodesByLabel.get(this.fromLabel); + this.to = nodesByLabel.get(this.toLabel); + for (let from of this.from) { + for (let to of this.to) { + from.links.push(to); + to.linksIn.push(from); + } + } + } + + setPageRank() { + for (let to of this.to) { + to.incPageRank(new Set()); + } + } + + static process(item) { + let link = new GraphLink(); + link.label = item.label; + link.fromLabel = item.from.label; + link.toLabel = item.to.label; + if (link.fromLabel == '' || link.toLabel == '') { + return null; + } + return link; + } +} diff --git a/GraphNode.js b/GraphNode.js new file mode 100644 index 0000000..6694aa9 --- /dev/null +++ b/GraphNode.js @@ -0,0 +1,93 @@ +class GraphNode { + constructor() { + this.links = []; + this.linksIn = []; + this.groups = new Set(); + this.affinity = []; + this.pageRank = 0; + this.subgraph = null; + } + + incPageRank(visited) { + if (visited.has(this)) { + // Loop detection + return; + } + ++this.pageRank; + visited.add(this); + for (let to of this.links) { + to.incPageRank(visited); + } + visited.delete(this); + } + + setSubgraph(subgraph, nodes) { + // Flood fill + if (this.subgraph !== null) { + return; + } + this.subgraph = subgraph; + nodes.delete(this); + for (let to of this.links) { + to.setSubgraph(subgraph, nodes); + } + for (let from of this.linksIn) { + from.setSubgraph(subgraph, nodes); + } + for (let group of this.groups.values()) { + for (let node of group.nodes) { + node.setSubgraph(subgraph, nodes); + } + } + } + + setAffinity(nodes) { + const INF = 999999; + for (let node of nodes) { + // Weak affinity full mesh + // Keep unassociated subgroups together + this.addAffinity(node, d => d); + + // Keep one space between subgraphs + if (this.subgraph != node.subgraph) { + this.addAffinity(node, d => d < 1.5 ? -INF : 0); + } + + // Keep one space around groups + if (this.groups.size && !intersects(this.groups, node.groups)) { + node.addAffinity(this, d => d < 1.5 ? -INF : 0); + } + } + for (let to of this.links) { + // Stronger affinity for links + // Prefer to move toward the target instance + this.addAffinity(to, d => d < 1.5 ? -INF : d * 11); + to.addAffinity(this, d => d < 1.5 ? -INF : d * 9); + } + for (let group of this.groups.values()) { + for (let node of group.nodes) { + this.addAffinity(node, d => d * 100); + } + } + } + + addAffinity(node, distanceToWeight) { + if (this == node) { + return; + } + this.affinity.push({ + node: node, + distanceToWeight: distanceToWeight, + }); + } + + static process(item, soft=false) { + if (item.label == '') { + return null; + } + let node = new GraphNode(); + node.label = item.label; + node.soft = soft; + return node; + } +}