refactor: isolate the calculated data from original data model

This commit is contained in:
Yanyan-Wang 2018-07-23 13:39:03 +08:00
parent 2d5e3bac1a
commit 206cc0a492
9 changed files with 303 additions and 231 deletions

View File

@ -11,6 +11,7 @@
<script src="../build/plugin.tool.mapper.js"></script>
<script src="../build/plugin.tool.fisheye.js"></script>
<script src="../build/plugin.util.extractSubgraph.js"></script>
<!-- <script src="../build/plugin.util.maxSpanningForest.js"></script> -->
<script src="../build/plugin.tool.highlightSubgraph.js"></script>
<script src="../build/plugin.edge.quadraticCurve.js"></script>
<script src="../build/plugin.tool.minimap.js"></script>
@ -94,10 +95,13 @@
const highlighter = new Highlighter();
const textDisplay = new G6.Plugins['tool.textDisplay']();
const Util = G6.Util;
graph = new G6.Graph({
id: 'mountNode', // dom id
fitView: 'cc',
plugins: [new Plugin({
plugins: [
new Plugin({
max_iteration: 600,
kg: 10,
prev_overlapping: true
@ -135,6 +139,7 @@
}
});
graph.read(data);
// Util.maxSpanningForest(graph, {});
const edges = graph.getEdges();
for (let i = 0; i < edges.length; i += 1) {
@ -190,7 +195,6 @@
});
});
const Util = G6.Util;
function clickMenu(ev) {
let type = 'in';

View File

@ -1,201 +0,0 @@
// console.log('aa');
// onmessage = function(event) {
// const {
// nodes,
// edges,
// kr,
// kg,
// mode,
// prev_overlapping,
// dissuade_hubs,
// barnes_hut,
// ks,
// ksmax,
// tao,
// center,
// widths
// } = event.data.params;
// let {
// pre_Forces,
// Forces,
// iter,
// prevo_iter,
// kr_prime,
// start,
// end,
// estart,
// eend
// } = event.data;
// const size = nodes.length;
// const esize = edges.length;
// for (let i = start; i < end; i += 1) {
// pre_Forces[2 * i] = Forces[2 * i];
// pre_Forces[2 * i + 1] = Forces[2 * i + 1];
// Forces[2 * i] = 0;
// Forces[2 * i + 1] = 0;
// }
// // // attractive forces, existing on every actual edge
// Forces = getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths, estart, eend);
// // // // repulsive forces and Gravity, existing on every node pair
// // // // if prev_overlapping, using the no-optimized method in the last prevo_iter instead.
// // if (barnes_hut && ((prev_overlapping && iter > prevo_iter) || !prev_overlapping)) {
// // Forces = getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, bodies);
// // } else {
// Forces = getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths, start, end);
// // }
// // // update the positions
// // const res = updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao);
// // nodes = res[0];
// // SG = res[1];
// // iter -= 1;
// };
// function getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths, estart, eend) {
// for (let i = estart; i < eend; i += 1) {
// // const source_node = graph.find(edges[i].source).getModel();
// // const target_node = graph.find(edges[i].target).getModel();
// let source_node;
// let target_node;
// let source_idx;
// let target_idx;
// for (let j = 0; j < size; j += 1) {
// if (nodes[j].id === edges[i].source) {
// source_node = nodes[j];
// source_idx = j;
// } else if (nodes[j].id === edges[i].target) {
// target_node = nodes[j];
// target_idx = j;
// }
// }
// let dir = [ target_node.x - source_node.x, target_node.y - source_node.y ];
// let eucli_dis = Math.hypot(dir[0], dir[1]);
// eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
// dir[0] = dir[0] / eucli_dis;
// dir[1] = dir[1] / eucli_dis;
// // the force
// if (prev_overlapping && iter < prevo_iter) eucli_dis = eucli_dis - widths[source_idx] - widths[target_idx];
// let Fa1 = eucli_dis;
// let Fa2 = Fa1;
// if (mode === 'linlog') {
// Fa1 = Math.log(1 + eucli_dis);
// Fa2 = Fa1;
// }
// if (dissuade_hubs) {
// Fa1 = eucli_dis / source_node.degree;
// Fa2 = eucli_dis / target_node.degree;
// }
// if (prev_overlapping && iter < prevo_iter && eucli_dis <= 0) {
// Fa1 = 0;
// Fa2 = 0;
// } else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) {
// Fa1 = eucli_dis;
// Fa2 = eucli_dis;
// }
// Forces[2 * source_node.index] += Fa1 * dir[0];
// Forces[2 * target_node.index] -= Fa2 * dir[0];
// Forces[2 * source_node.index + 1] += Fa1 * dir[1];
// Forces[2 * target_node.index + 1] -= Fa2 * dir[1];
// dir = null;
// }
// return Forces;
// }
// function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths, start, end) {
// for (let i = start; i < end; i += 1) {
// for (let j = i + 1; j < size; j += 1) {
// let dir = [nodes[j].x - nodes[i].x, nodes[j].y - nodes[i].y];
// let eucli_dis = Math.hypot(dir[0], dir[1]);
// eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
// dir[0] = dir[0] / eucli_dis;
// dir[1] = dir[1] / eucli_dis;
// if (prev_overlapping && iter < prevo_iter) eucli_dis = eucli_dis - widths[i] - widths[j];
// let Fr = kr * (nodes[i].degree + 1) * (nodes[j].degree + 1) / eucli_dis;
// if (prev_overlapping && iter < prevo_iter && eucli_dis < 0) {
// Fr = kr_prime * (nodes[i].degree + 1) * (nodes[j].degree + 1);
// } else if (prev_overlapping && iter < prevo_iter && eucli_dis === 0) {
// Fr = 0;
// } else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) {
// Fr = kr * (nodes[i].degree + 1) * (nodes[j].degree + 1) / eucli_dis;
// }
// Forces[2 * i] -= Fr * dir[0];
// Forces[2 * j] += Fr * dir[0];
// Forces[2 * i + 1] -= Fr * dir[1];
// Forces[2 * j + 1] += Fr * dir[1];
// dir = null;
// }
// // gravity
// let dir = [nodes[i].x - center.x, nodes[i].y - center.y];
// const eucli_dis = Math.hypot(dir[0], dir[1]);
// dir[0] = dir[0] / eucli_dis;
// dir[1] = dir[1] / eucli_dis;
// const Fg = kg * (nodes[i].degree + 1);
// Forces[2 * i] -= Fg * dir[0];
// Forces[2 * i + 1] -= Fg * dir[1];
// dir = null;
// }
// return Forces;
// }
// // function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, ct, bodies) {
// // let minx = 9e10,
// // maxx = -9e10,
// // miny = 9e10,
// // maxy = -9e10;
// // for (let i = 0; i < size; i += 1) {
// // bodies[i].setPos(nodes[i].x, nodes[i].y);
// // if (nodes[i].x >= maxx) maxx = nodes[i].x;
// // if (nodes[i].x <= minx) minx = nodes[i].x;
// // if (nodes[i].y >= maxy) maxy = nodes[i].y;
// // if (nodes[i].y <= miny) miny = nodes[i].y;
// // }
// // let width = Math.max(maxx - minx, maxy - miny);
// // let quad_params = {
// // xmid: (maxx + minx) / 2,
// // ymid: (maxy + miny) / 2,
// // length: width,
// // mass_center: ct,
// // mass: size
// // };
// // let quad = new Quad(quad_params);
// // let quad_tree = new QuadTree(quad);
// // // build the tree, insert the nodes(quads) into the tree
// // for (let i = 0; i < size; i += 1) {
// // if (bodies[i].in(quad)) quad_tree.insert(bodies[i]);
// // }
// // // update the repulsive forces and the gravity.
// // for (let i = 0; i < size; i += 1) {
// // bodies[i].resetForce();
// // quad_tree.updateForce(bodies[i]);
// // Forces[2 * i] -= bodies[i].fx;
// // Forces[2 * i + 1] -= bodies[i].fy;
// // // gravity
// // let dir = [nodes[i].x - ct.x, nodes[i].y - ct.y];
// // let eucli_dis = Math.hypot(dir[0], dir[1]);
// // eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
// // dir[0] = dir[0] / eucli_dis;
// // dir[1] = dir[1] / eucli_dis;
// // let Fg = kg * (nodes[i].degree + 1);
// // Forces[2 * i] -= Fg * dir[0];
// // Forces[2 * i + 1] -= Fg * dir[1];
// // eucli_dis = null;
// // Fg = null;
// // dir = null;
// // }
// // quad_params = null;
// // quad = null;
// // quad_tree = null;
// // width = null;
// // return Forces;
// // }

View File

@ -24,9 +24,14 @@ onmessage = function(event) {
let SG = 0;
const bodies = [];
const degrees = [];
const idmap = {};
for (let i = 0; i < size; i += 1) {
nodes[i].index = i;
nodes[i].degree = 0;
idmap[nodes[i].id] = i;
degrees[i] = 0;
// nodes[i].index = i;
// nodes[i].degree = 0;
nodes[i].x = Math.random() * 1000;
nodes[i].y = Math.random() * 1000;
}
@ -42,8 +47,10 @@ onmessage = function(event) {
}
// // const node1 = graph.find(edges[i].source).getModel();
// // const node2 = graph.find(edges[i].target).getModel();
nodes[node1.index].degree += 1;
nodes[node2.index].degree += 1;
// nodes[node1.index].degree += 1;
// nodes[node2.index].degree += 1;
degrees[idmap[node1.id]] += 1;
degrees[idmap[node2.id]] += 1;
}
const kr_prime = 100;
@ -62,7 +69,7 @@ onmessage = function(event) {
ry: nodes[i].y,
mass: 1,
G: kr,
degree: nodes[i].degree
degree: degrees[i]
};
bodies[i] = new Body(params);
params = null;
@ -77,16 +84,16 @@ onmessage = function(event) {
Forces[2 * i + 1] = 0;
}
// // attractive forces, existing on every actual edge
Forces = getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths);
Forces = getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths, idmap, degrees);
// // // repulsive forces and Gravity, existing on every node pair
// // // if prev_overlapping, using the no-optimized method in the last prevo_iter instead.
if (barnes_hut && ((prev_overlapping && iter > prevo_iter) || !prev_overlapping)) {
Forces = getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, bodies);
Forces = getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, bodies, degrees);
} else {
Forces = getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths);
Forces = getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths, degrees);
}
// // update the positions
const res = updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao);
const res = updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao, degrees);
nodes = res[0];
SG = res[1];
iter -= 1;
@ -94,7 +101,7 @@ onmessage = function(event) {
self.postMessage(nodes);
};
function getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths) {
function getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths, idmap, degrees) {
for (let i = 0; i < esize; i += 1) {
// const source_node = graph.find(edges[i].source).getModel();
// const target_node = graph.find(edges[i].target).getModel();
@ -125,8 +132,10 @@ function getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hub
Fa2 = Fa1;
}
if (dissuade_hubs) {
Fa1 = eucli_dis / source_node.degree;
Fa2 = eucli_dis / target_node.degree;
// Fa1 = eucli_dis / source_node.degree;
// Fa2 = eucli_dis / target_node.degree;
Fa1 = eucli_dis / degrees[source_idx];
Fa2 = eucli_dis / degrees[target_idx];
}
if (prev_overlapping && iter < prevo_iter && eucli_dis <= 0) {
Fa1 = 0;
@ -135,16 +144,20 @@ function getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hub
Fa1 = eucli_dis;
Fa2 = eucli_dis;
}
Forces[2 * source_node.index] += Fa1 * dir[0];
Forces[2 * target_node.index] -= Fa2 * dir[0];
Forces[2 * source_node.index + 1] += Fa1 * dir[1];
Forces[2 * target_node.index + 1] -= Fa2 * dir[1];
// Forces[2 * source_node.index] += Fa1 * dir[0];
// Forces[2 * target_node.index] -= Fa2 * dir[0];
// Forces[2 * source_node.index + 1] += Fa1 * dir[1];
// Forces[2 * target_node.index + 1] -= Fa2 * dir[1];
Forces[2 * idmap[source_node.id]] += Fa1 * dir[0];
Forces[2 * idmap[target_node.id]] -= Fa2 * dir[0];
Forces[2 * idmap[source_node.id] + 1] += Fa1 * dir[1];
Forces[2 * idmap[target_node.id] + 1] -= Fa2 * dir[1];
dir = null;
}
return Forces;
}
function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths) {
function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths, degrees) {
for (let i = 0; i < size; i += 1) {
for (let j = i + 1; j < size; j += 1) {
let dir = [ nodes[j].x - nodes[i].x, nodes[j].y - nodes[i].y ];
@ -155,14 +168,14 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
if (prev_overlapping && iter < prevo_iter) eucli_dis = eucli_dis - widths[i] - widths[j];
let Fr = kr * (nodes[i].degree + 1) * (nodes[j].degree + 1) / eucli_dis;
let Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucli_dis;
if (prev_overlapping && iter < prevo_iter && eucli_dis < 0) {
Fr = kr_prime * (nodes[i].degree + 1) * (nodes[j].degree + 1);
Fr = kr_prime * (degrees[i] + 1) * (degrees[j] + 1);
} else if (prev_overlapping && iter < prevo_iter && eucli_dis === 0) {
Fr = 0;
} else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) {
Fr = kr * (nodes[i].degree + 1) * (nodes[j].degree + 1) / eucli_dis;
Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucli_dis;
}
Forces[2 * i] -= Fr * dir[0];
Forces[2 * j] += Fr * dir[0];
@ -176,7 +189,7 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
const eucli_dis = Math.hypot(dir[0], dir[1]);
dir[0] = dir[0] / eucli_dis;
dir[1] = dir[1] / eucli_dis;
const Fg = kg * (nodes[i].degree + 1);
const Fg = kg * (degrees[i] + 1);
Forces[2 * i] -= Fg * dir[0];
Forces[2 * i + 1] -= Fg * dir[1];
dir = null;
@ -184,7 +197,7 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
return Forces;
}
function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, ct, bodies) {
function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, ct, bodies, degrees) {
let minx = 9e10,
maxx = -9e10,
miny = 9e10,
@ -226,7 +239,7 @@ function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuad
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
dir[0] = dir[0] / eucli_dis;
dir[1] = dir[1] / eucli_dis;
let Fg = kg * (nodes[i].degree + 1);
let Fg = kg * (degrees[i] + 1);
Forces[2 * i] -= Fg * dir[0];
Forces[2 * i + 1] -= Fg * dir[1];
@ -241,7 +254,7 @@ function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuad
return Forces;
}
function updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao) {
function updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao, degrees) {
let swgns = [];
let trans = [];
// swg(G) and tra(G)
@ -260,8 +273,8 @@ function updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao) {
swgns[i] = minus_norm;
trans[i] = add_norm / 2;
swgG += (nodes[i].degree + 1) * swgns[i];
traG += (nodes[i].degree + 1) * trans[i];
swgG += (degrees[i] + 1) * swgns[i];
traG += (degrees[i] + 1) * trans[i];
}
let pre_SG = SG;

View File

@ -1,7 +1,9 @@
## Fisheye
Fisheye is a magnifying lens for graph exploration. We inplement 'Graphical Fisheye Views' with polar coordinate system: ftp://ftp.cs.brown.edu/pub/techreports/93/cs93-40.pdf
params:
- radius: the radius of the fisheye lens. Default: 200.
- d: the magnification factor of the fisheye lens. Default: 1.
## use

View File

@ -2,6 +2,15 @@
Highlight a subgraph and weaken the rest of the graph.
interface:
- highlightSubgraph(hl_items)
hightlight a subgraph
params:
- hl_items: the items which will be highlighted
- restoreGraph()
restore the graph to the un-lighlighted style.
## use
simple use.

View File

@ -11,7 +11,6 @@ const Size = require('@antv/g2/src/component/legend/size');
const Attr = require('@antv/attr');
const Util = G6.Util;
const Scale = require('@antv/scale');
class Plugin {
constructor(itemType, dim, channel, range, otherCfg) {
Util.mix(this, {

View File

@ -0,0 +1,63 @@
/**
* @fileOverview 图分析模版
* @author huangtonger@aliyun.com
* 保留字段:
* node.vx, node.vy, node.x, node.y
* node.to, node.from
* node.visited, node.edges, node.links
* edge.isTreeEdgeedge.lineWidth
* 可配置字段:
* node.rank 分层权重
* node.label 节点标签
*/
const G6 = require('@antv/g6');
const Forest = require('./maxSpanningForest');
const Layout = require('../layout.forceAtlas2/layout');
const Util = G6.Util;
const maxSpanningForest = {
span(graph, layoutCfg) {
// class Plugin {
// constructor(options) {
const layout = {
auto: 'once', // true false once
processer: new Layout({
kr: 50,
kg: 8.0,
mode: 'common',
prev_overlapping: true,
dissuade_hubs: false,
max_iteration: 1000,
barnes_hut: true,
ks: 0.1,
ksmax: 10,
tao: 0.1,
...layoutCfg
})
};
graph.on('beforeinit', () => {
const graph_layout = graph.get('layout');
if (!graph_layout) {
graph.set('layout', layout);
}
});
graph.on('beforerender', () => {
const data = graph.getSource();
const {
nodes,
edges
} = data;
const forest = Forest(nodes, edges);
forest.edges.forEach(edge => {
edge.isTreeEdge = true;
});
graph.addFilter(item => {
return !item.isEdge || item.getModel().isTreeEdge;
});
});
}
};
Util.mix(Util, maxSpanningForest);

View File

@ -0,0 +1,112 @@
/**
* @fileOverview 最大生成森林
* @author huangtonger@aliyun.com
*/
const G6 = require('@antv/g6');
const { Util } = G6;
const maxSpanningTree = require('./maxSpanningTree');
function maxSpanningForest(nodes, edges) {
const connectedSubsets = [];
const forest = {
nodes: [],
edges: [],
maxRankNode: null
};
const nodeMap = {};
let maxRank = -Infinity;
Util.each(nodes, node => {
node.links = [];
node.edges = [];
nodeMap[node.id] = node;
if (Util.isNil(node.weight)) {
node.weight = 1;
}
if (Util.isNil(node.rank)) {
if (node.weight) {
node.rank = node.weight;
} else {
node.rank = 1;
}
}
});
Util.each(edges, (edge, i) => {
const source = nodeMap[edge.source];
const target = nodeMap[edge.target];
source.links.push(nodes.indexOf(target));
target.links.push(nodes.indexOf(source));
source.edges.push(i);
target.edges.push(i);
if (Util.isNil(edge.weight)) {
edge.weight = 1;
}
});
Util.each(nodes, (node, i) => {
if (!node.visited) {
connectedSubsets.push(getConnectedSubset(i, nodes, edges));
}
});
Util.each(nodes, node => {
delete node.links;
delete node.edges;
delete node.visited;
});
Util.each(connectedSubsets, connectedSubset => {
const tree = maxSpanningTree(connectedSubset);
const root = connectedSubset.root;
forest.nodes = forest.nodes.concat(tree.nodes);
forest.edges = forest.edges.concat(tree.edges);
if (root.rank > maxRank) {
maxRank = root.rank;
forest.maxRankNode = root;
}
});
return forest;
}
// 获取连通子集
function getConnectedSubset(start, nodes, edges) {
const connectedSubset = {
nodes: [],
edges: [],
root: null
};
const edgeMap = {};
const subEdges = [];
let maxRank = -Infinity;
dfs(start, nodes, index => {
const node = nodes[index];
if (node.rank > maxRank) {
maxRank = node.rank;
connectedSubset.root = node;
}
connectedSubset.nodes.push(node);
connectedSubset.edges = connectedSubset.edges.concat(node.edges);
});
// 边索引去重
Util.each(connectedSubset.edges, edgeIndex => {
if (!edgeMap[edgeIndex]) {
subEdges.push(edges[edgeIndex]);
edgeMap[edgeIndex] = true;
}
});
connectedSubset.edges = subEdges;
return connectedSubset;
}
// 深度优先遍历算法
function dfs(start, nodes, callback) {
const listToExplore = [ start ];
nodes[ start ].visited = true;
callback(start);
while (listToExplore.length > 0) {
const nodeIndex = listToExplore.pop();
nodes[ nodeIndex ].links.forEach(function(childIndex) {
if (!nodes[ childIndex ].visited) {
nodes[ childIndex ].visited = true;
callback(childIndex);
listToExplore.push(childIndex);
}
});
}
}
module.exports = maxSpanningForest;

View File

@ -0,0 +1,71 @@
/**
* @fileOverview 基于 prim 获取有向图最大生成树 算法
* https://zh.wikipedia.org/wiki/%E6%99%AE%E6%9E%97%E5%A7%86%E7%AE%97%E6%B3%95
* @author huangtonger@aliyun.com
*/
const G6 = require('@antv/g6');
const { Util } = G6;
let treeNodes = [];
let treeEdges = [];
function maxSpanningTree(connectedSubset) {
const nodes = connectedSubset.nodes;
const edges = connectedSubset.edges;
const root = connectedSubset.root;
const nodeMap = {};
Util.each(nodes, node => {
node.to = [];
node.from = [];
nodeMap[node.id] = node;
});
Util.each(edges, edge => {
const source = nodeMap[edge.source];
const target = nodeMap[edge.target];
source.to.push(edge);
target.from.push(edge);
});
treeNodes = [ root ];
treeEdges = [];
while (treeNodes.length !== nodes.length) {
let maxWeight = -Infinity;
let maxEdge;
let direct = 'target';
Util.each(treeNodes, treeNode => {
Util.each(treeNode.to, edge => {
if (treeNodes.indexOf(nodeMap[edge[direct]]) === -1) {
if (edge.weight > maxWeight) {
maxWeight = edge.weight;
maxEdge = edge;
}
}
});
});
if (!maxEdge) {
direct = 'source';
Util.each(treeNodes, treeNode => {
Util.each(treeNode.from, edge => {
if (treeNodes.indexOf(nodeMap[edge[direct]]) === -1) {
if (edge.weight > maxWeight) {
maxWeight = edge.weight;
maxEdge = edge;
}
}
});
});
}
treeNodes.push(nodeMap[maxEdge[direct]]);
treeEdges.push(maxEdge);
}
// 清除标记
Util.each(nodes, node => {
delete node.to;
delete node.from;
});
return {
nodes: treeNodes,
edges: treeEdges
};
}
module.exports = maxSpanningTree;