feat: adjacency matrix in Algorithm. feat: floydShall shortest path in Algorithm.

This commit is contained in:
Yanyan-Wang 2020-06-23 19:26:18 +08:00 committed by Yanyan Wang
parent 2378d4eac9
commit 76af4f59f2
9 changed files with 737 additions and 6 deletions

View File

@ -126,4 +126,4 @@
"webpack-cli": "^3.3.10",
"worker-loader": "^2.0.0"
}
}
}

View File

@ -0,0 +1,40 @@
import { IGraph } from '../interface/graph';
import { Matrix } from '../types';
const adjMatrix = (graph: IGraph, directed?: boolean) => {
const nodes = graph.getNodes();
const edges = graph.getEdges();
const matrix: Matrix[] = [];
// map node with index in data.nodes
const nodeMap: {
[key: string]: number;
} = {};
if (!nodes) {
throw new Error('invalid nodes data!');
}
if (nodes) {
nodes.forEach((node, i) => {
nodeMap[node.getID()] = i;
const row: number[] = [];
matrix.push(row);
});
}
if (edges) {
edges.forEach(e => {
const model = e.getModel();
const { source, target } = model;
const sIndex = nodeMap[source as string];
const tIndex = nodeMap[target as string];
matrix[sIndex][tIndex] = 1;
if (!directed) {
matrix[tIndex][sIndex] = 1;
}
});
}
return matrix
}
export default adjMatrix

View File

@ -0,0 +1,41 @@
import { IGraph } from '../interface/graph';
import adjMatrix from './adjacent-matrix';
import { Matrix } from '../types';
const floydWarshall = (graph: IGraph, directed?: boolean) => {
let adjacentMatrix = graph.get('adjMatrix');
if (!adjacentMatrix) {
adjacentMatrix = adjMatrix(graph, directed);
}
const dist: Matrix[] = [];
const size = adjacentMatrix.length;
for (let i = 0; i < size; i += 1) {
dist[i] = [];
for (let j = 0; j < size; j += 1) {
if (i === j) {
dist[i][j] = 0;
} else if (adjacentMatrix[i][j] === 0 || !adjacentMatrix[i][j]) {
dist[i][j] = Infinity;
} else {
dist[i][j] = adjacentMatrix[i][j];
}
}
}
// floyd
for (let k = 0; k < size; k += 1) {
for (let i = 0; i < size; i += 1) {
for (let j = 0; j < size; j += 1) {
if (dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
return dist;
}
export default floydWarshall

View File

@ -1,3 +1,7 @@
export { default as depthFirstSearch } from './dfs'
export { default as breadthFirstSearch } from './bfs'
export { default as detectDirectedCycle } from './detect-cycle'
export { default as degree } from './degree'
export { default as adjMatrix } from './adjacent-matrix'
export { default as floydWarshall } from './floydWarshall'

View File

@ -46,6 +46,8 @@ import createDom from '@antv/dom-util/lib/create-dom';
import { plainCombosToTrees, traverseTree, reconstructTree, traverseTreeUp } from '../util/graphic';
import degree from '../algorithm/degree';
import Stack from '../algorithm/structs/stack'
import adjMatrix from '../algorithm/adjacent-matrix';
import floydWarshall from '../algorithm/floydWarshall'
const NODE = 'node';
const SVG = 'svg';
@ -2370,8 +2372,13 @@ export default class Graph extends EventEmitter implements IGraph {
console.warn('The combo to be collapsed does not exist!');
return;
}
const comboModel = combo.getModel();
const itemController: ItemController = this.get('itemController');
itemController.collapseCombo(combo);
comboModel.collapsed = true;
// add virtual edges
const edges = this.getEdges().concat(this.get('vedges'));
@ -2414,6 +2421,7 @@ export default class Graph extends EventEmitter implements IGraph {
const edgeWeightMap = {};
const addedVEdges = [];
edges.forEach(edge => {
if (edge.isVisible() && !edge.getModel().isVEdge) return;
let source = edge.getSource();
let target = edge.getTarget();
if (((cnodes.includes(source) || ccombos.includes(source))
@ -2485,9 +2493,6 @@ export default class Graph extends EventEmitter implements IGraph {
}, false)
});
const itemController: ItemController = this.get('itemController');
itemController.collapseCombo(combo);
comboModel.collapsed = true;
}
/**
@ -2548,6 +2553,7 @@ export default class Graph extends EventEmitter implements IGraph {
const edgeWeightMap = {};
const addedVEdges = {};
edges.forEach(edge => {
if (edge.isVisible() && !edge.getModel().isVEdge) return;
let source = edge.getSource();
let target = edge.getTarget();
let sourceId = source.get('id');
@ -2907,7 +2913,7 @@ export default class Graph extends EventEmitter implements IGraph {
console.warn('请先启用 undo & redo 功能,在实例化 Graph 时候配置 enabledStack: true !')
return
}
const stackData = data ? clone(data) : clone(this.save())
if (stackType === 'redo') {
@ -2926,6 +2932,48 @@ export default class Graph extends EventEmitter implements IGraph {
undoStack: this.undoStack,
redoStack: this.redoStack
})
/**
*
*
* @param {boolean} cache 使
* @param {boolean} directed graph.directed
* @returns {Matrix}
* @memberof IGraph
*/
public getAdjMatrix(cache: boolean = true, directed?: boolean): Number | Object {
if (directed === undefined) directed = this.get('directed');
let currentAdjMatrix = this.get('adjMatrix');
if (!currentAdjMatrix || !cache) {
currentAdjMatrix = adjMatrix(this, directed);
this.set('adjMatrix', currentAdjMatrix);
}
return currentAdjMatrix;
}
/**
*
*
* @param {boolean} cache 使
* @param {boolean} directed graph.directed
* @returns {Matrix}
* @memberof IGraph
*/
public getShortestPathMatrix(cache: boolean = true, directed?: boolean): Number | Object {
if (directed === undefined) directed = this.get('directed');
let currentAdjMatrix = this.get('adjMatrix');
let currentShourtestPathMatrix = this.get('shortestPathMatrix');
if (!currentAdjMatrix || !cache) {
currentAdjMatrix = adjMatrix(this, directed);
this.set('adjMatrix', currentAdjMatrix);
}
if (!currentShourtestPathMatrix || !cache) {
currentShourtestPathMatrix = floydWarshall(this, directed);
this.set('shortestPathMatrix', currentShourtestPathMatrix);
}
return currentShourtestPathMatrix;
}
/**
@ -2936,7 +2984,7 @@ export default class Graph extends EventEmitter implements IGraph {
// 清空栈数据
this.clearStack();
each(this.get('plugins'), plugin => {
plugin.destroyPlugin();
});

View File

@ -13,6 +13,7 @@ import RegisterRectCombo from './component/register-rect-combo';
import RegisterCircleCombo from './component/register-circle-combo';
import Edges from './component/edges';
import DagreCombo from './component/dagre-combo';
import Edges2 from './component/edges2';
export default { title: 'Combo' };
@ -48,4 +49,6 @@ storiesOf('Combo', module)
<Edges />
)).add('dagre combo', () => (
<DagreCombo />
)).add('edges2 ', () => (
<Edges2 />
));

View File

@ -0,0 +1,97 @@
import React, { useEffect } from 'react';
import G6 from '../../../src';
import { IGraph } from '../../../src/interface/graph';
import { GraphData } from '../../../src/types';
let graph: IGraph = null;
const data: GraphData = {
nodes: [{
id: '1',
comboId: '分组1',
x: 100,
y: 100
}, {
id: '2',
comboId: '分组1',
x: 200,
y: 100
}, {
id: '4',
comboId: '分组2',
x: 400,
y: 100
}
],
edges: [
{
source: '分组1',
target: '分组2',
}
],
combos: [
{
id: '分组1',
label: '分组1',
//collapsed: true
},
{
id: '分组2',
label: '分组2',
//collapsed: true,
}
]
};
const Edges2 = () => {
const container = React.useRef();
useEffect(() => {
if (!graph) {
const graph = new G6.Graph({
container: container.current as string | HTMLElement,
width: 1000,
height: 800,
groupByTypes: false,
defaultEdge: {
style: {
endArrow: true
}
},
defaultCombo: {
type: 'rect',
size: [50, 60], // The minimum size of the Combo
padding: [20, 10, 10, 20],
style: {
lineWidth: 3,
},
anchorPoints: [
[0.5, 1],
[0.5, 0],
],
labelCfg: {
refY: 10,
refX: 20,
position: 'top',
},
},
modes: {
default: ['drag-canvas', 'drag-node',
{
type: 'drag-combo',
enableDelegate: true //拖动时禁止合并
}
],
},
});
graph.data(data);
graph.render();
graph.on('combo:click', function (e) {
graph.collapseExpandCombo(e.item);
graph.refreshPositions();
});
}
});
return <div ref={container}></div>;
};
export default Edges2;

View File

@ -0,0 +1,234 @@
import G6, { Algorithm } from '../../../src';
const { adjMatrix } = Algorithm;
const div = document.createElement('div');
div.id = 'container';
document.body.appendChild(div);
const data = {
nodes: [
{
id: 'A',
label: '0'
},
{
id: 'B',
label: '1'
},
{
id: 'C',
label: '2'
},
{
id: 'D',
label: '3'
},
{
id: 'E',
label: '4'
},
{
id: 'F',
label: '5'
},
{
id: 'G',
label: '6'
},
{
id: 'H',
label: '7'
},
],
edges: [
{
source: 'A',
target: 'B',
},
{
source: 'B',
target: 'C',
},
{
source: 'C',
target: 'G',
},
{
source: 'A',
target: 'D',
},
{
source: 'A',
target: 'E',
},
{
source: 'E',
target: 'F',
},
{
source: 'F',
target: 'D',
},
]
}
describe('Adjacency Matrix on graph', () => {
const graph = new G6.Graph({
container: 'container',
width: 500,
height: 500,
defaultEdge: {
style: {
endArrow: true
}
}
})
graph.data(data)
graph.render()
it('get graph adjacency matrix', () => {
const matrix = graph.getAdjMatrix();
expect(Object.keys(matrix).length).toBe(8);
const node0Adj = matrix[0];
expect(node0Adj.length).toBe(5);
expect(node0Adj[0]).toBe(undefined);
expect(node0Adj[1]).toBe(1);
expect(node0Adj[2]).toBe(undefined);
expect(node0Adj[3]).toBe(1);
expect(node0Adj[4]).toBe(1);
});
it('add items and cache', () => {
graph.addItem('node', {
id: 'I',
label: '8'
});
graph.addItem('edge', {
source: 'I',
target: 'A'
});
graph.addItem('edge', {
source: 'C',
target: 'A'
});
// use the cache
const cachedMatrix = graph.getAdjMatrix();
expect(Object.keys(cachedMatrix).length).toBe(8);
const cachedNode0Adj = cachedMatrix[0];
expect(cachedNode0Adj.length).toBe(5);
expect(cachedNode0Adj[0]).toBe(undefined);
expect(cachedNode0Adj[1]).toBe(1);
expect(cachedNode0Adj[2]).toBe(undefined);
expect(cachedNode0Adj[3]).toBe(1);
expect(cachedNode0Adj[4]).toBe(1);
// do not use the cache
const matrix = graph.getAdjMatrix(false);
expect(Object.keys(matrix).length).toBe(9);
const node0Adj = matrix[0];
expect(node0Adj.length).toBe(9);
expect(node0Adj[0]).toBe(undefined);
expect(node0Adj[1]).toBe(1);
expect(node0Adj[2]).toBe(1);
expect(node0Adj[3]).toBe(1);
expect(node0Adj[4]).toBe(1);
expect(node0Adj[5]).toBe(undefined);
expect(node0Adj[6]).toBe(undefined);
expect(node0Adj[7]).toBe(undefined);
expect(node0Adj[8]).toBe(1);
})
it('directed', () => {
// do not use the cache and directed
const matrix = graph.getAdjMatrix(false, true);
expect(Object.keys(matrix).length).toBe(9);
const node0Adj = matrix[0];
expect(node0Adj.length).toBe(5);
expect(node0Adj[0]).toBe(undefined);
expect(node0Adj[1]).toBe(1);
expect(node0Adj[2]).toBe(undefined);
expect(node0Adj[3]).toBe(1);
expect(node0Adj[4]).toBe(1);
const node8Adj = matrix[8];
expect(node8Adj.length).toBe(1);
expect(node8Adj[0]).toBe(1);
graph.destroy();
});
});
describe('Adjacency Matrix by Algorithm', () => {
const graph = new G6.Graph({
container: 'container',
width: 500,
height: 500,
defaultEdge: {
style: {
endArrow: true
}
}
})
graph.data(data)
graph.render()
it('get graph adjacency matrix', () => {
const matrix = adjMatrix(graph);
expect(Object.keys(matrix).length).toBe(8);
const node0Adj = matrix[0];
expect(node0Adj.length).toBe(5);
expect(node0Adj[0]).toBe(undefined);
expect(node0Adj[1]).toBe(1);
expect(node0Adj[2]).toBe(undefined);
expect(node0Adj[3]).toBe(1);
expect(node0Adj[4]).toBe(1);
});
it('add items and cache', () => {
graph.addItem('node', {
id: 'I',
label: '8'
});
graph.addItem('edge', {
source: 'I',
target: 'A'
});
graph.addItem('edge', {
source: 'C',
target: 'A'
});
const matrix = adjMatrix(graph);
expect(Object.keys(matrix).length).toBe(9);
const node0Adj = matrix[0];
expect(node0Adj.length).toBe(9);
expect(node0Adj[0]).toBe(undefined);
expect(node0Adj[1]).toBe(1);
expect(node0Adj[2]).toBe(1);
expect(node0Adj[3]).toBe(1);
expect(node0Adj[4]).toBe(1);
expect(node0Adj[5]).toBe(undefined);
expect(node0Adj[6]).toBe(undefined);
expect(node0Adj[7]).toBe(undefined);
expect(node0Adj[8]).toBe(1);
})
it('directed', () => {
// directed
const matrix = adjMatrix(graph, true);
expect(Object.keys(matrix).length).toBe(9);
const node0Adj = matrix[0];
expect(node0Adj.length).toBe(5);
expect(node0Adj[0]).toBe(undefined);
expect(node0Adj[1]).toBe(1);
expect(node0Adj[2]).toBe(undefined);
expect(node0Adj[3]).toBe(1);
expect(node0Adj[4]).toBe(1);
const node8Adj = matrix[8];
expect(node8Adj.length).toBe(1);
expect(node8Adj[0]).toBe(1);
graph.destroy();
});
});

View File

@ -0,0 +1,264 @@
import G6, { Algorithm } from '../../../src';
const { floydWarshall } = Algorithm;
const div = document.createElement('div');
div.id = 'container';
document.body.appendChild(div);
const data = {
nodes: [
{
id: 'A',
label: '0'
},
{
id: 'B',
label: '1'
},
{
id: 'C',
label: '2'
},
{
id: 'D',
label: '3'
},
{
id: 'E',
label: '4'
},
{
id: 'F',
label: '5'
},
{
id: 'G',
label: '6'
},
{
id: 'H',
label: '7'
},
],
edges: [
{
source: 'A',
target: 'B',
},
{
source: 'B',
target: 'C',
},
{
source: 'C',
target: 'G',
},
{
source: 'A',
target: 'D',
},
{
source: 'A',
target: 'E',
},
{
source: 'E',
target: 'F',
},
{
source: 'F',
target: 'D',
},
]
}
describe('Shortest Path Matrix on graph', () => {
const graph = new G6.Graph({
container: 'container',
width: 500,
height: 500,
defaultEdge: {
style: {
endArrow: true
}
}
})
graph.data(data)
graph.render()
it('get graph shortest path matrix', () => {
const matrix = graph.getShortestPathMatrix();
console.log(matrix);
expect(Object.keys(matrix).length).toBe(8);
const node0 = matrix[0];
expect(node0.length).toBe(8);
expect(node0[0]).toBe(0);
expect(node0[1]).toBe(1);
expect(node0[2]).toBe(2);
expect(node0[3]).toBe(1);
expect(node0[4]).toBe(1);
expect(node0[5]).toBe(2);
expect(node0[6]).toBe(3);
expect(node0[7]).toBe(Infinity);
expect(matrix[1][7]).toBe(Infinity);
expect(matrix[2][7]).toBe(Infinity);
expect(matrix[3][7]).toBe(Infinity);
});
it('add items and cache', () => {
graph.addItem('node', {
id: 'I',
label: '8'
});
graph.addItem('edge', {
source: 'I',
target: 'A'
});
graph.addItem('edge', {
source: 'C',
target: 'A'
});
// use the cache
const cachedMatrix = graph.getShortestPathMatrix();
expect(Object.keys(cachedMatrix).length).toBe(8);
const cachedNode0 = cachedMatrix[0];
expect(cachedNode0.length).toBe(8);
expect(cachedNode0[0]).toBe(0);
expect(cachedNode0[1]).toBe(1);
expect(cachedNode0[2]).toBe(2);
expect(cachedNode0[3]).toBe(1);
expect(cachedNode0[4]).toBe(1);
expect(cachedNode0[5]).toBe(2);
expect(cachedNode0[6]).toBe(3);
expect(cachedNode0[7]).toBe(Infinity);
expect(cachedMatrix[1][7]).toBe(Infinity);
expect(cachedMatrix[2][7]).toBe(Infinity);
expect(cachedMatrix[3][7]).toBe(Infinity);
// do not use the cache
const matrix = graph.getShortestPathMatrix(false);
expect(Object.keys(matrix).length).toBe(9);
console.log(matrix);
const node0 = matrix[0];
expect(node0.length).toBe(9);
expect(node0[0]).toBe(0);
expect(node0[1]).toBe(1);
expect(node0[2]).toBe(1);
expect(node0[3]).toBe(1);
expect(node0[4]).toBe(1);
expect(node0[5]).toBe(2);
expect(node0[6]).toBe(2);
expect(node0[7]).toBe(Infinity);
expect(node0[8]).toBe(1);
})
it('directed', () => {
// do not use the cache and directed
const matrix = graph.getShortestPathMatrix(false, true);
expect(Object.keys(matrix).length).toBe(9);
console.log(matrix)
const node0 = matrix[0];
expect(node0.length).toBe(9);
expect(node0[0]).toBe(0);
expect(node0[1]).toBe(1);
expect(node0[2]).toBe(2);
expect(node0[3]).toBe(1);
expect(node0[4]).toBe(1);
expect(node0[5]).toBe(2);
expect(node0[6]).toBe(3);
expect(node0[7]).toBe(Infinity);
expect(node0[8]).toBe(Infinity);
const node8 = matrix[8];
expect(node8.length).toBe(9);
expect(node8[0]).toBe(1);
graph.destroy();
});
});
describe('Adjacency Matrix by Algorithm', () => {
const graph = new G6.Graph({
container: 'container',
width: 500,
height: 500,
defaultEdge: {
style: {
endArrow: true
}
}
})
graph.data(data)
graph.render()
it('get graph shortestpath matrix', () => {
const matrix = floydWarshall(graph);
expect(Object.keys(matrix).length).toBe(8);
const node0 = matrix[0];
expect(node0.length).toBe(8);
expect(node0[0]).toBe(0);
expect(node0[1]).toBe(1);
expect(node0[2]).toBe(2);
expect(node0[3]).toBe(1);
expect(node0[4]).toBe(1);
expect(node0[5]).toBe(2);
expect(node0[6]).toBe(3);
expect(node0[7]).toBe(Infinity);
expect(matrix[1][7]).toBe(Infinity);
expect(matrix[2][7]).toBe(Infinity);
expect(matrix[3][7]).toBe(Infinity);
});
it('add items and cache', () => {
graph.addItem('node', {
id: 'I',
label: '8'
});
graph.addItem('edge', {
source: 'I',
target: 'A'
});
graph.addItem('edge', {
source: 'C',
target: 'A'
});
const matrix = floydWarshall(graph);
expect(Object.keys(matrix).length).toBe(9);
console.log(matrix);
const node0 = matrix[0];
expect(node0.length).toBe(9);
expect(node0[0]).toBe(0);
expect(node0[1]).toBe(1);
expect(node0[2]).toBe(1);
expect(node0[3]).toBe(1);
expect(node0[4]).toBe(1);
expect(node0[5]).toBe(2);
expect(node0[6]).toBe(2);
expect(node0[7]).toBe(Infinity);
expect(node0[8]).toBe(1);
})
it('directed', () => {
// directed
const matrix = floydWarshall(graph, true);
expect(Object.keys(matrix).length).toBe(9);
const node0 = matrix[0];
expect(node0.length).toBe(9);
expect(node0[0]).toBe(0);
expect(node0[1]).toBe(1);
expect(node0[2]).toBe(2);
expect(node0[3]).toBe(1);
expect(node0[4]).toBe(1);
expect(node0[5]).toBe(2);
expect(node0[6]).toBe(3);
expect(node0[7]).toBe(Infinity);
expect(node0[8]).toBe(Infinity);
const node8 = matrix[8];
expect(node8.length).toBe(9);
expect(node8[0]).toBe(1);
graph.destroy();
});
});