mirror of
https://gitee.com/antv/g6.git
synced 2024-11-30 02:38:20 +08:00
fix: revert clustering for force layout pull request from community; fix: update the typings for G6Event; docs: update the event api docs
This commit is contained in:
parent
1d75524307
commit
40e9626914
@ -15,6 +15,7 @@
|
||||
- feat: add edgeConfig for create-edge behavior, closes: #2195;
|
||||
- fix: remove the source node while creat-edge;
|
||||
- feat: create-edge for combo, closes: #2211;
|
||||
- fix: update the typings for G6Event;
|
||||
|
||||
#### 3.8.1
|
||||
- fix: update edge states with updateItem problem, closes: #2142;
|
||||
|
@ -148,38 +148,52 @@ graph.on(timingEventName, evt => {
|
||||
|
||||
| Event Name | Description |
|
||||
| --- | --- |
|
||||
| beforeadditem | Activated before `add` / `addItem` being called. |
|
||||
| afteradditem | Activated after `add` / `addItem` being called. |
|
||||
| beforeremoveitem | Activated before `remove` / `removeItem` being called. |
|
||||
| afterremoveitem | Activated after `remove` / `removeItem` being called. |
|
||||
| beforeupdateitem | Activated before `update` / `updateItem` being called. |
|
||||
| afterupdateitem | Activated after `update` / `updateItem` being called. |
|
||||
| beforeitemvisibilitychange | Activated before `showItem` / `hideItem` being called. |
|
||||
| afteritemvisibilitychange | Activated after `showItem` / `hideItem` being called. |
|
||||
| beforeitemstatechange | Activated before `setItemState` being called. |
|
||||
| afteritemstatechange | Activated after `setItemState` being called. |
|
||||
| beforeitemrefresh | Activated before `refreshItem` being called. |
|
||||
| afteritemrefresh | Activated after `refreshItem` being called. |
|
||||
| beforeitemstatesclear | Activated before `clearItemStates` being called. |
|
||||
| afteritemstatesclear | Activated after `clearItemStates` being called. |
|
||||
| beforemodechange | Activated before `setMode` / `addBehaviors` / `removeBehaviors` being called. |
|
||||
| aftermodechange | Activated after `setMode` / `addBehaviors` / `removeBehaviors` being called. |
|
||||
| beforelayout | Activated before graph layout. `render` will layout the graph, so `render` will activate this event as well. |
|
||||
| afterlayout | Activated after graph layout being done. `render` will layout the graph, so `render` will activate this event as well. |
|
||||
| beforerender | Activated before `graph.render` / `graph.read` being called. |
|
||||
| afterrender | Activated after `graph.render` / `graph.read` being called. |
|
||||
| beforeadditem | Activated before `graph.add` / `graph.addItem` being called. |
|
||||
| afteradditem | Activated after `graph.add` / `graph.addItem` being called. |
|
||||
| beforeremoveitem | Activated before `graph.remove` / `graph.removeItem` being called. |
|
||||
| afterremoveitem | Activated after `graph.remove` / `graph.removeItem` being called. |
|
||||
| beforeupdateitem | Activated before `graph.update` / `graph.updateItem` being called. |
|
||||
| afterupdateitem | Activated after `graph.update` / `graph.updateItem` being called. |
|
||||
| beforeitemvisibilitychange | Activated before `graph.showItem` / `graph.hideItem` being called. |
|
||||
| afteritemvisibilitychange | Activated after `graph.showItem` / `graph.hideItem` being called. |
|
||||
| beforeitemstatechange | Activated before `graph.setItemState` being called. |
|
||||
| afteritemstatechange | Activated after `graph.setItemState` being called. |
|
||||
| beforeitemrefresh | Activated before `graph.refreshItem` being called. |
|
||||
| afteritemrefresh | Activated after `graph.refreshItem` being called. |
|
||||
| beforeitemstatesclear | Activated before `graph.clearItemStates` being called. |
|
||||
| afteritemstatesclear | Activated after `graph.clearItemStates` being called. |
|
||||
| beforemodechange | Activated before `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` being called. |
|
||||
| aftermodechange | Activated after `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` being called. |
|
||||
| beforelayout | Activated before graph layout. `graph.render` will layout the graph, so `graph.render` will activate this event as well. |
|
||||
| afterlayout | Activated after graph layout being done. `graph.render` will layout the graph, so `graph.render` will activate this event as well. |
|
||||
| beforegraphrefreshposition | Activated before `graph.refreshPositions` beging called |
|
||||
| aftergraphrefreshposition | Activated after `graph.refreshPositions` beging called |
|
||||
| beforegraphrefresh | Activated before `graph.refresh` beging called |
|
||||
| aftergraphrefresh | Activated after `graph.refresh` beging called |
|
||||
| beforeanimate | Activated before global animation |
|
||||
| afteranimate | Activated after global animation |
|
||||
| beforecreateedge | Activated before an edge is created by the built-in behavior `create-edge` |
|
||||
| aftercreateedge | Activated after an edge is created by the built-in behavior `create-edge` |
|
||||
| graphstatechange | Activated after `graph.updateItemState` being called. |
|
||||
| afteractivaterelations | Activated while activating a node by `'activate-relations'` Behavior which is assigned to the the instance of Graph. |
|
||||
| nodeselectchange | Activated while the selected items are changed by `'brush-select'`, `'click-select'` or `'lasso-select'` Behavior which is assigned to the instance of Graph. |
|
||||
| beforecreateedge | Activated before an edge is created by the built-in behavior `create-edge` |
|
||||
| aftercreateedge | Activated after an edge is created by the built-in behavior `create-edge` |
|
||||
| itemcollapsed | Activated while a node is clicked to collapse or expand by `'collapse-expand'` Behavior which is assigned to the instance of TreeGraph. |
|
||||
| tooltipchange | Activated after the show/hide state is changed by `'tooltip'` or `'edge-tooltip'` Behavior which is assigned to the instance of Graph. |
|
||||
| wheelzoom | Activated after the canvas being zoomed by `'zoom-canvas'` Behavior which is assigned to the instance of Graph. |
|
||||
| dragnodeend | Activated while drag node end by `'drag-node'` Behavior |
|
||||
| wheelzoom | Activated after the canvas is zoomed by `'zoom-canvas'` Behavior which is assigned to the instance of Graph. |
|
||||
| viewportchange | Activated after the canvas is translated by `graph.moveTo`, `graph.translate`, and `graph.zoom`. |
|
||||
| dragnodeend | Activated while drag node end by `'drag-node'` Behavior. |
|
||||
| stackchange | Activated while the redo or undo stacks are changed. |
|
||||
|
||||
### Callback Parameters
|
||||
|
||||
The callback paramters are different from custom events.
|
||||
|
||||
#### beforerender / afterrender
|
||||
|
||||
No parameters.
|
||||
|
||||
#### beforeadditem
|
||||
|
||||
| Name | Type | Description |
|
||||
@ -303,9 +317,24 @@ No parameters.
|
||||
| deltaY | Number | The y-axis direction of the wheel scroll, value is `1`, `0`, or `-1`, where `0` means no scrolling on this direction. |
|
||||
| ... Other parameters of wheel event. | | |
|
||||
|
||||
#### viewportchange
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------ | ----------------------------------------------- |
|
||||
| action | 'translate' / 'move' / 'zoom' | The action of view port changing. |
|
||||
| matrix | Array | The matrix of the graph after the view port changed. |
|
||||
|
||||
|
||||
#### dragnodeend
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------ | ----------------------------------------------- |
|
||||
| items | Item[] | The manipulated items. |
|
||||
| targetItem | null/Node/Combo | The position where the node is placed after dragging, the default is null, that is, placed on the canvas. |
|
||||
|
||||
#### stackchange
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------ | ----------------------------------------------- |
|
||||
| redoStack | Object[] | The redo stack. |
|
||||
| undoStack | Object[] | The undo stack. |
|
||||
|
@ -151,6 +151,8 @@ graph.on(timingEventName, evt => {
|
||||
|
||||
| 事件名称 | 描述 |
|
||||
| --- | --- |
|
||||
| beforerender | 调用 `graph.render` / `graph.read` 方法之前触发 |
|
||||
| afterrender | 调用 `graph.render` / `graph.read` 方法之后触发 |
|
||||
| beforeadditem | 调用 `graph.add` / `graph.addItem` 方法之前触发 |
|
||||
| afteradditem | 调用 `graph.add` / `graph.addItem` 方法之后触发 |
|
||||
| beforeremoveitem | 调用 `graph.remove` / `graph.removeItem` 方法之前触发 |
|
||||
@ -169,20 +171,32 @@ graph.on(timingEventName, evt => {
|
||||
| aftermodechange | 调用 `graph.setMode` / `graph.addBehaviors` / `graph.removeBehaviors` 方法之后触发 |
|
||||
| beforelayout | 布局前触发。调用 `graph.render` 时会进行布局,因此 `render` 时会触发。或用户主动调用图的 `graph.layout` 时触发。 |
|
||||
| afterlayout | 布局完成后触发。调用 `graph.render` 时会进行布局,因此 `render` 时布局完成后会触发。或用户主动调用图的 `lgraph.ayout` 时布局完成后触发。 |
|
||||
| beforegraphrefreshposition | `graph.refreshPositions` 被调用前触发 |
|
||||
| aftergraphrefreshposition | `graph.refreshPositions` 被调用后触发 |
|
||||
| beforegraphrefresh | `graph.refresh` 被调用前触发 |
|
||||
| aftergraphrefresh | `graph.refresh` 被调用后触发 |
|
||||
| beforeanimate | 全局动画发生前触发 |
|
||||
| afteranimate | 全局动画发生后触发 |
|
||||
| beforecreateedge | 使用内置交互 `create-edge`,创建边之前触发 |
|
||||
| aftercreateedge | 使用内置交互 `create-edge`,创建边之后触发 |
|
||||
| graphstatechange | 调用 `graph.updateItemState` 方法之后触发 |
|
||||
| afteractivaterelations | 使用了 `'activate-relations'` Behavior 并触发了该行为后,该事件被触发 |
|
||||
| nodeselectchange | 使用了 `'brush-select'` , `'click-select'` 或 `'lasso-select'` Behavior 且选中元素发生变化时,该事件被触发 |
|
||||
| beforecreateedge | 使用内置交互 `create-edge`,创建边之前触发 |
|
||||
| aftercreateedge | 使用内置交互 `create-edge`,创建边之后触发 |
|
||||
| itemcollapsed | 在 TreeGraph 上使用了 `'collapse-expand'` Behavior 并触发了该行为后,该事件被触发 |
|
||||
| tooltipchange | 使用了 `'tooltip'` 或 `'edge-tooltip'` Behavior 且 tooltip 的显示/隐藏被改变后,该事件被触发 |
|
||||
| wheelzoom | 使用了 `'zoom-canvas'` Behavior 并用滚轮对图进行缩放后,该事件被触发 |
|
||||
| viewportchange |调用 `graph.moveTo`,`graph.translate`,或 `graph.zoom` 均会触发该事件 |
|
||||
| dragnodeend | 使用了 `'drag-node'` Behavior,当拖动结束时,该事件被触发 |
|
||||
| stackchange | 撤销/重做栈发生变化时,该事件触发 |
|
||||
|
||||
### 回调参数
|
||||
|
||||
不同时机监听事件的回调参数不同,下面针对各个自定义事件的回调参数进行说明。
|
||||
|
||||
#### beforerender / afterrender
|
||||
|
||||
无参数
|
||||
|
||||
#### beforeadditem
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
@ -298,9 +312,24 @@ graph.on(timingEventName, evt => {
|
||||
| deltaY | Number | 滚动的 y 方向,取值 `1`,`0`,`-1`,`0` 代表没有该方向的滚动 |
|
||||
| ... 其他滚轮事件的回调参数 | | |
|
||||
|
||||
#### viewportchange
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| ------ | ------ | ----------------------------------------------- |
|
||||
| action | 'translate' / 'move' / 'zoom' | 视窗变换的类型,`'translate'`、`'move'`、`'zoom'` 分别标识该时机是由 `graph.translate`、`graph.move`、还是 `graph.zoom` 函数的调用而产生 |
|
||||
| matrix | Array | 视窗变换后的图的矩阵 |
|
||||
|
||||
#### dragnodeend
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| ------ | ------ | --------------------------------------------- |
|
||||
| items | Item[] | 当前操作的 item 实例 |
|
||||
| targetItem | null/Node/Combo | 拖动节点结束后,节点是放到canvas、Node 还是 Combo 上面 |
|
||||
| targetItem | null/Node/Combo | 拖动节点结束后,节点是放到canvas、Node 还是 Combo 上面 |
|
||||
|
||||
#### stackchange
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| ------ | ------ | ----------------------------------------------- |
|
||||
| redoStack | Object[] | 重做堆栈 |
|
||||
| undoStack | Object[] | 撤销堆栈 |
|
||||
|
||||
|
@ -3281,7 +3281,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
this.get('layoutController').destroy();
|
||||
this.get('customGroupControll').destroy();
|
||||
this.get('canvas').destroy();
|
||||
|
||||
|
||||
if (this.get('graphWaterMarker')) {
|
||||
this.get('graphWaterMarker').destroy();
|
||||
}
|
||||
@ -3363,13 +3363,13 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
* @param {WaterMarkerConfig} config 文本水印的配置项
|
||||
*/
|
||||
public setImageWaterMarker(
|
||||
imgURL: string = Global.waterMarkerImage,
|
||||
config?: WaterMarkerConfig) {
|
||||
imgURL: string = Global.waterMarkerImage,
|
||||
config?: WaterMarkerConfig) {
|
||||
let container: string | HTMLElement | null = this.get('container')
|
||||
if (isString(container)) {
|
||||
container = document.getElementById(container)
|
||||
}
|
||||
|
||||
|
||||
if (!container.style.position) {
|
||||
container.style.position = 'relative'
|
||||
}
|
||||
@ -3403,23 +3403,23 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.src = imgURL
|
||||
img.onload = () => {
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, x, y, image.width, image.height)
|
||||
// 恢复旋转角度
|
||||
ctx.rotate((rotate * Math.PI) / 180)
|
||||
|
||||
|
||||
// 默认按照现代浏览器处理
|
||||
if (!compatible) {
|
||||
let box = document.querySelector('.g6-graph-watermarker') as HTMLElement
|
||||
if (!box) {
|
||||
box = document.createElement('div')
|
||||
box.className = 'g6-graph-watermarker'
|
||||
box.className = 'g6-graph-watermarker'
|
||||
}
|
||||
box.className = 'g6-graph-watermarker'
|
||||
if (!canvas.destroyed) {
|
||||
box.style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;position:absolute;top:0;bottom:0;left:0;right:0;pointer-events:none;z-index:-1;`;
|
||||
box.style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;position:absolute;top:0;bottom:0;left:0;right:0;pointer-events:none;z-index:-1;`;
|
||||
(container as HTMLElement).appendChild(box)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 当需要兼容不支持 pointer-events属性的浏览器时,将 compatible 设置为 true
|
||||
(container as HTMLElement).style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;`
|
||||
@ -3433,12 +3433,12 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
* @param {string[]} texts 水印的文本内容
|
||||
* @param {WaterMarkerConfig} config 文本水印的配置项
|
||||
*/
|
||||
public setTextWaterMarker(texts: string[], config?: WaterMarkerConfig) {
|
||||
public setTextWaterMarker(texts: string[], config?: WaterMarkerConfig) {
|
||||
let container: string | HTMLElement | null = this.get('container')
|
||||
if (isString(container)) {
|
||||
container = document.getElementById(container)
|
||||
}
|
||||
|
||||
|
||||
if (!container.style.position) {
|
||||
container.style.position = 'relative'
|
||||
}
|
||||
@ -3462,7 +3462,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
canvas = new GCanvas(canvasCfg)
|
||||
this.set('graphWaterMarker', canvas)
|
||||
}
|
||||
canvas.get('el').style.display = 'none'
|
||||
canvas.get('el').style.display = 'none'
|
||||
const ctx = canvas.get('context')
|
||||
|
||||
const { rotate, fill, fontFamily, fontSize, baseline, x, y, lineHeight } = text
|
||||
@ -3475,12 +3475,12 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
// 设置文字颜色
|
||||
ctx.fillStyle = fill
|
||||
|
||||
ctx.textBaseline = baseline
|
||||
ctx.textBaseline = baseline
|
||||
|
||||
for (let i = texts.length - 1; i >= 0; i--) {
|
||||
for (let i = texts.length - 1; i >= 0; i--) {
|
||||
// 将文字绘制到画布
|
||||
ctx.fillText(texts[i], x, y + i * lineHeight)
|
||||
}
|
||||
ctx.fillText(texts[i], x, y + i * lineHeight)
|
||||
}
|
||||
|
||||
// 恢复旋转角度
|
||||
ctx.rotate((rotate * Math.PI) / 180)
|
||||
@ -3490,13 +3490,13 @@ export default class Graph extends EventEmitter implements IGraph {
|
||||
let box = document.querySelector('.g6-graph-watermarker') as HTMLElement
|
||||
if (!box) {
|
||||
box = document.createElement('div')
|
||||
box.className = 'g6-graph-watermarker'
|
||||
box.className = 'g6-graph-watermarker'
|
||||
}
|
||||
box.style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;position:absolute;top:0;bottom:0;left:0;right:0;pointer-events:none;z-index:99;`;
|
||||
box.style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;position:absolute;top:0;bottom:0;left:0;right:0;pointer-events:none;z-index:99;`;
|
||||
container.appendChild(box)
|
||||
} else {
|
||||
// 当需要兼容不支持 pointer-events属性的浏览器时,将 compatible 设置为 true
|
||||
container.style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;`;
|
||||
container.style.cssText = `background-image: url(${canvas.get('el').toDataURL('image/png')});background-repeat:repeat;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ import {
|
||||
GraphAnimateConfig,
|
||||
StackData,
|
||||
HullCfg,
|
||||
WaterMarkerConfig
|
||||
WaterMarkerConfig,
|
||||
G6Event
|
||||
} from '../types';
|
||||
import { IEdge, INode, ICombo } from './item';
|
||||
import Hull from '../item/hull';
|
||||
@ -706,7 +707,7 @@ export interface ITreeGraph extends IGraph {
|
||||
* @param {string} imgURL 图片水印的url地址
|
||||
* @param {WaterMarkerConfig} config 文本水印的配置项
|
||||
*/
|
||||
setImageWaterMarker(imgURL: string , config: WaterMarkerConfig);
|
||||
setImageWaterMarker(imgURL: string, config: WaterMarkerConfig);
|
||||
|
||||
/**
|
||||
* 设置文本水印
|
||||
|
366
src/layout/force/force-in-a-box.ts
Normal file
366
src/layout/force/force-in-a-box.ts
Normal file
@ -0,0 +1,366 @@
|
||||
import * as d3Force from 'd3-force';
|
||||
|
||||
// https://github.com/john-guerra/forceInABox/blob/master/src/forceInABox.js
|
||||
export default function forceInABox() {
|
||||
function constant(_: any): () => any {
|
||||
return () => _;
|
||||
}
|
||||
|
||||
let groupBy = (d) => {
|
||||
return d.cluster;
|
||||
};
|
||||
let forceNodeSize: (() => number) | ((d: any) => number) = constant(1);
|
||||
let forceCharge: (() => number) | ((d: any) => number) = constant(-1);
|
||||
let forceLinkDistance: (() => number) | ((d: any) => number) = constant(100);
|
||||
let forceLinkStrength: (() => number) | ((d: any) => number) = constant(0.1);
|
||||
let offset = [0, 0];
|
||||
|
||||
let nodes = [];
|
||||
let nodesMap = {};
|
||||
let links = [];
|
||||
let centerX = 100;
|
||||
let centerY = 100;
|
||||
let foci = { none: { x: 0, y: 0 } };
|
||||
let templateNodes = [];
|
||||
let templateForce;
|
||||
let template = 'force';
|
||||
let enableGrouping = true;
|
||||
let strength = 0.1;
|
||||
|
||||
function force(alpha) {
|
||||
if (!enableGrouping) {
|
||||
return force;
|
||||
}
|
||||
templateForce.tick();
|
||||
getFocisFromTemplate();
|
||||
|
||||
for (let i = 0, n = nodes.length, node, k = alpha * strength; i < n; ++i) {
|
||||
node = nodes[i];
|
||||
node.vx += (foci[groupBy(node)].x - node.x) * k;
|
||||
node.vy += (foci[groupBy(node)].y - node.y) * k;
|
||||
}
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
if (!nodes) return;
|
||||
initializeWithForce();
|
||||
}
|
||||
|
||||
function initializeWithForce() {
|
||||
if (!nodes || !nodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupBy(nodes[0]) === undefined) {
|
||||
throw Error(
|
||||
"Couldnt find the grouping attribute for the nodes. Make sure to set it up with forceInABox.groupBy('clusterAttr') before calling .links()",
|
||||
);
|
||||
}
|
||||
|
||||
// checkLinksAsObjects();
|
||||
|
||||
const net = getGroupsGraph();
|
||||
templateForce = d3Force
|
||||
.forceSimulation(net.nodes)
|
||||
.force('x', d3Force.forceX(centerX).strength(0.1))
|
||||
.force('y', d3Force.forceY(centerY).strength(0.1))
|
||||
.force('collide', d3Force.forceCollide((d: { r: any }) => d.r).iterations(4))
|
||||
.force('charge', d3Force.forceManyBody().strength(forceCharge))
|
||||
.force(
|
||||
'links',
|
||||
d3Force
|
||||
.forceLink(net.nodes.length ? net.links : [])
|
||||
.distance(forceLinkDistance)
|
||||
.strength(forceLinkStrength),
|
||||
);
|
||||
|
||||
templateNodes = templateForce.nodes();
|
||||
|
||||
getFocisFromTemplate();
|
||||
}
|
||||
|
||||
function getGroupsGraph() {
|
||||
const gnodes = [];
|
||||
const glinks = [];
|
||||
const dNodes = {};
|
||||
let clustersList = [],
|
||||
clustersCounts = {},
|
||||
clustersLinks = [];
|
||||
|
||||
clustersCounts = computeClustersNodeCounts(nodes);
|
||||
clustersLinks = computeClustersLinkCounts(links);
|
||||
|
||||
clustersList = Object.keys(clustersCounts);
|
||||
|
||||
clustersList.forEach((key, index) => {
|
||||
const val = clustersCounts[key];
|
||||
// Uses approx meta-node size
|
||||
gnodes.push({
|
||||
id: key,
|
||||
size: val.count,
|
||||
r: Math.sqrt(val.sumforceNodeSize / Math.PI),
|
||||
});
|
||||
dNodes[key] = index;
|
||||
});
|
||||
|
||||
clustersLinks.forEach((l) => {
|
||||
const source = dNodes[l.source];
|
||||
const target = dNodes[l.target];
|
||||
if (source !== undefined && target !== undefined) {
|
||||
glinks.push({
|
||||
source,
|
||||
target,
|
||||
count: l.count,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { nodes: gnodes, links: glinks };
|
||||
}
|
||||
|
||||
function computeClustersNodeCounts(nodes) {
|
||||
const clustersCounts = {};
|
||||
|
||||
nodes.forEach((d) => {
|
||||
const key = groupBy(d);
|
||||
if (!clustersCounts[key]) {
|
||||
clustersCounts[key] = { count: 0, sumforceNodeSize: 0 };
|
||||
}
|
||||
});
|
||||
nodes.forEach((d: any) => {
|
||||
const key = groupBy(d);
|
||||
const nodeSize = forceNodeSize(d);
|
||||
const tmpCount = clustersCounts[key];
|
||||
tmpCount.count = tmpCount.count + 1;
|
||||
tmpCount.sumforceNodeSize = tmpCount.sumforceNodeSize + Math.PI * (nodeSize * nodeSize) * 1.3;
|
||||
clustersCounts[key] = tmpCount;
|
||||
});
|
||||
|
||||
return clustersCounts;
|
||||
}
|
||||
|
||||
function computeClustersLinkCounts(links) {
|
||||
const dClusterLinks = {};
|
||||
const clusterLinks = [];
|
||||
links.forEach((l) => {
|
||||
const key = getLinkKey(l);
|
||||
let count = 0;
|
||||
if (dClusterLinks[key] !== undefined) {
|
||||
count = dClusterLinks[key];
|
||||
}
|
||||
count += 1;
|
||||
dClusterLinks[key] = count;
|
||||
});
|
||||
|
||||
const entries = Object.entries(dClusterLinks);
|
||||
|
||||
entries.forEach(([key, count]) => {
|
||||
const source = key.split('~')[0];
|
||||
const target = key.split('~')[1];
|
||||
if (source !== undefined && target !== undefined) {
|
||||
clusterLinks.push({
|
||||
source,
|
||||
target,
|
||||
count,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return clusterLinks;
|
||||
}
|
||||
|
||||
function getFocisFromTemplate() {
|
||||
foci = { none: { x: 0, y: 0 } };
|
||||
templateNodes.forEach((d) => {
|
||||
foci[d.id] = {
|
||||
x: d.x - offset[0],
|
||||
y: d.y - offset[1],
|
||||
};
|
||||
});
|
||||
return foci;
|
||||
}
|
||||
|
||||
function getLinkKey(l) {
|
||||
const sourceID = groupBy(nodesMap[l.source]);
|
||||
const targetID = groupBy(nodesMap[l.target]);
|
||||
|
||||
return sourceID <= targetID ? `${sourceID}~${targetID}` : `${targetID}~${sourceID}`;
|
||||
}
|
||||
|
||||
function genNodesMap(nodes) {
|
||||
nodesMap = {};
|
||||
nodes.forEach((node) => {
|
||||
nodesMap[node.id] = node;
|
||||
});
|
||||
}
|
||||
|
||||
function setTemplate(x) {
|
||||
if (!arguments.length) return template;
|
||||
template = x;
|
||||
initialize();
|
||||
return force;
|
||||
}
|
||||
|
||||
function setGroupBy(x) {
|
||||
if (!arguments.length) return groupBy;
|
||||
if (typeof x === 'string') {
|
||||
groupBy = (d) => {
|
||||
return d[x];
|
||||
};
|
||||
return force;
|
||||
}
|
||||
groupBy = x;
|
||||
return force;
|
||||
}
|
||||
|
||||
function setEnableGrouping(x) {
|
||||
if (!arguments.length) return enableGrouping;
|
||||
enableGrouping = x;
|
||||
return force;
|
||||
}
|
||||
|
||||
function setStrength(x) {
|
||||
if (!arguments.length) return strength;
|
||||
strength = x;
|
||||
return force;
|
||||
}
|
||||
|
||||
function setCenterX(_) {
|
||||
if (arguments.length) {
|
||||
centerX = _;
|
||||
return force;
|
||||
}
|
||||
|
||||
return centerX;
|
||||
}
|
||||
|
||||
function setCenterY(_) {
|
||||
if (arguments.length) {
|
||||
centerY = _;
|
||||
return force;
|
||||
}
|
||||
|
||||
return centerY;
|
||||
}
|
||||
|
||||
function setNodes(_) {
|
||||
if (arguments.length) {
|
||||
genNodesMap(_ || []);
|
||||
nodes = _ || [];
|
||||
return force;
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function setLinks(_) {
|
||||
if (arguments.length) {
|
||||
links = _ || [];
|
||||
initialize();
|
||||
return force;
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
function setForceNodeSize(_) {
|
||||
if (arguments.length) {
|
||||
if (typeof _ === 'function') {
|
||||
forceNodeSize = _;
|
||||
} else {
|
||||
forceNodeSize = constant(+_);
|
||||
}
|
||||
initialize();
|
||||
return force;
|
||||
}
|
||||
|
||||
return forceNodeSize;
|
||||
}
|
||||
|
||||
function setForceCharge(_) {
|
||||
if (arguments.length) {
|
||||
if (typeof _ === 'function') {
|
||||
forceCharge = _;
|
||||
} else {
|
||||
forceCharge = constant(+_);
|
||||
}
|
||||
initialize();
|
||||
return force;
|
||||
}
|
||||
|
||||
return forceCharge;
|
||||
}
|
||||
|
||||
function setForceLinkDistance(_) {
|
||||
if (arguments.length) {
|
||||
if (typeof _ === 'function') {
|
||||
forceLinkDistance = _;
|
||||
} else {
|
||||
forceLinkDistance = constant(+_);
|
||||
}
|
||||
initialize();
|
||||
return force;
|
||||
}
|
||||
|
||||
return forceLinkDistance;
|
||||
}
|
||||
|
||||
function setForceLinkStrength(_) {
|
||||
if (arguments.length) {
|
||||
if (typeof _ === 'function') {
|
||||
forceLinkStrength = _;
|
||||
} else {
|
||||
forceLinkStrength = constant(+_);
|
||||
}
|
||||
initialize();
|
||||
return force;
|
||||
}
|
||||
|
||||
return forceLinkStrength;
|
||||
}
|
||||
|
||||
function setOffset(_) {
|
||||
if (arguments.length) {
|
||||
offset = _;
|
||||
return force;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
force.initialize = (_) => {
|
||||
nodes = _;
|
||||
initialize();
|
||||
};
|
||||
|
||||
force.template = setTemplate;
|
||||
|
||||
force.groupBy = setGroupBy;
|
||||
|
||||
force.enableGrouping = setEnableGrouping;
|
||||
|
||||
force.strength = setStrength;
|
||||
|
||||
force.centerX = setCenterX;
|
||||
|
||||
force.centerY = setCenterY;
|
||||
|
||||
force.nodes = setNodes;
|
||||
|
||||
force.links = setLinks;
|
||||
|
||||
force.forceNodeSize = setForceNodeSize;
|
||||
|
||||
// Legacy support
|
||||
force.nodeSize = force.forceNodeSize;
|
||||
|
||||
force.forceCharge = setForceCharge;
|
||||
|
||||
force.forceLinkDistance = setForceLinkDistance;
|
||||
|
||||
force.forceLinkStrength = setForceLinkStrength;
|
||||
|
||||
force.offset = setOffset;
|
||||
|
||||
force.getFocis = getFocisFromTemplate;
|
||||
|
||||
return force;
|
||||
}
|
@ -3,18 +3,18 @@
|
||||
* @author shiwu.wyy@antfin.com
|
||||
*/
|
||||
|
||||
import { GraphData, IPointTuple } from '../types';
|
||||
import { GraphData, IPointTuple } from '../../types';
|
||||
|
||||
import * as d3Force from 'd3-force';
|
||||
import forceInABox from './force-in-a-box';
|
||||
|
||||
import isArray from '@antv/util/lib/is-array';
|
||||
import isFunction from '@antv/util/lib/is-function';
|
||||
import isNumber from '@antv/util/lib/is-number';
|
||||
import mix from '@antv/util/lib/mix';
|
||||
|
||||
import { BaseLayout } from './layout';
|
||||
import { LAYOUT_MESSAGE } from './worker/layoutConst';
|
||||
import { clone } from '@antv/util';
|
||||
import { BaseLayout } from '../layout';
|
||||
import { LAYOUT_MESSAGE } from '../worker/layoutConst';
|
||||
|
||||
/**
|
||||
* 经典力导布局 force-directed
|
||||
@ -38,6 +38,24 @@ export default class ForceLayout<Cfg = any> extends BaseLayout {
|
||||
/** 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离) */
|
||||
public nodeSpacing: ((d?: unknown) => number) | undefined;
|
||||
|
||||
/** 是否支持按类聚合 */
|
||||
public clustering: boolean;
|
||||
|
||||
/** 聚类节点作用力 */
|
||||
public clusterNodeStrength: number | null = null;
|
||||
|
||||
/** 聚类边作用力 */
|
||||
public clusterEdgeStrength: number | null = null;
|
||||
|
||||
/** 聚类边长度 */
|
||||
public clusterEdgeDistance: number | null = null;
|
||||
|
||||
/** 聚类节点大小 / 直径,直径越大,越分散 */
|
||||
public clusterNodeSize: number | null = null;
|
||||
|
||||
/** 用于 foci 的力 */
|
||||
public clusterFociStrength: number | null = null;
|
||||
|
||||
/** 默认边长度 */
|
||||
public linkDistance: number = 50;
|
||||
|
||||
@ -59,19 +77,21 @@ export default class ForceLayout<Cfg = any> extends BaseLayout {
|
||||
/** 是否启用web worker。前提是在web worker里执行布局,否则无效 */
|
||||
public workerEnabled: boolean = false;
|
||||
|
||||
public tick: () => void = () => {};
|
||||
public tick: () => void = () => { };
|
||||
|
||||
/** 布局完成回调 */
|
||||
public onLayoutEnd: () => void = () => {};
|
||||
public onLayoutEnd: () => void = () => { };
|
||||
|
||||
/** 布局每一次迭代完成的回调 */
|
||||
public onTick: () => void = () => {};
|
||||
public onTick: () => void = () => { };
|
||||
|
||||
/** 是否正在布局 */
|
||||
private ticking: boolean | undefined = undefined;
|
||||
|
||||
private edgeForce: any;
|
||||
|
||||
private clusterForce: any;
|
||||
|
||||
public getDefaultCfg() {
|
||||
return {
|
||||
center: [0, 0],
|
||||
@ -86,9 +106,15 @@ export default class ForceLayout<Cfg = any> extends BaseLayout {
|
||||
alphaMin: 0.001,
|
||||
alpha: 0.3,
|
||||
collideStrength: 1,
|
||||
tick() {},
|
||||
onLayoutEnd() {}, // 布局完成回调
|
||||
onTick() {}, // 每一迭代布局回调
|
||||
clustering: false,
|
||||
clusterNodeStrength: -1,
|
||||
clusterEdgeStrength: 0.1,
|
||||
clusterEdgeDistance: 100,
|
||||
clusterFociStrength: 0.8,
|
||||
clusterNodeSize: 10,
|
||||
tick() { },
|
||||
onLayoutEnd() { }, // 布局完成回调
|
||||
onTick() { }, // 每一迭代布局回调
|
||||
// 是否启用web worker。前提是在web worker里执行布局,否则无效
|
||||
workerEnabled: false,
|
||||
};
|
||||
@ -137,14 +163,37 @@ export default class ForceLayout<Cfg = any> extends BaseLayout {
|
||||
if (self.nodeStrength) {
|
||||
nodeForce.strength(self.nodeStrength);
|
||||
}
|
||||
simulation = d3Force
|
||||
.forceSimulation()
|
||||
.nodes(nodes)
|
||||
simulation = d3Force.forceSimulation().nodes(nodes);
|
||||
|
||||
if (self.clustering) {
|
||||
const clusterForce = forceInABox() as any;
|
||||
clusterForce
|
||||
.centerX(self.center[0])
|
||||
.centerY(self.center[1])
|
||||
.template('force')
|
||||
.strength(self.clusterFociStrength);
|
||||
if (edges) {
|
||||
clusterForce.links(edges);
|
||||
}
|
||||
if (nodes) {
|
||||
clusterForce.nodes(nodes);
|
||||
}
|
||||
clusterForce
|
||||
.forceLinkDistance(self.clusterEdgeDistance)
|
||||
.forceLinkStrength(self.clusterEdgeStrength)
|
||||
.forceCharge(self.clusterNodeStrength)
|
||||
.forceNodeSize(self.clusterNodeSize);
|
||||
|
||||
self.clusterForce = clusterForce;
|
||||
simulation.force('group', clusterForce);
|
||||
}
|
||||
simulation
|
||||
.force('center', d3Force.forceCenter(self.center[0], self.center[1]))
|
||||
.force('charge', nodeForce)
|
||||
.alpha(alpha)
|
||||
.alphaDecay(alphaDecay)
|
||||
.alphaMin(alphaMin);
|
||||
|
||||
if (self.preventOverlap) {
|
||||
self.overlapProcess(simulation);
|
||||
}
|
||||
@ -204,6 +253,10 @@ export default class ForceLayout<Cfg = any> extends BaseLayout {
|
||||
}
|
||||
} else {
|
||||
if (reloadData) {
|
||||
if (self.clustering && self.clusterForce) {
|
||||
self.clusterForce.nodes(nodes);
|
||||
self.clusterForce.links(edges);
|
||||
}
|
||||
simulation.nodes(nodes);
|
||||
self.edgeForce.links(edges);
|
||||
}
|
@ -9,7 +9,7 @@ import Layout from './layout';
|
||||
import Circular from './circular';
|
||||
import Concentric from './concentric';
|
||||
import Dagre from './dagre';
|
||||
import Force from './force';
|
||||
import Force from './force/force';
|
||||
import G6Force from './g6force';
|
||||
import Fruchterman from './fruchterman';
|
||||
import Grid from './grid';
|
||||
|
@ -561,10 +561,11 @@ export interface GroupNodeIds {
|
||||
|
||||
// Behavior type file
|
||||
export enum G6Event {
|
||||
// common events
|
||||
CLICK = 'click',
|
||||
DBLCLICK = 'dblclick',
|
||||
MOUSEDOWN = 'mousedown',
|
||||
MOUDEUP = 'mouseup',
|
||||
DBLCLICK = 'dblclick',
|
||||
CONTEXTMENU = 'contextmenu',
|
||||
MOUSEENTER = 'mouseenter',
|
||||
MOUSEOUT = 'mouseout',
|
||||
@ -585,57 +586,125 @@ export enum G6Event {
|
||||
FOCUS = 'focus',
|
||||
BLUR = 'blur',
|
||||
|
||||
// touch events
|
||||
TOUCHSTART = 'touchstart',
|
||||
TOUCHMOVE = 'touchmove',
|
||||
TOUCHEND = 'touchend',
|
||||
|
||||
NODE_CLICK = 'node:click',
|
||||
// node events
|
||||
NODE_CONTEXTMENU = 'node:contextmenu',
|
||||
NODE_CLICK = 'node:click',
|
||||
NODE_DBLCLICK = 'node:dblclick',
|
||||
NODE_DRAGSTART = 'node:dragstart',
|
||||
NODE_DRAG = 'node:drag',
|
||||
NODE_DRAGEND = 'node:dragend',
|
||||
NODE_MOUSEDOWN = 'node:mousedown',
|
||||
NODE_MOUSEUP = 'node:mouseup',
|
||||
NODE_MOUSEENTER = 'node:mouseenter',
|
||||
NODE_MOUSELEAVE = 'node:mouseleave',
|
||||
NODE_MOUSEMOVE = 'node:mousemove',
|
||||
NODE_MOUSEOUT = 'node:mouseout',
|
||||
NODE_MOUSEOVER = 'node:mouseover',
|
||||
NODE_DROP = 'node:drop',
|
||||
NODE_DRAGOVER = 'node:dragover',
|
||||
NODE_DRAGENTER = 'node:dragenter',
|
||||
NODE_DRAGLEAVE = 'node:dragleave',
|
||||
|
||||
EDGE_CLICK = 'edge:click',
|
||||
EDGE_CONTEXTMENU = 'edge:contextmenu',
|
||||
EDGE_DBLCLICK = 'edge:dblclick',
|
||||
EDGE_MOUSEENTER = 'edge:mouseenter',
|
||||
EDGE_MOUSELEAVE = 'edge:mouseleave',
|
||||
EDGE_MOUSEMOVE = 'edge:mousemove',
|
||||
|
||||
CANVAS_MOUSEDOWN = 'canvas:mousedown',
|
||||
CANVAS_MOUSEMOVE = 'canvas:mousemove',
|
||||
CANVAS_MOUSEUP = 'canvas:mouseup',
|
||||
CANVAS_CLICK = 'canvas:click',
|
||||
CANVAS_MOSUELEAVE = 'canvas:mouseleave',
|
||||
CANVAS_DRAGSTART = 'canvas:dragstart',
|
||||
CANVAS_DRAG = 'canvas:drag',
|
||||
CANVAS_DRAGEND = 'canvas:dragend',
|
||||
CANVAS_DRAGLEAVE = 'canvas:dragleave',
|
||||
CANVAS_DROP = 'canvas:drop',
|
||||
|
||||
// combo
|
||||
COMBO_CLICK = 'combo:click',
|
||||
NODE_DRAGSTART = 'node:dragstart',
|
||||
NODE_DRAG = 'node:drag',
|
||||
NODE_DRAGEND = 'node:dragend',
|
||||
// combo, extends from nodes
|
||||
COMBO_CONTEXTMENU = 'combo:contextmenu',
|
||||
COMBO_CLICK = 'combo:click',
|
||||
COMBO_DBLCLICK = 'combo:dblclick',
|
||||
COMBO_DRAGSTART = 'combo:dragstart',
|
||||
COMBO_DRAG = 'combo:drag',
|
||||
COMBO_DRAGEND = 'combo:dragend',
|
||||
COMBO_MOUSEDOWN = 'combo:mousedown',
|
||||
COMBO_MOUSEUP = 'combo:mouseup',
|
||||
COMBO_MOUSEENTER = 'combo:mouseenter',
|
||||
COMBO_MOUSELEAVE = 'combo:mouseleave',
|
||||
COMBO_MOUSEMOVE = 'combo:mousemove',
|
||||
COMBO_MOUSEOUT = 'combo:mouseout',
|
||||
COMBO_MOUSEOVER = 'combo:mouseover',
|
||||
COMBO_DROP = 'combo:drop',
|
||||
COMBO_DRAGOVER = 'combo:dragover',
|
||||
COMBO_DRAGLEAVE = 'combo:dragleave',
|
||||
COMBO_DRAGENTER = 'combo:dragenter',
|
||||
COMBO_DRAGLEAVE = 'combo:dragleave',
|
||||
COMBO_DRAGSTART = 'combo:dragstart',
|
||||
COMBO_DRAG = 'combo:drag',
|
||||
COMBO_DRAGEND = 'combo:dragend',
|
||||
|
||||
// edge events
|
||||
EDGE_CONTEXTMENU = 'edge:contextmenu',
|
||||
EDGE_CLICK = 'edge:click',
|
||||
EDGE_DBLCLICK = 'edge:dblclick',
|
||||
EDGE_MOUSEDOWN = 'edge:mousedown',
|
||||
EDGE_MOUSEUP = 'edge:mouseup',
|
||||
EDGE_MOUSEENTER = 'edge:mouseenter',
|
||||
EDGE_MOUSELEAVE = 'edge:mouseleave',
|
||||
EDGE_MOUSEMOVE = 'edge:mousemove',
|
||||
EDGE_MOUSEOUT = 'edge:mouseout',
|
||||
EDGE_MOUSEOVER = 'edge:mouseover',
|
||||
EDGE_DROP = 'edge:drop',
|
||||
EDGE_DRAGOVER = 'edge:dragover',
|
||||
EDGE_DRAGENTER = 'edge:dragenter',
|
||||
EDGE_DRAGLEAVE = 'edge:dragleave',
|
||||
|
||||
// canvas events
|
||||
CANVAS_CONTEXTMENU = 'canvas:contextmenu',
|
||||
CANVAS_CLICK = 'canvas:click',
|
||||
CANVAS_DBLCLICK = 'canvas:dblclick',
|
||||
CANVAS_MOUSEDOWN = 'canvas:mousedown',
|
||||
CANVAS_MOUSEUP = 'canvas:mouseup',
|
||||
CANVAS_MOUSEENTER = 'canvas:mouseenter',
|
||||
CANVAS_MOUSELEAVE = 'canvas:mouseleave',
|
||||
CANVAS_MOUSEMOVE = 'canvas:mousemove',
|
||||
CANVAS_MOUSEOUT = 'canvas:mouseout',
|
||||
CANVAS_MOUSEOVER = 'canvas:mouseover',
|
||||
CANVAS_DROP = 'canvas:drop',
|
||||
CANVAS_DRAGENTER = 'canvas:dragenter',
|
||||
CANVAS_DRAGLEAVE = 'canvas:dragleave',
|
||||
CANVAS_DRAGSTART = 'canvas:dragstart',
|
||||
CANVAS_DRAG = 'canvas:drag',
|
||||
CANVAS_DRAGEND = 'canvas:dragend',
|
||||
|
||||
// timing events
|
||||
BEFORERENDER = 'beforerender',
|
||||
AFTERRENDER = 'afterrender',
|
||||
BEFOREADDITEM = 'beforeadditem',
|
||||
AFTERADDITEM = 'afteradditem',
|
||||
BEFOREREMOVEITEM = 'beforeremoveitem',
|
||||
AFTERREMOVEITEM = 'afterremoveitem',
|
||||
BEFOREUPDATEITEM = 'beforeupdateitem',
|
||||
AFTERUPDATEITEM = 'afterupdateitem',
|
||||
BEFOREITEMVISIBILITYCHANGE = 'beforeitemvisibilitychange',
|
||||
AFTERITEMVISIBILITYCHANGE = 'afteritemvisibilitychange',
|
||||
BEFOREITEMSTATECHANGE = 'beforeitemstatechange',
|
||||
AFTERITEMSTATECHANGE = 'afteritemstatechange',
|
||||
BEFOREITEMREFRESH = 'beforeitemrefresh',
|
||||
AFTERITEMREFRESH = 'afteritemrefresh',
|
||||
BEFOREITEMSTATESCLEAR = 'beforeitemstatesclear',
|
||||
AFTERITEMSTATESCLEAR = 'afteritemstatesclear',
|
||||
BEFOREMODECHANGE = 'beforemodechange',
|
||||
AFTERMODECHANGE = 'aftermodechange',
|
||||
BEFORELAYOUT = 'beforelayout',
|
||||
AFTERLAYOUT = 'afterlayout',
|
||||
BEFORECREATEEDGE = 'beforecreateedge',
|
||||
AFTERCREATEEDGE = 'aftercreateedge',
|
||||
BEFOREGRAPHREFRESHPOSITION = 'beforegraphrefreshposition',
|
||||
AFTERGRAPHREFRESHPOSITION = 'aftergraphrefreshposition',
|
||||
BEFOREGRAPHREFRESH = 'beforegraphrefresh',
|
||||
AFTERGRAPHREFRESH = 'aftergraphrefresh',
|
||||
BEFOREANIMATE = 'beforeanimate',
|
||||
AFTERANIMATE = 'afteranimate',
|
||||
BEFOREPAINT = 'beforepaint',
|
||||
AFTERPAINT = 'afterpaint',
|
||||
|
||||
GRAPHSTATECHANGE = 'graphstatechange',
|
||||
AFTERACTIVATERELATIONS = 'afteractivaterelations',
|
||||
NODESELECTCHANGE = 'nodeselectchange',
|
||||
TOOLTIPCHANGE = 'tooltipchange',
|
||||
WHEELZOOM = 'wheelzoom',
|
||||
VIEWPORTCHANGE = 'viewportchange',
|
||||
DRAGNODEEND = 'dragnodeend',
|
||||
STACKCHANGE = 'stackchange'
|
||||
}
|
||||
|
||||
|
||||
export type DefaultBehaviorType = IG6GraphEvent | string | number | object;
|
||||
|
||||
export interface BehaviorOption {
|
||||
|
76
stories/Issues/addGroup/index.tsx
Normal file
76
stories/Issues/addGroup/index.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import G6 from '../../../src';
|
||||
import { IGraph } from '../../../src/interface/graph';
|
||||
import { G6Event } from '../../../src/types';
|
||||
|
||||
G6.registerNode(
|
||||
"card-node",
|
||||
{
|
||||
drawShape: function drawShape(cfg, group) {
|
||||
const shape = group.addShape("rect", {
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 200,
|
||||
height: 100,
|
||||
fill: "#f00",
|
||||
}
|
||||
});
|
||||
group.addGroup();
|
||||
return shape;
|
||||
},
|
||||
update: undefined
|
||||
}
|
||||
);
|
||||
|
||||
export default () => {
|
||||
const graphContainer = useRef(null);
|
||||
let graph: IGraph = null;
|
||||
|
||||
// 图初始化
|
||||
useEffect(() => {
|
||||
if (!graph) {
|
||||
graph = new G6.Graph({
|
||||
container: graphContainer.current,
|
||||
width: 500,
|
||||
height: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: "node1", // String,该节点存在则必须,节点的唯一标识
|
||||
type: "card-node",
|
||||
placeholder: "暂无内容请",
|
||||
title: "节点1",
|
||||
x: 100,
|
||||
y: 100
|
||||
},
|
||||
{
|
||||
id: "node2",
|
||||
type: "card-node",
|
||||
placeholder: "暂无内容请",
|
||||
title: "节点2",
|
||||
x: 3000,
|
||||
y: -100
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
|
||||
graph.on('canvas:click', (evt) => {
|
||||
const node = graph.findById("node2");
|
||||
graph.update(node, { placeholder: "new placeholder" });
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={graphContainer} className={'graph-container'} />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -9,6 +9,7 @@ import ForceLayout from './forceLayout';
|
||||
import GGEditorNode from './ggeditorNode';
|
||||
import MultiLayout from './forceLayout/multiLayout'
|
||||
import EdgeStatus from './edgeStatus'
|
||||
import AddGroup from './addGroup'
|
||||
|
||||
export default { title: 'Issues' };
|
||||
|
||||
@ -22,3 +23,4 @@ storiesOf('Issues', module)
|
||||
.add('ggeditor node issue', () => <GGEditorNode />)
|
||||
.add('multi layout', () => <MultiLayout />)
|
||||
.add('edge status', () => <EdgeStatus />)
|
||||
.add('add group', () => <AddGroup />)
|
||||
|
70
stories/Layout/component/force-clustering-layout.tsx
Normal file
70
stories/Layout/component/force-clustering-layout.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import G6 from '../../../src';
|
||||
import { IGraph } from '../../../src/interface/graph';
|
||||
|
||||
let graph: IGraph = null;
|
||||
let colorMap = {
|
||||
2012: 'blue',
|
||||
2013: 'yellow',
|
||||
2014: 'red',
|
||||
2015: 'gray',
|
||||
2016: 'black',
|
||||
};
|
||||
|
||||
const ForceClusteringLayout = () => {
|
||||
const container = React.useRef();
|
||||
useEffect(() => {
|
||||
if (!graph) {
|
||||
const graph = new G6.Graph({
|
||||
container: container.current as string | HTMLElement,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
layout: {
|
||||
type: 'force',
|
||||
clustering: true,
|
||||
clusterNodeStrength: -10,
|
||||
clusterEdgeDistance: 200,
|
||||
clusterNodeSize: 20,
|
||||
// nodeStrength: -100,
|
||||
clusterFociStrength: 1.2,
|
||||
nodeSpacing: 5,
|
||||
preventOverlap: true,
|
||||
},
|
||||
defaultNode: {
|
||||
size: 15,
|
||||
color: '#5B8FF9',
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
fill: '#C6E5FF',
|
||||
},
|
||||
},
|
||||
defaultEdge: {
|
||||
size: 1,
|
||||
color: '#e2e2e2',
|
||||
},
|
||||
modes: {
|
||||
default: ['drag-canvas'],
|
||||
},
|
||||
});
|
||||
|
||||
fetch(
|
||||
'https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json',
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
graph.data(data);
|
||||
data.nodes.forEach((i) => {
|
||||
i.cluster = i.year;
|
||||
i.style = Object.assign(i.style || {}, {
|
||||
fill: colorMap[i.year],
|
||||
});
|
||||
});
|
||||
graph.render();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return <div ref={container}></div>;
|
||||
};
|
||||
|
||||
export default ForceClusteringLayout;
|
@ -10,6 +10,7 @@ import ComboForceLayout from './component/combo-force-layout';
|
||||
import ForceLayout from './component/force-layout';
|
||||
import CompactBox from './component/compact-box';
|
||||
import AutoLayout from './component/auto-layout';
|
||||
import ForceClusteringLayout from './component/force-clustering-layout';
|
||||
|
||||
export default { title: 'Layout' };
|
||||
|
||||
@ -24,3 +25,4 @@ storiesOf('Layout', module)
|
||||
.add('force layout', () => <ForceLayout />)
|
||||
.add('compactbox layout', () => <CompactBox />)
|
||||
.add('auto layout', () => <AutoLayout />)
|
||||
.add('force clustering layout', () => <ForceClusteringLayout />)
|
||||
|
Loading…
Reference in New Issue
Block a user