mirror of
https://gitee.com/antv/g6.git
synced 2024-11-29 18:28:19 +08:00
feat: dijkstra shortest path length algorithm; fix: integrate getSourceNeighbors and getTargetNeighbors to getNeighbors(node, type)
This commit is contained in:
parent
684cd874ec
commit
b1d494610f
@ -5,9 +5,11 @@
|
||||
- fix: dulplicated edges in nodeselectchange event of brush-select;
|
||||
- fix: triple click and drag canvas problem;
|
||||
- fix: sync the minZoom and maxZoom in drag-canvas and graph;
|
||||
- fix: integrate getSourceNeighbors and getTargetNeighbors to getNeighbors(node, type);
|
||||
- feat: initial x and y for combo data;
|
||||
- feat: dagre layout supports sortByCombo;
|
||||
- feat: allow user to disable relayout in collapse-expand-combo behavior.
|
||||
- feat: allow user to disable relayout in collapse-expand-combo behavior;
|
||||
- feat: dijkstra shortest path lenght algorithm.
|
||||
|
||||
#### 3.5.9
|
||||
- fix: multiple animate update shape for combo;
|
||||
|
@ -50,7 +50,7 @@
|
||||
"site:deploy": "npm run site:build && gh-pages -d public",
|
||||
"start": "npm run site:develop",
|
||||
"test": "jest",
|
||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/svg-spec.ts",
|
||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/tree-graph-spec.ts",
|
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
||||
"watch": "father build -w",
|
||||
"cdn": "antv-bin upload -n @antv/g6"
|
||||
|
@ -61,7 +61,7 @@ const breadthFirstSearch = (graph: IGraph, startNodeId: string, originalCallback
|
||||
})
|
||||
|
||||
// 将所有邻居添加到队列中以便遍历
|
||||
graph.getSourceNeighbors(currentNode).forEach((nextNode: INode) => {
|
||||
graph.getNeighbors(currentNode, 'target').forEach((nextNode: INode) => {
|
||||
if (callbacks.allowTraversal({
|
||||
previous: previousNode,
|
||||
current: currentNode,
|
||||
|
@ -39,7 +39,7 @@ function depthFirstSearchRecursive(graph: IGraph, currentNode: INode, previousNo
|
||||
previous: previousNode
|
||||
});
|
||||
|
||||
graph.getSourceNeighbors(currentNode).forEach((nextNode) => {
|
||||
graph.getNeighbors(currentNode, 'target').forEach((nextNode) => {
|
||||
if (callbacks.allowTraversal({
|
||||
previous: previousNode,
|
||||
current: currentNode,
|
||||
|
48
src/algorithm/dijkstra.ts
Normal file
48
src/algorithm/dijkstra.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { IGraph } from '../interface/graph';
|
||||
|
||||
const dijkstra = (graph: IGraph, source: string, directed?: boolean, weightPropertyName?: string) => {
|
||||
|
||||
const nodes = graph.getNodes();
|
||||
|
||||
const nodeIdxMap = {};
|
||||
let i, v;
|
||||
const marks = [];
|
||||
const D = [];
|
||||
nodes.forEach((node, i) => {
|
||||
const id = node.getID();
|
||||
nodeIdxMap[id] = i;
|
||||
D[i] = Infinity;
|
||||
if (id === source) D[i] = 0;
|
||||
});
|
||||
|
||||
const nodeNum = nodes.length;
|
||||
for (i = 0; i < nodeNum; i++) { //Process the vertices
|
||||
v = minVertex(D, nodeNum, marks);
|
||||
if (D[v] === Infinity) return; //Unreachable vertices
|
||||
marks[i] = true;
|
||||
|
||||
let relatedEdges = [];
|
||||
if (!directed) relatedEdges = nodes[v].getOutEdges();
|
||||
else relatedEdges = nodes[v].getEdges();
|
||||
|
||||
relatedEdges.forEach(e => {
|
||||
const w = nodeIdxMap[e.getTarget().getID()];
|
||||
const weight = (weightPropertyName && e.getModel()[weightPropertyName]) ? e.getModel()[weightPropertyName] : 1;
|
||||
if (D[w] > (D[v] + weight)) D[w] = D[v] + weight;
|
||||
});
|
||||
}
|
||||
return D
|
||||
}
|
||||
|
||||
function minVertex(D: number[], nodeNum: number, marks: boolean[]) {//找出最小的点
|
||||
let i, v;
|
||||
for (i = 0; i < nodeNum; i++) { //找没有被访问的点
|
||||
if (marks[i] == false) v = i; break;
|
||||
}
|
||||
for (i++; i < nodeNum; i++) { //找比上面还小的未被访问的点(注意此时的i++)
|
||||
if (!marks[i] && D[i] < D[v]) v = i;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
export default dijkstra;
|
@ -9,7 +9,7 @@ export default {
|
||||
* 发生收缩/扩展变化时的回调
|
||||
*/
|
||||
trigger: DEFAULT_TRIGGER,
|
||||
onChange() {},
|
||||
onChange() { },
|
||||
};
|
||||
},
|
||||
getEvents(): { [key in G6Event]?: string } {
|
||||
@ -22,6 +22,7 @@ export default {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Behavior collapse-expand 的 trigger 参数不合法,请输入 'click' 或 'dblclick'");
|
||||
}
|
||||
console.log('triggertrigger', trigger);
|
||||
return {
|
||||
[`node:${trigger}`]: 'onNodeClick',
|
||||
};
|
||||
|
@ -2823,43 +2823,14 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
* @returns {INode[]}
|
||||
* @memberof IGraph
|
||||
*/
|
||||
public getNeighbors(node: string | INode): INode[] {
|
||||
public getNeighbors(node: string | INode, type?: 'source' | 'target' | undefined): INode[] {
|
||||
let item = node as INode
|
||||
if (isString(node)) {
|
||||
item = this.findById(node) as INode
|
||||
}
|
||||
return item.getNeighbors()
|
||||
return item.getNeighbors(type)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取以 node 为起点的所有邻居节点
|
||||
*
|
||||
* @param {(string | INode)} node 节点 ID 或实例
|
||||
* @returns {INode[]}
|
||||
* @memberof IGraph
|
||||
*/
|
||||
public getSourceNeighbors(node: string | INode): INode[] {
|
||||
let item = node as INode
|
||||
if (isString(node)) {
|
||||
item = this.findById(node) as INode
|
||||
}
|
||||
return item.getSourceNeighbors()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取以 node 为终点的所有邻居节点
|
||||
*
|
||||
* @param {(string | INode)} node 节点 ID 或实例
|
||||
* @returns {INode[]}
|
||||
* @memberof IGraph
|
||||
*/
|
||||
public getTargetNeighbors(node: string | INode): INode[] {
|
||||
let item = node as INode
|
||||
if (isString(node)) {
|
||||
item = this.findById(node) as INode
|
||||
}
|
||||
return item.getTargetNeighbors()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -229,24 +229,6 @@ export interface IGraph extends EventEmitter {
|
||||
*/
|
||||
getCombos(): ICombo[];
|
||||
|
||||
/**
|
||||
* 获取以 node 为起点的所有邻居节点
|
||||
*
|
||||
* @param {(string | INode)} node 节点 ID 或实例
|
||||
* @returns {INode[]}
|
||||
* @memberof IGraph
|
||||
*/
|
||||
getSourceNeighbors(node: string | INode): INode[];
|
||||
|
||||
/**
|
||||
* 获取以 node 为终点的所有邻居节点
|
||||
*
|
||||
* @param {(string | INode)} node 节点 ID 或实例
|
||||
* @returns {INode[]}
|
||||
* @memberof IGraph
|
||||
*/
|
||||
getTargetNeighbors(node: string | INode): INode[];
|
||||
|
||||
/**
|
||||
* 获取节点所有的邻居节点,有向图中效果同无向图
|
||||
*
|
||||
@ -254,7 +236,7 @@ export interface IGraph extends EventEmitter {
|
||||
* @returns {INode[]}
|
||||
* @memberof IGraph
|
||||
*/
|
||||
getNeighbors(node: string | INode): INode[];
|
||||
getNeighbors(node: string | INode, type?: 'source' | 'target' | undefined): INode[];
|
||||
|
||||
/**
|
||||
* 获取指定 combo 中所有的节点
|
||||
|
@ -301,23 +301,7 @@ export interface INode extends IItemBase {
|
||||
* @returns {INode[]}
|
||||
* @memberof INode
|
||||
*/
|
||||
getNeighbors(): INode[];
|
||||
|
||||
/**
|
||||
* 获取以 node 为起点的所有邻居节点
|
||||
*
|
||||
* @returns {INode[]}
|
||||
* @memberof INode
|
||||
*/
|
||||
getSourceNeighbors(): INode[];
|
||||
|
||||
/**
|
||||
* 获取以 node 为终点的所有邻居节点
|
||||
*
|
||||
* @returns {INode[]}
|
||||
* @memberof INode
|
||||
*/
|
||||
getTargetNeighbors(): INode[];
|
||||
getNeighbors(type?: 'source' | 'target' | undefined): INode[];
|
||||
}
|
||||
|
||||
export interface ICombo extends INode {
|
||||
|
@ -6,7 +6,7 @@ import { IPoint, IShapeBase, NodeConfig } from '../types';
|
||||
import {
|
||||
distance,
|
||||
getCircleIntersectByPoint,
|
||||
getEllipseIntersectByPoint,
|
||||
getEllipseIntersectByPoint,
|
||||
getRectIntersectByPoint,
|
||||
} from '../util/math';
|
||||
import Item from './item';
|
||||
@ -68,48 +68,30 @@ export default class Node extends Item implements INode {
|
||||
* @returns {INode[]}
|
||||
* @memberof Node
|
||||
*/
|
||||
public getNeighbors(): INode[] {
|
||||
public getNeighbors(type?: 'target' | 'source' | undefined): INode[] {
|
||||
const edges = this.get('edges') as IEdge[]
|
||||
|
||||
if (type === 'target') {
|
||||
// 当前节点为 source,它所指向的目标节点
|
||||
const neighhborsConverter = (edge: IEdge) => {
|
||||
return edge.getSource() === this;
|
||||
}
|
||||
return edges.filter(neighhborsConverter).map(edge => edge.getTarget());
|
||||
} else if (type === 'source') {
|
||||
// 当前节点为 target,它所指向的源节点
|
||||
const neighhborsConverter = (edge: IEdge) => {
|
||||
return edge.getTarget() === this
|
||||
}
|
||||
return edges.filter(neighhborsConverter).map(edge => edge.getSource())
|
||||
}
|
||||
|
||||
// 若未指定 type ,则返回所有邻居
|
||||
const neighhborsConverter = (edge: IEdge) => {
|
||||
return edge.getSource() === this ? edge.getTarget() : edge.getSource()
|
||||
}
|
||||
|
||||
return edges.map(neighhborsConverter)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取以当前节点为source的邻居节点
|
||||
*
|
||||
* @returns {INode[]}
|
||||
* @memberof Node
|
||||
*/
|
||||
public getSourceNeighbors(): INode[] {
|
||||
const edges = this.get('edges') as IEdge[]
|
||||
|
||||
const neighhborsConverter = (edge: IEdge) => {
|
||||
return edge.getSource() === this
|
||||
}
|
||||
|
||||
return edges.filter(neighhborsConverter).map(edge => edge.getTarget())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取以当前节点为target的邻居节点
|
||||
*
|
||||
* @returns {INode[]}
|
||||
* @memberof Node
|
||||
*/
|
||||
public getTargetNeighbors(): INode[] {
|
||||
const edges = this.get('edges') as IEdge[]
|
||||
|
||||
const neighhborsConverter = (edge: IEdge) => {
|
||||
return edge.getTarget() === this
|
||||
}
|
||||
|
||||
return edges.filter(neighhborsConverter).map(edge => edge.getSource())
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据锚点的索引获取连接点
|
||||
* @param {Number} index 索引
|
||||
|
@ -128,6 +128,20 @@ export default class DagreLayout extends BaseLayout {
|
||||
Object.keys(levels).forEach(key => {
|
||||
const levelNodes = levels[key].nodes;
|
||||
const nodesNum = levelNodes.length;
|
||||
const comboCenters = {};
|
||||
levelNodes.forEach(lnode => {
|
||||
const lnodeCombo = lnode.comboId;
|
||||
if (!comboCenters[lnodeCombo]) comboCenters[lnodeCombo] = { x: 0, y: 0, count: 0 };
|
||||
comboCenters[lnodeCombo].x += lnode.x;
|
||||
comboCenters[lnodeCombo].y += lnode.y;
|
||||
comboCenters[lnodeCombo].count++;
|
||||
});
|
||||
Object.keys(comboCenters).forEach(ckey => {
|
||||
comboCenters[ckey].x /= comboCenters[ckey].count;
|
||||
comboCenters[ckey].y /= comboCenters[ckey].count;
|
||||
});
|
||||
|
||||
|
||||
if (nodesNum === 1) return;
|
||||
const sortedByX = levelNodes.sort((a, b) => { return a.x - b.x });
|
||||
const minX = sortedByX[0].x;
|
||||
|
@ -60,7 +60,7 @@ describe('graph', () => {
|
||||
it('invalid container', () => {
|
||||
expect(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new Graph({} as any);
|
||||
new Graph({} as any);
|
||||
}).toThrowError('invalid container');
|
||||
});
|
||||
|
||||
@ -1461,7 +1461,7 @@ describe('auto rotate label on edge', () => {
|
||||
graph.on('canvas:click', evt => {
|
||||
graph.downloadFullImage('graph', {
|
||||
backgroundColor: '#fff',
|
||||
padding: [ 40, 10, 10, 10 ]
|
||||
padding: [40, 10, 10, 10]
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1536,21 +1536,21 @@ describe('node Neighbors', () => {
|
||||
graph.render()
|
||||
|
||||
it('getSourceNeighbors', () => {
|
||||
const neighbors = graph.getSourceNeighbors('B')
|
||||
const neighbors = graph.getNeighbors('B', 'target')
|
||||
expect(neighbors.length).toBe(1)
|
||||
expect(neighbors[0].getID()).toEqual('C')
|
||||
|
||||
const neighborE = graph.getSourceNeighbors('A')
|
||||
|
||||
const neighborE = graph.getNeighbors('A', 'target')
|
||||
expect(neighborE.length).toBe(3)
|
||||
expect(neighborE[0].getID()).toEqual('B')
|
||||
})
|
||||
|
||||
it('getTargetNeighbors', () => {
|
||||
const neighbors = graph.getTargetNeighbors('B')
|
||||
const neighbors = graph.getNeighbors('B', 'source')
|
||||
expect(neighbors.length).toBe(1)
|
||||
expect(neighbors[0].getID()).toEqual('A')
|
||||
|
||||
const neighborE = graph.getTargetNeighbors('E')
|
||||
const neighborE = graph.getNeighbors('E', 'source')
|
||||
expect(neighborE.length).toBe(1)
|
||||
expect(neighborE[0].getID()).toEqual('A')
|
||||
})
|
||||
@ -1619,7 +1619,7 @@ describe('redo stack & undo stack', () => {
|
||||
|
||||
// update 后,undo stack 中有 2 条数据,一条 render,一条 update
|
||||
graph.update('node1', {
|
||||
x: 120,
|
||||
x: 120,
|
||||
y: 200
|
||||
})
|
||||
|
||||
@ -1635,7 +1635,7 @@ describe('redo stack & undo stack', () => {
|
||||
|
||||
// 执行 update 后,undo stack 中有3条数据
|
||||
graph.update('node2', {
|
||||
x: 120,
|
||||
x: 120,
|
||||
y: 350
|
||||
})
|
||||
|
||||
@ -1696,7 +1696,7 @@ describe('redo stack & undo stack', () => {
|
||||
let stackData = graph.getStackData()
|
||||
let undoStack = stackData.undoStack
|
||||
let redoStack = stackData.redoStack
|
||||
|
||||
|
||||
expect(undoStack.length).toBe(0)
|
||||
expect(redoStack.length).toBe(0)
|
||||
})
|
||||
|
@ -121,7 +121,7 @@ describe('tree graph without animate', () => {
|
||||
type: 'rect',
|
||||
children: [{ x: 150, y: 150, id: 'SubTreeNode3.1.1' }],
|
||||
};
|
||||
graph.on('afteraddchild', function(e) {
|
||||
graph.on('afteraddchild', function (e) {
|
||||
expect(e.item.getModel().id === 'SubTreeNode3.1' || e.item.getModel().id === 'SubTreeNode3.1.1').toBe(true);
|
||||
expect(e.item.get('parent').getModel().id === 'SubTreeNode3' || e.item.get('parent').getModel().id === 'SubTreeNode3.1').toBe(true);
|
||||
expect(e.parent.getModel().id === 'SubTreeNode3' || e.parent.getModel().id === 'SubTreeNode3.1').toBe(true);
|
||||
@ -535,11 +535,54 @@ describe('tree graph with animate', () => {
|
||||
expect(edge.get('source')).toEqual(graph3.findById('SubTreeNode4'));
|
||||
expect(edge.get('target')).toEqual(graph3.findById('SubTreeNode4.1'));
|
||||
});
|
||||
|
||||
|
||||
graph3.changeData(data3);
|
||||
|
||||
expect(graph3.save()).toEqual(data3);
|
||||
});
|
||||
it('collapse & expand with parameter trigger=dblclick', done => {
|
||||
graph3.off();
|
||||
graph3.moveTo(100, 150);
|
||||
|
||||
const parent = graph3.findById('SubTreeNode1');
|
||||
let child = graph3.findById('SubTreeNode1.1');
|
||||
|
||||
let collapsed = undefined;
|
||||
graph3.on('afteranimate', () => {
|
||||
if (collapsed === undefined) return;
|
||||
if (collapsed) {
|
||||
expect(parent.getModel().collapsed).toBe(true);
|
||||
expect(child.destroyed).toBe(true);
|
||||
} else {
|
||||
child = graph3.findById('SubTreeNode1.1');
|
||||
expect(parent.getModel().collapsed).toBe(false);
|
||||
expect(child.get('model').x).not.toEqual(parent.get('model').x);
|
||||
expect(!!child.getModel().collapsed).toBe(false);
|
||||
expect(child.get('model').y).not.toEqual(parent.get('model').y);
|
||||
// done();
|
||||
}
|
||||
});
|
||||
graph3.addBehaviors(
|
||||
[
|
||||
{
|
||||
type: 'collapse-expand',
|
||||
trigger: 'dblclick',
|
||||
},
|
||||
],
|
||||
'default',
|
||||
);
|
||||
timerOut(() => {
|
||||
graph3.emit('node:dblclick', { item: parent });
|
||||
collapsed = true;
|
||||
}, 600);
|
||||
|
||||
|
||||
timerOut(() => {
|
||||
collapsed = false;
|
||||
graph3.emit('node:dblclick', { item: parent });
|
||||
done();
|
||||
}, 1200);
|
||||
});
|
||||
it('collapse & expand', () => {
|
||||
graph3.off();
|
||||
|
||||
@ -567,45 +610,6 @@ describe('tree graph with animate', () => {
|
||||
graph3.emit('node:click', { item: parent });
|
||||
}, 600);
|
||||
});
|
||||
it('collapse & expand with parameter trigger=dblclick', () => {
|
||||
graph3.off();
|
||||
graph3.moveTo(100, 150);
|
||||
|
||||
const parent = graph3.findById('SubTreeNode1');
|
||||
let child = graph3.findById('SubTreeNode1.1');
|
||||
|
||||
let collapsed = true;
|
||||
graph3.on('afteranimate', () => {
|
||||
if (collapsed) {
|
||||
console.log(parent.getModel().collapsed, child.destroyed)
|
||||
expect(parent.getModel().collapsed).toBe(true);
|
||||
expect(child.destroyed).toBe(true);
|
||||
} else {
|
||||
child = graph3.findById('SubTreeNode1.1');
|
||||
expect(parent.getModel().collapsed).toBe(false);
|
||||
expect(child.get('model').x).not.toEqual(parent.get('model').x);
|
||||
expect(!!child.getModel().collapsed).toBe(false);
|
||||
expect(child.get('model').y).not.toEqual(parent.get('model').y);
|
||||
// done();
|
||||
}
|
||||
});
|
||||
graph3.addBehaviors(
|
||||
[
|
||||
{
|
||||
type: 'collapse-expand',
|
||||
trigger: 'dblclick',
|
||||
},
|
||||
],
|
||||
'default',
|
||||
);
|
||||
|
||||
graph3.emit('node:dblclick', { item: parent });
|
||||
|
||||
timerOut(() => {
|
||||
collapsed = false;
|
||||
graph3.emit('node:dblclick', { item: parent });
|
||||
}, 600);
|
||||
});
|
||||
|
||||
// it('test', () => {
|
||||
// const data = {
|
||||
|
Loading…
Reference in New Issue
Block a user