mirror of
https://gitee.com/antv/g6.git
synced 2024-11-29 18:28:19 +08:00
feat: unified datachange from changeData, addData, updateData, and removeData; feat: item definition and first drawing (#4259)
* feat: unified datachange from changeData, addData, updateData, and removeData; feat: item definition and first drawing * feat: update canvas while addData; feat: update canvas while updateData (node); chore: unified additems, removeitems, updateitems to be itemchange hook * feat: node and edge updating and drawing * chore: neaten * feat: draw and update labels for node and edge * feat: icon for edge; feat: custom node and edge and register to lib * feat: state related API for graph and item * chore: update notes * feat: state styles for item * chore: update tests
This commit is contained in:
parent
bb82593eb8
commit
875a9eb39b
1
packages/g6/docs/api-ts/.nojekyll
Normal file
1
packages/g6/docs/api-ts/.nojekyll
Normal file
@ -0,0 +1 @@
|
||||
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
|
@ -1,10 +1,4 @@
|
||||
---
|
||||
id: "index"
|
||||
title: "@antv/g6"
|
||||
sidebar_label: "README"
|
||||
---
|
||||
|
||||
[@antv/g6](index.md) › [Globals](globals.md)
|
||||
@antv/g6 / [Exports](modules.md)
|
||||
|
||||
# G6:图分析引擎
|
||||
|
@ -1,834 +0,0 @@
|
||||
---
|
||||
id: "_graph_.graph"
|
||||
title: "Graph"
|
||||
sidebar_label: "Graph"
|
||||
---
|
||||
|
||||
[@antv/g6](../index.md) › [Globals](../globals.md) › ["graph"](../modules/_graph_.md) › [Graph](_graph_.graph.md)
|
||||
|
||||
## Type parameters
|
||||
|
||||
▪ **B**: *BehaviorRegistry*
|
||||
|
||||
## Hierarchy
|
||||
|
||||
* EventEmitter
|
||||
|
||||
↳ **Graph**
|
||||
|
||||
## Implements
|
||||
|
||||
* IGraph‹B›
|
||||
|
||||
## Index
|
||||
|
||||
### Constructors
|
||||
|
||||
* [constructor](_graph_.graph.md#constructor)
|
||||
|
||||
### Properties
|
||||
|
||||
* [hooks](_graph_.graph.md#hooks)
|
||||
|
||||
### Methods
|
||||
|
||||
* [addBehaviors](_graph_.graph.md#addbehaviors)
|
||||
* [addItem](_graph_.graph.md#additem)
|
||||
* [clear](_graph_.graph.md#clear)
|
||||
* [collapseCombo](_graph_.graph.md#collapsecombo)
|
||||
* [createCombo](_graph_.graph.md#createcombo)
|
||||
* [emit](_graph_.graph.md#emit)
|
||||
* [expandCombo](_graph_.graph.md#expandcombo)
|
||||
* [findIdByState](_graph_.graph.md#findidbystate)
|
||||
* [fitCenter](_graph_.graph.md#fitcenter)
|
||||
* [fitView](_graph_.graph.md#fitview)
|
||||
* [focusItem](_graph_.graph.md#focusitem)
|
||||
* [getComboData](_graph_.graph.md#getcombodata)
|
||||
* [getEdgeData](_graph_.graph.md#getedgedata)
|
||||
* [getEvents](_graph_.graph.md#getevents)
|
||||
* [getNodeData](_graph_.graph.md#getnodedata)
|
||||
* [getSpecification](_graph_.graph.md#getspecification)
|
||||
* [hideItem](_graph_.graph.md#hideitem)
|
||||
* [layout](_graph_.graph.md#layout)
|
||||
* [move](_graph_.graph.md#move)
|
||||
* [moveTo](_graph_.graph.md#moveto)
|
||||
* [off](_graph_.graph.md#off)
|
||||
* [on](_graph_.graph.md#on)
|
||||
* [once](_graph_.graph.md#once)
|
||||
* [read](_graph_.graph.md#read)
|
||||
* [removeBehaviors](_graph_.graph.md#removebehaviors)
|
||||
* [removeItem](_graph_.graph.md#removeitem)
|
||||
* [setItemState](_graph_.graph.md#setitemstate)
|
||||
* [setMode](_graph_.graph.md#setmode)
|
||||
* [showItem](_graph_.graph.md#showitem)
|
||||
* [uncombo](_graph_.graph.md#uncombo)
|
||||
* [updateBehavior](_graph_.graph.md#updatebehavior)
|
||||
* [updateItem](_graph_.graph.md#updateitem)
|
||||
* [updateSpecification](_graph_.graph.md#updatespecification)
|
||||
* [zoom](_graph_.graph.md#zoom)
|
||||
* [zoomTo](_graph_.graph.md#zoomto)
|
||||
|
||||
## Constructors
|
||||
|
||||
### constructor
|
||||
|
||||
\+ **new Graph**(`spec`: Specification‹B›): *[Graph](_graph_.graph.md)*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:26](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L26)*
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`spec` | Specification‹B› |
|
||||
|
||||
**Returns:** *[Graph](_graph_.graph.md)*
|
||||
|
||||
## Properties
|
||||
|
||||
### hooks
|
||||
|
||||
• **hooks**: *Hooks*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:19](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L19)*
|
||||
|
||||
## Methods
|
||||
|
||||
### addBehaviors
|
||||
|
||||
▸ **addBehaviors**(`behaviors`: BehaviorOptionsOf‹B›[], `modes`: string | string[]): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:374](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L374)*
|
||||
|
||||
Add behavior(s) to mode(s).
|
||||
|
||||
**`group`** Interaction
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`behaviors` | BehaviorOptionsOf‹B›[] | behavior names or configs |
|
||||
`modes` | string | string[] | mode names |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### addItem
|
||||
|
||||
▸ **addItem**(`itemType`: ITEM_TYPE, `models`: NodeUserModel | EdgeUserModel | ComboUserModel | NodeUserModel[] | EdgeUserModel[] | ComboUserModel[], `stack?`: boolean): *boolean*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:235](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L235)*
|
||||
|
||||
Add an item or items to the graph.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`itemType` | ITEM_TYPE | item type |
|
||||
`models` | NodeUserModel | EdgeUserModel | ComboUserModel | NodeUserModel[] | EdgeUserModel[] | ComboUserModel[] | - |
|
||||
`stack?` | boolean | whether push this operation to stack |
|
||||
|
||||
**Returns:** *boolean*
|
||||
|
||||
whether success
|
||||
|
||||
___
|
||||
|
||||
### clear
|
||||
|
||||
▸ **clear**(): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:102](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L102)*
|
||||
|
||||
Clear the graph, means remove all the items on the graph.
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### collapseCombo
|
||||
|
||||
▸ **collapseCombo**(`comboId`: string | number, `stack?`: boolean): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:329](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L329)*
|
||||
|
||||
Collapse a combo.
|
||||
|
||||
**`group`** Combo
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`comboId` | string | number | combo id or item |
|
||||
`stack?` | boolean | - |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### createCombo
|
||||
|
||||
▸ **createCombo**(`combo`: string | ComboUserModel, `childrenIds`: string[], `stack?`: boolean): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:313](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L313)*
|
||||
|
||||
Create a new combo with existing child nodes and combos.
|
||||
|
||||
**`group`** Combo
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`combo` | string | ComboUserModel | combo ID or Combo model |
|
||||
`childrenIds` | string[] | id array of children of the new combo |
|
||||
`stack?` | boolean | - |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### emit
|
||||
|
||||
▸ **emit**(`evt`: string, ...`args`: any[]): *void*
|
||||
|
||||
*Inherited from [Graph](_graph_.graph.md).[emit](_graph_.graph.md#emit)*
|
||||
|
||||
Defined in node_modules/_@antv_event-emitter@0.1.3@@antv/event-emitter/lib/index.d.ts:25
|
||||
|
||||
触发一个事件
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`evt` | string | - |
|
||||
`...args` | any[] | |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### expandCombo
|
||||
|
||||
▸ **expandCombo**(`comboId`: string | number, `stack?`: boolean): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:337](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L337)*
|
||||
|
||||
Expand a combo.
|
||||
|
||||
**`group`** Combo
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`comboId` | string | number |
|
||||
`stack?` | boolean |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### findIdByState
|
||||
|
||||
▸ **findIdByState**(`itemType`: ITEM_TYPE, `state`: string, `additionalFilter?`: function): *(string | number)[]*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:223](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L223)*
|
||||
|
||||
Find items which has the state.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
▪ **itemType**: *ITEM_TYPE*
|
||||
|
||||
item type
|
||||
|
||||
▪ **state**: *string*
|
||||
|
||||
state name
|
||||
|
||||
▪`Optional` **additionalFilter**: *function*
|
||||
|
||||
additional filter function
|
||||
|
||||
▸ (`item`: NodeModel | EdgeModel | ComboModel): *boolean*
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`item` | NodeModel | EdgeModel | ComboModel |
|
||||
|
||||
**Returns:** *(string | number)[]*
|
||||
|
||||
items that is the type and has the state
|
||||
|
||||
___
|
||||
|
||||
### fitCenter
|
||||
|
||||
▸ **fitCenter**(`animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:172](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L172)*
|
||||
|
||||
Fit the graph center to the view center.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### fitView
|
||||
|
||||
▸ **fitView**(`padding?`: Padding, `rules?`: FitViewRules, `animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:163](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L163)*
|
||||
|
||||
Fit the graph content to the view.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`padding?` | Padding | padding while fitting |
|
||||
`rules?` | FitViewRules | rules for fitting |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### focusItem
|
||||
|
||||
▸ **focusItem**(`ids`: string | number | (string | number)[], `animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:182](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L182)*
|
||||
|
||||
Move the graph to make the item align the view center.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`ids` | string | number | (string | number)[] | - |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### getComboData
|
||||
|
||||
▸ **getComboData**(`condition`: string | Function): *ComboModel | undefined*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:212](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L212)*
|
||||
|
||||
Find an combo's inner data according to id or function.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`condition` | string | Function | id or condition function |
|
||||
|
||||
**Returns:** *ComboModel | undefined*
|
||||
|
||||
result combo
|
||||
|
||||
___
|
||||
|
||||
### getEdgeData
|
||||
|
||||
▸ **getEdgeData**(`condition`: string | Function): *EdgeModel | undefined*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:203](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L203)*
|
||||
|
||||
Find an edge's inner data according to id or function.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`condition` | string | Function | id or condition function |
|
||||
|
||||
**Returns:** *EdgeModel | undefined*
|
||||
|
||||
result edge
|
||||
|
||||
___
|
||||
|
||||
### getEvents
|
||||
|
||||
▸ **getEvents**(): *Record‹string, EventType[]›*
|
||||
|
||||
*Inherited from [Graph](_graph_.graph.md).[getEvents](_graph_.graph.md#getevents)*
|
||||
|
||||
Defined in node_modules/_@antv_event-emitter@0.1.3@@antv/event-emitter/lib/index.d.ts:32
|
||||
|
||||
**Returns:** *Record‹string, EventType[]›*
|
||||
|
||||
___
|
||||
|
||||
### getNodeData
|
||||
|
||||
▸ **getNodeData**(`condition`: string | Function): *NodeModel | undefined*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:194](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L194)*
|
||||
|
||||
Find a node's inner data according to id or function.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`condition` | string | Function | id or condition function |
|
||||
|
||||
**Returns:** *NodeModel | undefined*
|
||||
|
||||
result node
|
||||
|
||||
___
|
||||
|
||||
### getSpecification
|
||||
|
||||
▸ **getSpecification**(): *Specification‹B›*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:80](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L80)*
|
||||
|
||||
Get the copy of specs(configurations).
|
||||
|
||||
**Returns:** *Specification‹B›*
|
||||
|
||||
graph specs
|
||||
|
||||
___
|
||||
|
||||
### hideItem
|
||||
|
||||
▸ **hideItem**(`ids`: string | number | (string | number)[]): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:290](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L290)*
|
||||
|
||||
Hide the item(s).
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`ids` | string | number | (string | number)[] |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### layout
|
||||
|
||||
▸ **layout**(`cfg?`: LayoutCommonConfig, `align?`: GraphAlignment, `canvasPoint?`: Point, `stack?`: boolean): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:351](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L351)*
|
||||
|
||||
Layout the graph (with current configurations if cfg is not assigned).
|
||||
|
||||
**`group`** Layout
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`cfg?` | LayoutCommonConfig | layout configurations. if assigned, the layout spec of the graph will be updated in the same time |
|
||||
`align?` | GraphAlignment | align the result |
|
||||
`canvasPoint?` | Point | align the result |
|
||||
`stack?` | boolean | push it into stack |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### move
|
||||
|
||||
▸ **move**(`dx`: number, `dy`: number, `animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:114](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L114)*
|
||||
|
||||
Move the graph with a relative vector.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`dx` | number | x of the relative vector |
|
||||
`dy` | number | y of the relative vector |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### moveTo
|
||||
|
||||
▸ **moveTo**(`x`: number, `y`: number, `alignment`: GraphAlignment, `animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:127](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L127)*
|
||||
|
||||
Move the graph and align to a point.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`x` | number | position on the canvas to align |
|
||||
`y` | number | position on the canvas to align |
|
||||
`alignment` | GraphAlignment | alignment of the graph content |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### off
|
||||
|
||||
▸ **off**(`evt?`: string, `callback?`: Function): *this*
|
||||
|
||||
*Inherited from [Graph](_graph_.graph.md).[off](_graph_.graph.md#off)*
|
||||
|
||||
Defined in node_modules/_@antv_event-emitter@0.1.3@@antv/event-emitter/lib/index.d.ts:31
|
||||
|
||||
取消监听一个事件,或者一个channel
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`evt?` | string | - |
|
||||
`callback?` | Function | |
|
||||
|
||||
**Returns:** *this*
|
||||
|
||||
___
|
||||
|
||||
### on
|
||||
|
||||
▸ **on**(`evt`: string, `callback`: Function, `once?`: boolean): *this*
|
||||
|
||||
*Inherited from [Graph](_graph_.graph.md).[on](_graph_.graph.md#on)*
|
||||
|
||||
Defined in node_modules/_@antv_event-emitter@0.1.3@@antv/event-emitter/lib/index.d.ts:13
|
||||
|
||||
监听一个事件
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`evt` | string | - |
|
||||
`callback` | Function | - |
|
||||
`once?` | boolean | |
|
||||
|
||||
**Returns:** *this*
|
||||
|
||||
___
|
||||
|
||||
### once
|
||||
|
||||
▸ **once**(`evt`: string, `callback`: Function): *this*
|
||||
|
||||
*Inherited from [Graph](_graph_.graph.md).[once](_graph_.graph.md#once)*
|
||||
|
||||
Defined in node_modules/_@antv_event-emitter@0.1.3@@antv/event-emitter/lib/index.d.ts:19
|
||||
|
||||
监听一个事件一次
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`evt` | string | - |
|
||||
`callback` | Function | |
|
||||
|
||||
**Returns:** *this*
|
||||
|
||||
___
|
||||
|
||||
### read
|
||||
|
||||
▸ **read**(`data`: GraphData): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:91](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L91)*
|
||||
|
||||
Input data and render the graph.
|
||||
If there is old data, diffs and changes it.
|
||||
|
||||
**`group`** Data
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`data` | GraphData |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### removeBehaviors
|
||||
|
||||
▸ **removeBehaviors**(`behaviorKeys`: string[], `modes`: string | string[]): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:394](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L394)*
|
||||
|
||||
Remove behavior(s) from mode(s).
|
||||
|
||||
**`group`** Interaction
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`behaviorKeys` | string[] | - |
|
||||
`modes` | string | string[] | mode names |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### removeItem
|
||||
|
||||
▸ **removeItem**(`itemType`: ITEM_TYPE, `ids`: string | number | (string | number)[], `stack?`: boolean): *boolean*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:251](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L251)*
|
||||
|
||||
Remove an item or items from the graph.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`itemType` | ITEM_TYPE | - |
|
||||
`ids` | string | number | (string | number)[] | - |
|
||||
`stack?` | boolean | whether push this operation to stack |
|
||||
|
||||
**Returns:** *boolean*
|
||||
|
||||
whether success
|
||||
|
||||
___
|
||||
|
||||
### setItemState
|
||||
|
||||
▸ **setItemState**(`ids`: string | number | (string | number)[], `state`: string, `value`: boolean): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:301](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L301)*
|
||||
|
||||
Set state for the item.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`ids` | string | number | (string | number)[] | - |
|
||||
`state` | string | the state name |
|
||||
`value` | boolean | state value |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### setMode
|
||||
|
||||
▸ **setMode**(`mode`: string): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:363](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L363)*
|
||||
|
||||
Switch mode.
|
||||
|
||||
**`group`** Interaction
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`mode` | string | mode name |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### showItem
|
||||
|
||||
▸ **showItem**(`ids`: string | number | (string | number)[]): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:281](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L281)*
|
||||
|
||||
Show the item(s).
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`ids` | string | number | (string | number)[] |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### uncombo
|
||||
|
||||
▸ **uncombo**(`comboId`: string | number, `stack?`: boolean): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:321](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L321)*
|
||||
|
||||
dissolve combo
|
||||
|
||||
**`group`** Combo
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`comboId` | string | number |
|
||||
`stack?` | boolean |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### updateBehavior
|
||||
|
||||
▸ **updateBehavior**(`behavior`: BehaviorObjectOptionsOf‹B›, `mode?`: string): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:417](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L417)*
|
||||
|
||||
Update a behavior on a mode.
|
||||
|
||||
**`group`** Interaction
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`behavior` | BehaviorObjectOptionsOf‹B› | behavior configs, whose name indicates the behavior to be updated |
|
||||
`mode?` | string | mode name |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### updateItem
|
||||
|
||||
▸ **updateItem**(`itemType`: ITEM_TYPE, `models`: Partial‹NodeUserModel› | Partial‹EdgeUserModel› | Partial‹ComboUserModel | Partial‹NodeUserModel›[] | Partial‹EdgeUserModel›[] | Partial‹ComboUserModel›[]›, `stack?`: boolean): *boolean*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:266](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L266)*
|
||||
|
||||
Update an item or items on the graph.
|
||||
|
||||
**`group`** Item
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`itemType` | ITEM_TYPE | - |
|
||||
`models` | Partial‹NodeUserModel› | Partial‹EdgeUserModel› | Partial‹ComboUserModel | Partial‹NodeUserModel›[] | Partial‹EdgeUserModel›[] | Partial‹ComboUserModel›[]› | - |
|
||||
`stack?` | boolean | 本次操作是否入栈,默认为 true |
|
||||
|
||||
**Returns:** *boolean*
|
||||
|
||||
___
|
||||
|
||||
### updateSpecification
|
||||
|
||||
▸ **updateSpecification**(`spec`: Specification‹B›): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:72](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L72)*
|
||||
|
||||
Update the specs(configurations).
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type |
|
||||
------ | ------ |
|
||||
`spec` | Specification‹B› |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### zoom
|
||||
|
||||
▸ **zoom**(`ratio`: number, `center?`: Point, `animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:139](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L139)*
|
||||
|
||||
Zoom the graph with a relative ratio.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`ratio` | number | relative ratio to zoom |
|
||||
`center?` | Point | zoom center |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
||||
|
||||
___
|
||||
|
||||
### zoomTo
|
||||
|
||||
▸ **zoomTo**(`toRatio`: number, `center?`: Point, `animateCfg?`: AnimateCfg): *void*
|
||||
|
||||
*Defined in [src/runtime/graph.ts:151](https://github.com/antvis/G6/blob/v5/packages/g6/src/runtime/graph.ts#L151)*
|
||||
|
||||
Zoom the graph to a specified ratio.
|
||||
|
||||
**`group`** View
|
||||
|
||||
**Parameters:**
|
||||
|
||||
Name | Type | Description |
|
||||
------ | ------ | ------ |
|
||||
`toRatio` | number | specified ratio |
|
||||
`center?` | Point | zoom center |
|
||||
`animateCfg?` | AnimateCfg | animation configurations |
|
||||
|
||||
**Returns:** *void*
|
1115
packages/g6/docs/api-ts/classes/default.md
Normal file
1115
packages/g6/docs/api-ts/classes/default.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,13 +0,0 @@
|
||||
---
|
||||
id: "globals"
|
||||
title: "@antv/g6"
|
||||
sidebar_label: "Globals"
|
||||
---
|
||||
|
||||
[@antv/g6](index.md) › [Globals](globals.md)
|
||||
|
||||
## Index
|
||||
|
||||
### Modules
|
||||
|
||||
* ["graph"](modules/_graph_.md)
|
9
packages/g6/docs/api-ts/modules.md
Normal file
9
packages/g6/docs/api-ts/modules.md
Normal file
@ -0,0 +1,9 @@
|
||||
[@antv/g6](README.md) / Exports
|
||||
|
||||
# @antv/g6
|
||||
|
||||
## Table of contents
|
||||
|
||||
### Classes
|
||||
|
||||
- [default](classes/default.md)
|
@ -1,13 +0,0 @@
|
||||
---
|
||||
id: "_graph_"
|
||||
title: "graph"
|
||||
sidebar_label: "graph"
|
||||
---
|
||||
|
||||
[@antv/g6](../index.md) › [Globals](../globals.md) › ["graph"](_graph_.md)
|
||||
|
||||
## Index
|
||||
|
||||
### Classes
|
||||
|
||||
* [Graph](../classes/_graph_.graph.md)
|
@ -43,7 +43,7 @@
|
||||
"lint:src": "eslint --ext .ts --format=pretty \"./src\"",
|
||||
"prettier": "prettier -c --write \"**/*\"",
|
||||
"test": "jest",
|
||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/behavior-spec.ts",
|
||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/item-spec.ts",
|
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
||||
"watch": "father build -w"
|
||||
},
|
||||
@ -59,8 +59,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g": "^5.15.7",
|
||||
"@antv/g-canvas": "^1.9.28",
|
||||
"@antv/g-svg": "^1.8.36",
|
||||
"@antv/graphlib": "^2.0.0-alpha.0",
|
||||
"@antv/util": "~2.0.5"
|
||||
"@antv/util": "~2.0.5",
|
||||
"typedoc-plugin-markdown": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^25.2.1",
|
||||
|
@ -1,5 +1,94 @@
|
||||
import Item from "./item";
|
||||
import { Group } from '@antv/g';
|
||||
import { ID } from '@antv/graphlib';
|
||||
import { EdgeDisplayModel, EdgeModel } from '../types';
|
||||
import { EdgeModelData } from '../types/edge';
|
||||
import { DisplayMapper, ITEM_TYPE, State } from '../types/item';
|
||||
import { updateShapes } from '../util/shape';
|
||||
import Item from './item';
|
||||
import Node from './node';
|
||||
|
||||
interface IProps {
|
||||
model: EdgeModel;
|
||||
renderExtensions: any; // TODO: type
|
||||
containerGroup: Group;
|
||||
mapper: DisplayMapper;
|
||||
stateMapper: {
|
||||
[stateName: string]: DisplayMapper
|
||||
};
|
||||
sourceItem: Node;
|
||||
targetItem: Node;
|
||||
}
|
||||
|
||||
export default class Edge extends Item {
|
||||
public destroyed: boolean = false;
|
||||
// inner data model
|
||||
public model: EdgeModel;
|
||||
// display data model
|
||||
public displayModel: EdgeDisplayModel;
|
||||
/** Set to different value in implements */
|
||||
public type: ITEM_TYPE = 'edge';
|
||||
public sourceItem: Node;
|
||||
public targetItem: Node;
|
||||
|
||||
}
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.type = 'edge';
|
||||
this.init(props);
|
||||
const { sourceItem, targetItem } = props;
|
||||
this.sourceItem = sourceItem;
|
||||
this.targetItem = targetItem;
|
||||
this.draw(this.displayModel);
|
||||
}
|
||||
public draw(displayModel: EdgeDisplayModel, diffData?: { previous: EdgeModelData; current: EdgeModelData }, diffState?: { previous: State[], current: State[] }) {
|
||||
// get the end points
|
||||
const sourceBBox = this.sourceItem.getKeyBBox();
|
||||
const targetBBox = this.targetItem.getKeyBBox();
|
||||
const sourcePoint = {
|
||||
x: sourceBBox.center[0],
|
||||
y: sourceBBox.center[1],
|
||||
};
|
||||
const targetPoint = {
|
||||
x: targetBBox.center[0],
|
||||
y: targetBBox.center[1],
|
||||
};
|
||||
const shapeMap = this.renderExt.draw(
|
||||
this.displayModel,
|
||||
sourcePoint,
|
||||
targetPoint,
|
||||
this.shapeMap,
|
||||
diffData,
|
||||
diffState
|
||||
);
|
||||
|
||||
// add shapes to group, and update shapeMap
|
||||
this.shapeMap = updateShapes(this.shapeMap, shapeMap, this.group);
|
||||
|
||||
const { labelShape } = this.shapeMap;
|
||||
labelShape?.toFront();
|
||||
|
||||
super.draw(displayModel, diffData, diffState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes no changes on edge data, but need to re-draw it
|
||||
* e.g. source and target nodes' position changed
|
||||
*/
|
||||
public forceUpdate() {
|
||||
this.draw(this.displayModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update end item for item and re-draw the edge
|
||||
* @param type update source or target
|
||||
* @param endItem new item to ended
|
||||
*/
|
||||
public updateEnd(type: 'source' | 'target', endItem: Node) {
|
||||
if (type === 'source') this.sourceItem = endItem;
|
||||
else if (type === 'target') this.targetItem = endItem;
|
||||
this.draw(this.displayModel);
|
||||
}
|
||||
|
||||
// public update(model: EdgeModel) {
|
||||
// super.update(model);
|
||||
// }
|
||||
}
|
||||
|
@ -1,38 +1,216 @@
|
||||
import { Group } from "@antv/g";
|
||||
import { IItem, ItemDisplayModel, ItemModel } from "../types/item";
|
||||
import { Group, DisplayObject } from '@antv/g';
|
||||
import { clone, isFunction } from '@antv/util';
|
||||
import { EdgeLabelShapeStyle, EdgeShapeMap } from '../types/edge';
|
||||
import {
|
||||
DisplayMapper,
|
||||
IItem,
|
||||
ItemDisplayModel,
|
||||
ItemModel,
|
||||
ItemModelData,
|
||||
ITEM_TYPE,
|
||||
ShapeStyle,
|
||||
State,
|
||||
} from '../types/item';
|
||||
import { NodeShapeMap, NodeLabelShapeStyle } from '../types/node';
|
||||
import { isArrayOverlap } from '../util/array';
|
||||
import { updateShapes } from '../util/shape';
|
||||
import { isEncode } from '../util/type';
|
||||
|
||||
export default class Item implements IItem {
|
||||
export const RESERVED_SHAPE_IDS = ['keyShape', 'labelShape', 'iconShape'];
|
||||
const OTHER_SHAPES_FIELD_NAME = 'otherShapes';
|
||||
|
||||
export default abstract class Item implements IItem {
|
||||
public destroyed: boolean = false;
|
||||
// inner data model
|
||||
public model: ItemModel;
|
||||
private displayModel: ItemDisplayModel;
|
||||
private group: Group;
|
||||
private visible: boolean = true;
|
||||
private states: {
|
||||
name: string,
|
||||
value: string | boolean
|
||||
// display data model
|
||||
public displayModel: ItemDisplayModel;
|
||||
public mapper: DisplayMapper;
|
||||
public stateMapper: {
|
||||
[stateName: string]: DisplayMapper
|
||||
};
|
||||
public group: Group;
|
||||
public keyShape: DisplayObject;
|
||||
// render extension for this item
|
||||
public renderExt;
|
||||
public visible: boolean = true;
|
||||
public states: {
|
||||
name: string;
|
||||
value: string | boolean;
|
||||
}[] = [];
|
||||
public shapeMap: NodeShapeMap | EdgeShapeMap = {
|
||||
keyShape: undefined,
|
||||
};
|
||||
/** Set to different value in implements */
|
||||
private type: 'node' | 'edge' | 'combo' = 'node';
|
||||
public type: ITEM_TYPE;
|
||||
public renderExtensions: any; // TODO
|
||||
|
||||
constructor(model: ItemModel) {
|
||||
/** Cache the dirty tags for states when data changed, to re-map the state styles when state changed */
|
||||
private stateDirtyMap: { [stateName: string]: boolean } = {};
|
||||
private cacheStateStyles: {
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: NodeLabelShapeStyle | EdgeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
[shapeId: string]: ShapeStyle
|
||||
} = {}
|
||||
private renderExtClass;
|
||||
|
||||
constructor(props) {}
|
||||
|
||||
public init(props) {
|
||||
const { model, containerGroup, mapper, stateMapper, renderExtensions} = props;
|
||||
this.group = new Group();
|
||||
containerGroup.appendChild(this.group);
|
||||
this.model = model;
|
||||
this.draw();
|
||||
this.mapper = mapper;
|
||||
this.stateMapper = stateMapper;
|
||||
this.displayModel = this.getDisplayModelAndChanges(model).model;
|
||||
this.renderExtensions = renderExtensions;
|
||||
const { type = this.type === 'node' ? 'circle-node' : 'line-edge' } = this.displayModel.data;
|
||||
const RenderExtension = renderExtensions.find((ext) => ext.type === type);
|
||||
this.renderExtClass = RenderExtension;
|
||||
this.renderExt = new RenderExtension();
|
||||
}
|
||||
|
||||
public draw() {
|
||||
// TODO: 1. map this.model to displayModel
|
||||
// TODO: 2. call element draw fn from useLib
|
||||
public draw(displayModel: ItemDisplayModel, diffData?: { previous: ItemModelData; current: ItemModelData }, diffState?: { previous: State[], current: State[] }) {
|
||||
// call this.renderExt.draw in extend implementations
|
||||
const afterDrawShapes = this.renderExt.afterDraw?.(displayModel, this.shapeMap) || {};
|
||||
this.shapeMap = updateShapes(this.shapeMap, afterDrawShapes, this.group, false, (id) => {
|
||||
if (RESERVED_SHAPE_IDS.includes(id)) {
|
||||
console.warn(
|
||||
`Shape with id ${id} is reserved and should be returned in draw function, if the shape with ${id} returned by afterDraw is a new one, it will not be added to the group.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public update(model: ItemModel) {
|
||||
// TODO: 1. merge model into this model
|
||||
// TODO: 2. map new merged model to displayModel, keep prevModel and newModel for 3.
|
||||
// TODO: 3. call element update fn from useLib
|
||||
public update(
|
||||
model: ItemModel,
|
||||
diffData?: { previous: ItemModelData; current: ItemModelData },
|
||||
isReplace?: boolean,
|
||||
) {
|
||||
// 1. merge model into this model
|
||||
this.model = model;
|
||||
// 2. map new merged model to displayModel, keep prevModel and newModel for 3.
|
||||
const { model: displayModel, typeChange } = this.getDisplayModelAndChanges(
|
||||
this.model,
|
||||
diffData,
|
||||
isReplace,
|
||||
);
|
||||
this.displayModel = displayModel;
|
||||
|
||||
if (typeChange) {
|
||||
Object.values(this.shapeMap).forEach(child => child.remove(true));
|
||||
this.shapeMap = { keyShape: undefined };
|
||||
const { type = this.type === 'node' ? 'circle-node' : 'line-edge' } = displayModel.data;
|
||||
const extension = this.renderExtensions.find((ext) => ext.type === type);
|
||||
this.renderExt = new extension();
|
||||
}
|
||||
// 3. call element update fn from useLib
|
||||
this.draw(this.displayModel, diffData);
|
||||
// 4. tag all the states with 'dirty', for state style regenerating when state changed
|
||||
this.stateDirtyMap = {}
|
||||
this.states.forEach(({ name }) => this.stateDirtyMap[name] = true);
|
||||
}
|
||||
|
||||
public getModel() {
|
||||
return this.model;
|
||||
/**
|
||||
* Maps (mapper will be function, value, or encode format) model to displayModel and find out the shapes to be update for incremental updating.
|
||||
* @param model inner model
|
||||
* @param diffData changes from graphCore changed event
|
||||
* @param isReplace whether replace the whole data or partial udpate
|
||||
* @returns
|
||||
*/
|
||||
public getDisplayModelAndChanges(
|
||||
innerModel: ItemModel,
|
||||
diffData?: { previous: ItemModelData; current: ItemModelData },
|
||||
isReplace?: boolean,
|
||||
): {
|
||||
model: ItemDisplayModel;
|
||||
typeChange?: boolean;
|
||||
} {
|
||||
const { mapper } = this;
|
||||
const { current = innerModel.data, previous } = diffData || {};
|
||||
|
||||
// === no mapper, displayModel = model ===
|
||||
if (!mapper) {
|
||||
this.displayModel = innerModel; // TODO: need clone?
|
||||
// compare the previous data and current data to find shape changes
|
||||
let typeChange = false;
|
||||
if (current) {
|
||||
typeChange = Boolean(current.type);
|
||||
}
|
||||
return {
|
||||
model: innerModel,
|
||||
typeChange,
|
||||
};
|
||||
}
|
||||
|
||||
// === mapper is function, displayModel is mapper(model), cannot diff the displayModel, so all the shapes need to be updated ===
|
||||
if (isFunction(mapper)) return { model: (mapper as Function)(innerModel) };
|
||||
|
||||
// === fields' values in mapper are final value or Encode ===
|
||||
const dataChangedFields = isReplace
|
||||
? undefined
|
||||
// ? Array.from(new Set(Object.keys(current).concat(Object.keys(previous)))) // all the fields for replacing all data
|
||||
: Object.keys(current); // only fields in current data for partial updating
|
||||
|
||||
let typeChange = false;
|
||||
const { data, ...otherProps } = innerModel;
|
||||
const displayModelData = clone(data);
|
||||
Object.keys(mapper).forEach((fieldName) => {
|
||||
const subMapper = mapper[fieldName];
|
||||
if (RESERVED_SHAPE_IDS.includes(fieldName)) {
|
||||
// reserved shapes, fieldName is shapeId
|
||||
if (!displayModelData.hasOwnProperty(fieldName)) {
|
||||
displayModelData[fieldName] = {};
|
||||
updateShapeChange({
|
||||
innerModel,
|
||||
mapper: subMapper,
|
||||
dataChangedFields,
|
||||
shapeConfig: displayModelData[fieldName],
|
||||
});
|
||||
}
|
||||
} else if (fieldName === OTHER_SHAPES_FIELD_NAME) {
|
||||
// other shapes
|
||||
displayModelData[fieldName] = displayModelData[fieldName] || {};
|
||||
Object.keys(subMapper).forEach((shapeId) => {
|
||||
if (!displayModelData[fieldName]?.hasOwnProperty(shapeId)) {
|
||||
displayModelData[fieldName][shapeId] = displayModelData[fieldName][shapeId] || {};
|
||||
const shappStyle = subMapper[shapeId];
|
||||
updateShapeChange({
|
||||
innerModel,
|
||||
mapper: shappStyle,
|
||||
dataChangedFields,
|
||||
shapeConfig: displayModelData[fieldName][shapeId],
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// fields not about shape
|
||||
if (!displayModelData.hasOwnProperty(fieldName)) {
|
||||
const { changed, value: mappedValue } = updateChange({
|
||||
innerModel,
|
||||
mapper,
|
||||
fieldName,
|
||||
dataChangedFields,
|
||||
});
|
||||
displayModelData[fieldName] = mappedValue;
|
||||
if (changed && fieldName === 'type') typeChange = true;
|
||||
} else if (fieldName === 'type' && (!dataChangedFields || dataChangedFields.includes('type'))) {
|
||||
typeChange = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const displayModel = {
|
||||
...otherProps,
|
||||
data: displayModelData,
|
||||
};
|
||||
return {
|
||||
model: displayModel,
|
||||
typeChange,
|
||||
};
|
||||
}
|
||||
|
||||
public getID() {
|
||||
@ -44,11 +222,13 @@ export default class Item implements IItem {
|
||||
}
|
||||
|
||||
public show() {
|
||||
// TODO: utilize graphcore's view
|
||||
this.group.style.visibility = 'visible';
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
public hide() {
|
||||
// TODO: utilize graphcore's view
|
||||
this.group.style.visibility = 'hidden';
|
||||
this.visible = false;
|
||||
}
|
||||
@ -65,39 +245,223 @@ export default class Item implements IItem {
|
||||
this.group.toFront();
|
||||
}
|
||||
|
||||
/**
|
||||
* The state value for the item, false if the item does not have the state.
|
||||
* @param state state name
|
||||
* @returns { boolean | string } the state value
|
||||
*/
|
||||
public hasState(state: string) {
|
||||
const findState = this.states.find((item) => item.name === state);
|
||||
return findState?.value || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state for the item.
|
||||
* @param state state name
|
||||
* @param value state value
|
||||
*/
|
||||
public setState(state: string, value: string | boolean) {
|
||||
const existState = this.states.find(item => item.name === state);
|
||||
if (existState && value) {
|
||||
existState.value = value;
|
||||
const previousStates = clone(this.states);
|
||||
const existState = this.states.find((item) => item.name === state);
|
||||
if (value) {
|
||||
if (existState) existState.value = value;
|
||||
else this.states.push({
|
||||
name: state,
|
||||
value
|
||||
});
|
||||
} else {
|
||||
const idx = this.states.indexOf(existState);
|
||||
this.states.splice(idx, 1);
|
||||
}
|
||||
// TODO: call element setState fn from useLib
|
||||
}
|
||||
|
||||
public hasState(state: string) {
|
||||
const findState = this.states.find(item => item.name === state);
|
||||
return findState || false;
|
||||
|
||||
// if the renderExt overwrote the setState, run the custom setState instead of the default
|
||||
if (this.renderExt.constructor.prototype.hasOwnProperty('setState')) {
|
||||
this.renderExt.setState(state, value, this.shapeMap);
|
||||
return;
|
||||
}
|
||||
this.drawWithStates(previousStates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the states for the item.
|
||||
* @param states the states to be cleared. All the states will be cleared if the states is not assigned
|
||||
*/
|
||||
public clearStates(states?: string[]) {
|
||||
const newStates = this.states.filter(state => !states.includes(state.name));
|
||||
// if states is not assigned, clear all the states on the item
|
||||
const previousStates = clone(this.states);
|
||||
const newStates = [];
|
||||
let changedStates = [];
|
||||
if (states) {
|
||||
this.states.filter(state => {
|
||||
if (!states.includes(state.name)) {
|
||||
newStates.push(state);
|
||||
} else {
|
||||
changedStates.push(state);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
changedStates = this.states.map(({ name, value }) => ({ name, value: false }));
|
||||
}
|
||||
this.states = newStates;
|
||||
states.forEach(state => {
|
||||
// TODO: call element setState fn with false from useLib
|
||||
});
|
||||
// if the renderExt overwrote the setState, run the custom setState instead of the default
|
||||
if (this.renderExt.constructor.prototype.hasOwnProperty('setState')) {
|
||||
changedStates.forEach(({ name, value }) => this.renderExt.setState(name, value, this.shapeMap));
|
||||
return;
|
||||
}
|
||||
this.drawWithStates(previousStates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the states of the item.
|
||||
* @retruns states array with has { name: string, value: string | boolean } format item
|
||||
*/
|
||||
public getStates() {
|
||||
return this.states;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
// TODO: 1. stop animations
|
||||
// TODO: 2. clear group and remove group
|
||||
// 2. clear group and remove group
|
||||
this.group.remove(true);
|
||||
this.model = null;
|
||||
this.displayModel = null;
|
||||
this.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-draw the item with merged state styles.
|
||||
* @param previousStates previous states
|
||||
* @returns
|
||||
*/
|
||||
private drawWithStates(previousStates: State[]) {
|
||||
if (!this.stateMapper) return;
|
||||
|
||||
const { data: displayModelData } = this.displayModel;
|
||||
const styles = {}; // merged styles
|
||||
this.states.forEach(({ name: stateName, value }) => {
|
||||
let stateStyles = this.cacheStateStyles[stateName] || {};
|
||||
// re-mapper the state styles for states if they has dirty tag
|
||||
if (value && (!this.stateDirtyMap.hasOwnProperty(stateName) || this.stateDirtyMap[stateName])) {
|
||||
this.stateDirtyMap[stateName] = false;
|
||||
const mapper = this.stateMapper[stateName];
|
||||
if (!mapper) return;
|
||||
Object.keys(mapper).forEach(shapeId => {
|
||||
stateStyles[shapeId] = {
|
||||
...(displayModelData[shapeId] as Object)
|
||||
}
|
||||
if (RESERVED_SHAPE_IDS.includes(shapeId)) {
|
||||
// reserved shapes, fieldName is shapeId
|
||||
if (!displayModelData.hasOwnProperty(shapeId)) {
|
||||
updateShapeChange({
|
||||
innerModel: this.model,
|
||||
mapper: mapper[shapeId],
|
||||
dataChangedFields: undefined,
|
||||
shapeConfig: stateStyles[shapeId],
|
||||
});
|
||||
}
|
||||
} else if (shapeId === OTHER_SHAPES_FIELD_NAME) {
|
||||
// other shapes
|
||||
Object.keys(mapper).forEach((otherShapeId) => {
|
||||
if (!displayModelData[shapeId]?.hasOwnProperty(otherShapeId)) {
|
||||
stateStyles[shapeId] = stateStyles[shapeId] || {};
|
||||
updateShapeChange({
|
||||
innerModel: this.model,
|
||||
mapper: mapper[shapeId][otherShapeId],
|
||||
dataChangedFields: undefined,
|
||||
shapeConfig: stateStyles[shapeId],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// merge the state styles
|
||||
Object.keys(stateStyles).forEach(shapeId => {
|
||||
styles[shapeId] = Object.assign({}, styles[shapeId], stateStyles[shapeId]);
|
||||
});
|
||||
this.cacheStateStyles[stateName] = stateStyles;
|
||||
});
|
||||
|
||||
// apply the merged styles
|
||||
this.draw(
|
||||
// displayModel
|
||||
{
|
||||
...this.displayModel,
|
||||
data: {
|
||||
...displayModelData,
|
||||
...styles
|
||||
},
|
||||
} as ItemDisplayModel,
|
||||
// diffData
|
||||
undefined,
|
||||
// diffState
|
||||
{
|
||||
previous: previousStates,
|
||||
current: this.states,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mapped value of a field on innerModel.
|
||||
* @param param0: {
|
||||
* innerModel, // find unmapped field value from innerModel
|
||||
* fieldName, // name of the field to read from innerModel
|
||||
* mapper, // mapper object, contains the field's mapper
|
||||
* dataChangedFields, // fields' names which are changed in data
|
||||
* }
|
||||
* @returns { changed: boolean, value: unknown } return whether the mapper affects the value, and the mapped result
|
||||
*/
|
||||
const updateChange = ({
|
||||
innerModel,
|
||||
fieldName,
|
||||
mapper,
|
||||
dataChangedFields,
|
||||
}): {
|
||||
changed: boolean;
|
||||
value?: unknown;
|
||||
} => {
|
||||
const value = mapper[fieldName] || '';
|
||||
if (isEncode(value)) {
|
||||
const { fields, formatter } = value;
|
||||
// data changed fields and the encode fields are overlapped, display value should be changed
|
||||
if (!dataChangedFields || isArrayOverlap(dataChangedFields, fields)) {
|
||||
const formatedValue = formatter(innerModel);
|
||||
return {
|
||||
changed: true,
|
||||
value: formatedValue || '',
|
||||
};
|
||||
}
|
||||
return { changed: false };
|
||||
} else {
|
||||
// not an encode, take the mapper's value as the result directly
|
||||
return {
|
||||
changed: true,
|
||||
value,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a shape's config according to the mapper.
|
||||
* @param param0: {
|
||||
* innerModel, // find unmapped field value from innerModel
|
||||
* mapper, // mapper object, contains the field's mapper
|
||||
* shapeId, // id of the shape where the fieldName belong to
|
||||
* dataChangedFields, // fields' names which are changed in data
|
||||
* shapeConfig, // the shape's config to be updated
|
||||
* }
|
||||
* @returns { changed: boolean, value: unknown } return whether the mapper affects the value, and the mapped result
|
||||
*/
|
||||
const updateShapeChange = ({ innerModel, mapper, dataChangedFields, shapeConfig }) => {
|
||||
Object.keys(mapper).forEach((shapeAttrName) => {
|
||||
const { value: mappedValue } = updateChange({
|
||||
innerModel,
|
||||
mapper,
|
||||
fieldName: shapeAttrName,
|
||||
dataChangedFields,
|
||||
});
|
||||
shapeConfig[shapeAttrName] = mappedValue;
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,56 @@
|
||||
import Item from "./item";
|
||||
import { Group } from '@antv/g';
|
||||
import { NodeModel } from '../types';
|
||||
import { DisplayMapper, State } from '../types/item';
|
||||
import { NodeDisplayModel, NodeModelData } from '../types/node';
|
||||
import { updateShapes } from '../util/shape';
|
||||
import Item from './item';
|
||||
|
||||
interface IProps {
|
||||
model: NodeModel;
|
||||
renderExtensions: any; // TODO: type
|
||||
containerGroup: Group;
|
||||
mapper: DisplayMapper;
|
||||
stateMapper: {
|
||||
[stateName: string]: DisplayMapper
|
||||
};
|
||||
}
|
||||
export default class Node extends Item {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.type = 'node';
|
||||
this.init(props);
|
||||
this.draw(this.displayModel as NodeDisplayModel);
|
||||
}
|
||||
public draw(displayModel: NodeDisplayModel, diffData?: { previous: NodeModelData; current: NodeModelData }, diffState?: { previous: State[], current: State[] }) {
|
||||
const { group, renderExt, shapeMap: prevShapeMap } = this;
|
||||
const { data } = displayModel;
|
||||
const { x = 0, y = 0 } = data;
|
||||
group.style.x = x;
|
||||
group.style.y = y;
|
||||
const shapeMap = renderExt.draw(displayModel, this.shapeMap, diffData, diffState);
|
||||
|
||||
}
|
||||
// add shapes to group, and update shapeMap
|
||||
this.shapeMap = updateShapes(prevShapeMap, shapeMap, group);
|
||||
|
||||
this.shapeMap.labelShape?.toFront();
|
||||
|
||||
super.draw(displayModel, diffData, diffState);
|
||||
}
|
||||
|
||||
public update(
|
||||
model: NodeModel,
|
||||
diffData: { previous: NodeModelData; current: NodeModelData },
|
||||
isReplace?: boolean,
|
||||
) {
|
||||
super.update(model, diffData, isReplace);
|
||||
const { data } = this.displayModel;
|
||||
const { x = 0, y = 0 } = data;
|
||||
this.group.style.x = x;
|
||||
this.group.style.y = y;
|
||||
}
|
||||
|
||||
public getKeyBBox() {
|
||||
const { keyShape } = this.shapeMap;
|
||||
return keyShape?.getRenderBounds() || { center: [0, 0, 0] };
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Graph as GraphLib } from "@antv/graphlib";
|
||||
import { NodeUserModel, EdgeUserModel, ComboUserModel, GraphData, IGraph } from "../../types";
|
||||
import { Graph as GraphLib, ID } from '@antv/graphlib';
|
||||
import { GraphData, IGraph, ComboModel, ComboUserModel } from '../../types';
|
||||
import { registery } from '../../stdlib';
|
||||
import { getExtension } from "../../util/extension";
|
||||
import { clone, isArray, isNumber, isString } from "@antv/util";
|
||||
import { NodeModelData } from "../../types/node";
|
||||
import { EdgeModelData } from "../../types/edge";
|
||||
import { GraphCore } from "../../types/data";
|
||||
import { ITEM_TYPE } from "../../types/item";
|
||||
import { isFunction } from "@antv/g-lite/dist/utils";
|
||||
import { getExtension } from '../../util/extension';
|
||||
import { clone, isArray, isNumber, isString, isFunction, isObject } from '@antv/util';
|
||||
import { NodeModel, NodeModelData, NodeUserModel, NodeUserModelData } from '../../types/node';
|
||||
import { EdgeModel, EdgeModelData, EdgeUserModel, EdgeUserModelData } from '../../types/edge';
|
||||
import { DataChangeType, GraphCore } from '../../types/data';
|
||||
import { ITEM_TYPE } from '../../types/item';
|
||||
import { ComboUserModelData } from '../../types/combo';
|
||||
|
||||
/**
|
||||
* Manages the data transform extensions;
|
||||
@ -19,7 +19,7 @@ export class DataController {
|
||||
/**
|
||||
* User input data.
|
||||
*/
|
||||
public userData: GraphData;
|
||||
public userGraphCore: GraphCore;
|
||||
/**
|
||||
* Inner data stored in graphCore structure.
|
||||
*/
|
||||
@ -27,19 +27,21 @@ export class DataController {
|
||||
|
||||
constructor(graph: IGraph<any>) {
|
||||
this.graph = graph;
|
||||
this.graphCore = new GraphLib<NodeModelData, EdgeModelData>();
|
||||
this.tap();
|
||||
}
|
||||
|
||||
public findData(type: ITEM_TYPE, condition: string | number | (string | number)[] | Function) {
|
||||
public findData(
|
||||
type: ITEM_TYPE,
|
||||
condition: ID[] | Function,
|
||||
): EdgeModel[] | NodeModel[] | ComboModel[] {
|
||||
const { graphCore } = this;
|
||||
if (isString(condition) || isNumber(condition) || isArray(condition)) {
|
||||
const ids = isArray(condition) ? condition : [condition];
|
||||
switch (type) {
|
||||
case 'node':
|
||||
return ids.map(id => graphCore.getNode(id));
|
||||
return ids.map((id) => (graphCore.hasNode(id) ? graphCore.getNode(id) : undefined));
|
||||
case 'edge':
|
||||
return ids.map(id => graphCore.getEdge(id));
|
||||
return ids.map((id) => (graphCore.hasEdge(id) ? graphCore.getEdge(id) : undefined));
|
||||
case 'combo':
|
||||
// TODO;
|
||||
return;
|
||||
@ -50,7 +52,20 @@ export class DataController {
|
||||
// TODO getDatas = ?
|
||||
}
|
||||
const datas = getDatas() as any;
|
||||
return datas.find(data => condition(data));
|
||||
return datas.filter((data) => condition(data));
|
||||
}
|
||||
}
|
||||
|
||||
public findAllData(type: ITEM_TYPE): EdgeModel[] | NodeModel[] | ComboModel[] {
|
||||
switch (type) {
|
||||
case 'node':
|
||||
return this.graphCore.getAllNodes();
|
||||
case 'edge':
|
||||
return this.graphCore.getAllEdges();
|
||||
// case 'combo':
|
||||
// TODO
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,10 +74,7 @@ export class DataController {
|
||||
*/
|
||||
private tap() {
|
||||
this.extensions = this.getExtensions();
|
||||
this.graph.hooks.datachange.tap(this.onDataChange);
|
||||
this.graph.hooks.additems.tap(this.onAdd);
|
||||
this.graph.hooks.removeitems.tap(this.onRemove);
|
||||
this.graph.hooks.updateitems.tap(this.onUpdate);
|
||||
this.graph.hooks.datachange.tap(this.onDataChange.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,158 +82,398 @@ export class DataController {
|
||||
*/
|
||||
private getExtensions() {
|
||||
const { transform = [] } = this.graph.getSpecification();
|
||||
return transform.map(config => ({
|
||||
config,
|
||||
func: getExtension(config, registery.useLib, 'transform')
|
||||
})).filter(ext => !!ext.func);
|
||||
return transform
|
||||
.map((config) => ({
|
||||
config,
|
||||
func: getExtension(config, registery.useLib, 'transform'),
|
||||
}))
|
||||
.filter((ext) => !!ext.func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener of graph's datachange hook.
|
||||
* @param param contains new graph data
|
||||
* @param param contains new graph data and type of data change
|
||||
*/
|
||||
private onDataChange(param: { data: GraphData }) {
|
||||
const { data } = param;
|
||||
this.userData = data;
|
||||
let dataCloned: GraphData = clone(data);
|
||||
private onDataChange(param: { data: GraphData; type: DataChangeType }) {
|
||||
const { data, type: changeType } = param;
|
||||
const change = () => {
|
||||
switch (changeType) {
|
||||
case 'remove':
|
||||
this.removeData(data);
|
||||
break;
|
||||
case 'update':
|
||||
this.updateData(data);
|
||||
break;
|
||||
default:
|
||||
// 'replace' | 'mergeReplace' | 'union'
|
||||
this.changeData(data, changeType);
|
||||
break;
|
||||
}
|
||||
};
|
||||
const { userGraphCore } = this;
|
||||
if (userGraphCore) {
|
||||
userGraphCore.batch(change);
|
||||
} else {
|
||||
change();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change data by replace, merge repalce, or union.
|
||||
* @param data new data
|
||||
* @param changeType type of data change, 'replace' means discard the old data. 'mergeReplace' means merge the common part. 'union' means merge whole sets of old and new one
|
||||
*/
|
||||
private changeData(data: GraphData, changeType: 'replace' | 'mergeReplace' | 'union') {
|
||||
const { userGraphCore } = this;
|
||||
if (changeType === 'replace') {
|
||||
this.userGraphCore = new GraphLib<NodeUserModelData, EdgeUserModelData>({
|
||||
...data,
|
||||
onChanged: (event) => this.updateGraphCore(event),
|
||||
});
|
||||
const { data: transformedData } = this.transformData();
|
||||
this.graphCore = new GraphLib<NodeModelData, EdgeModelData>({ ...transformedData });
|
||||
} else {
|
||||
const prevNodes = userGraphCore.getAllNodes();
|
||||
const { nodes, edges, combos } = data;
|
||||
// TODO: distinguish combos
|
||||
if (!prevNodes.length) {
|
||||
userGraphCore.addNodes(nodes);
|
||||
} else {
|
||||
if (changeType === 'mergeReplace') {
|
||||
// remove the nodes which are not in data but in userGraphCore
|
||||
const nodeIds = nodes.map((node) => node.id);
|
||||
prevNodes.forEach((prevNode) => {
|
||||
if (!nodeIds.includes(prevNode.id)) userGraphCore.removeNode(prevNode.id);
|
||||
});
|
||||
}
|
||||
// add or update node
|
||||
nodes.forEach((node) => {
|
||||
if (userGraphCore.hasNode(node.id)) {
|
||||
// update node which is in the graphCore
|
||||
userGraphCore.mergeNodeData(node.id, node.data);
|
||||
} else {
|
||||
// add node which is in data but not in graphCore
|
||||
userGraphCore.addNode(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const prevEdges = userGraphCore.getAllEdges();
|
||||
if (!prevEdges.length) {
|
||||
userGraphCore.addEdges(edges);
|
||||
} else {
|
||||
if (changeType === 'mergeReplace') {
|
||||
// remove the edges which are not in data but in userGraphCore
|
||||
const edgeIds = edges.map((edge) => edge.id);
|
||||
prevEdges.forEach((prevEdge) => {
|
||||
if (!edgeIds.includes(prevEdge.id)) userGraphCore.removeEdge(prevEdge.id);
|
||||
});
|
||||
}
|
||||
// add or update edge
|
||||
edges.forEach((edge) => {
|
||||
if (userGraphCore.hasEdge(edge.id)) {
|
||||
// update edge which is in the graphCore
|
||||
userGraphCore.mergeEdgeData(edge.id, edge.data);
|
||||
} else {
|
||||
// add edge which is in data but not in graphCore
|
||||
userGraphCore.addEdge(edge);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove part of old data.
|
||||
* @param data data to be removed which is part of old one
|
||||
*/
|
||||
private removeData(data: GraphData) {
|
||||
const { userGraphCore } = this;
|
||||
const { nodes, edges, combos } = data;
|
||||
const prevNodes = userGraphCore.getAllNodes();
|
||||
const prevEdges = userGraphCore.getAllEdges();
|
||||
// TODO: distinguish combos
|
||||
if (prevNodes.length && nodes.length) {
|
||||
// remove the node
|
||||
userGraphCore.removeNodes(nodes.map((node) => node.id));
|
||||
}
|
||||
if (prevEdges.length && edges.length) {
|
||||
// add or update edge
|
||||
userGraphCore.removeEdges(edges.map((edge) => edge.id));
|
||||
}
|
||||
// TODO: combo
|
||||
}
|
||||
|
||||
/**
|
||||
* Update part of old data.
|
||||
* @param data data to be updated which is part of old one
|
||||
*/
|
||||
private updateData(data: GraphData) {
|
||||
const { userGraphCore } = this;
|
||||
const { nodes, edges, combos } = data;
|
||||
const prevNodes = userGraphCore.getAllNodes();
|
||||
const prevEdges = userGraphCore.getAllEdges();
|
||||
// TODO: distinguish combos
|
||||
if (prevNodes.length) {
|
||||
// update node
|
||||
nodes.forEach((newModel) => {
|
||||
const { id, data } = newModel;
|
||||
if (data) {
|
||||
const mergedData = mergeOneLevelData(userGraphCore.getNode(id), newModel);
|
||||
userGraphCore.mergeNodeData(id, mergedData);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (prevEdges.length) {
|
||||
// update edge
|
||||
edges.forEach((newModel) => {
|
||||
const oldModel = userGraphCore.getEdge(newModel.id);
|
||||
if (!oldModel) return;
|
||||
const { id, source, target, data } = newModel;
|
||||
if (source && oldModel.source !== source) userGraphCore.updateEdgeSource(id, source);
|
||||
if (target && oldModel.target !== target) userGraphCore.updateEdgeTarget(id, target);
|
||||
if (data) {
|
||||
const mergedData = mergeOneLevelData(userGraphCore.getEdge(id), newModel);
|
||||
userGraphCore.mergeEdgeData(id, mergedData);
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: combo
|
||||
}
|
||||
|
||||
/**
|
||||
* Update graphCore with transformed userGraphCore data.
|
||||
*/
|
||||
private updateGraphCore(event) {
|
||||
const { graphCore } = this;
|
||||
|
||||
// Transform the data.
|
||||
this.extensions.forEach(({ func, config }) => {
|
||||
dataCloned = func(dataCloned, config);
|
||||
})
|
||||
// === step 1: clone data from userGraphCore (userData) ===
|
||||
// === step 2: transform the data with transform extensions, output innerData and idMaps ===
|
||||
const { data: transformedData, idMaps } = this.transformData();
|
||||
const { nodes, edges, combos } = transformedData;
|
||||
|
||||
// Input and store in graphcore.
|
||||
const { nodes = [], edges = [], combos = [] } = dataCloned;
|
||||
// TODO: distinguish combos
|
||||
if (!graphCore.getAllNodes().length) {
|
||||
graphCore.addNodes(nodes);
|
||||
} else {
|
||||
nodes.forEach(node => {
|
||||
if (graphCore.hasNode(node.id)) {
|
||||
graphCore.mergeNodeData(node.id, node.data);
|
||||
} else {
|
||||
graphCore.addNode(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
const prevNodes = graphCore.getAllNodes();
|
||||
|
||||
if (!graphCore.getAllEdges().length) {
|
||||
graphCore.addEdges(edges);
|
||||
} else {
|
||||
edges.forEach(edge => {
|
||||
if (graphCore.hasEdge(edge.id)) {
|
||||
graphCore.mergeEdgeData(edge.id, edge.data);
|
||||
} else {
|
||||
graphCore.addEdge(edge);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add models to graphCore.
|
||||
* @param param item type and model list
|
||||
*/
|
||||
private onAdd(param: { type: ITEM_TYPE, models: NodeUserModel[] | EdgeUserModel[] | ComboUserModel[] }) {
|
||||
const { type, models } = param;
|
||||
const { userData } = this;
|
||||
// merge new models into userData, and format the whole dataset with extensions
|
||||
const useModels = (userData[`${type}s`] as any).concat(models);
|
||||
let dataCloned: GraphData = clone(userData);
|
||||
this.extensions.forEach(({ func, config }) => {
|
||||
dataCloned = func(dataCloned, config);
|
||||
});
|
||||
const addIds = models.map(model => model.id);
|
||||
const formattedModels = (dataCloned[`${type}s`] as any).filter(model => addIds.includes(model.id));
|
||||
|
||||
// add to graphCore
|
||||
// TODO: batch
|
||||
switch (type) {
|
||||
case 'node':
|
||||
this.graphCore.addNodes(formattedModels as NodeUserModel[]);
|
||||
break;
|
||||
case 'edge':
|
||||
this.graphCore.addEdges(formattedModels as EdgeUserModel[]);
|
||||
break;
|
||||
case 'combo':
|
||||
//TODO
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove models from graphCore.
|
||||
* @param param item type and id list
|
||||
*/
|
||||
private onRemove(param: { type: ITEM_TYPE, ids: (string | number)[] }) {
|
||||
const { type, ids } = param;
|
||||
const { userData } = this;
|
||||
// remove models from userData, and format the whole dataset with extensions
|
||||
userData[`${type}s`] = (userData[`${type}s`] as any).filter(model => !ids.includes(model.id));
|
||||
let dataCloned: GraphData = clone(userData);
|
||||
this.extensions.forEach(({ func, config }) => {
|
||||
dataCloned = func(dataCloned, config);
|
||||
});
|
||||
|
||||
// remove from graphCore
|
||||
// TODO: batch
|
||||
switch (type) {
|
||||
case 'node':
|
||||
this.graphCore.removeNodes(ids);
|
||||
break;
|
||||
case 'edge':
|
||||
this.graphCore.removeEdges(ids);
|
||||
break;
|
||||
case 'combo':
|
||||
//TODO
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onUpdate(param: { type: ITEM_TYPE, models: NodeUserModel[] | EdgeUserModel[] | ComboUserModel[] }) {
|
||||
const { type, models } = param;
|
||||
const { userData } = this;
|
||||
// update models in userData, and format the whole dataset with extensions
|
||||
userData[`${type}s`] = userData[`${type}s`].map(useModel => {
|
||||
const model = (models as any).find(item => item.id === useModel);
|
||||
if (model) {
|
||||
useModel.data = {
|
||||
...useModel.data,
|
||||
...model.data
|
||||
}
|
||||
// function to update one data in graphCore with different model type ('node' or 'edge')
|
||||
const syncUpdateToGraphCore = (id, newValue, oldValue, isNode, diff = []) => {
|
||||
if (isNode) {
|
||||
if (newValue.data) graphCore.updateNodeData(id, newValue.data);
|
||||
} else {
|
||||
if (diff.includes('data')) graphCore.updateEdgeData(id, newValue.data);
|
||||
// source and target may be changed
|
||||
if (diff.includes('source')) graphCore.updateEdgeSource(id, newValue.source);
|
||||
if (diff.includes('target')) graphCore.updateEdgeTarget(id, newValue.target);
|
||||
}
|
||||
// TODO: combo
|
||||
};
|
||||
|
||||
graphCore.batch(() => {
|
||||
// === step 3: sync to graphCore according to the changes in userGraphCore ==
|
||||
if (!idMaps?.length || idMaps.length !== this.extensions.length) {
|
||||
// situation 1: not every extension has corresponding idMap, use default mapping: suppose id is not changed by transforms
|
||||
// and diff the value in graphCore whose id is not in userGraphCore
|
||||
const newModelMap: {
|
||||
[id: string]: {
|
||||
type: 'node' | 'edge' | 'combo';
|
||||
model: NodeModel | EdgeModel | ComboModel;
|
||||
};
|
||||
} = {};
|
||||
nodes.forEach((model) => (newModelMap[model.id] = { type: 'node', model }));
|
||||
edges.forEach((model) => (newModelMap[model.id] = { type: 'edge', model }));
|
||||
prevNodes.forEach((prevNode) => {
|
||||
const { id } = prevNode;
|
||||
const { model: newModel } = newModelMap[id] || {};
|
||||
// remove
|
||||
if (!newModel) graphCore.removeNode(id);
|
||||
// update
|
||||
else if (diffAt(newModel, prevNode, true)?.length)
|
||||
syncUpdateToGraphCore(id, newModel, prevNode, true);
|
||||
// delete from the map indicates this model is visited
|
||||
delete newModelMap[id];
|
||||
});
|
||||
graphCore.getAllEdges().forEach((prevEdge) => {
|
||||
const { id } = prevEdge;
|
||||
const { model: newModel } = newModelMap[id] || {};
|
||||
// remove
|
||||
if (!newModel) graphCore.removeEdge(id);
|
||||
// update
|
||||
else {
|
||||
const diff = diffAt(newModel, prevEdge, false);
|
||||
if (diff?.length) syncUpdateToGraphCore(id, newModel, prevEdge, false, diff);
|
||||
}
|
||||
// delete from the map indicates this model is visited
|
||||
delete newModelMap[id];
|
||||
});
|
||||
// add
|
||||
Object.values(newModelMap).forEach(({ type, model }) => {
|
||||
if (type === 'node') graphCore.addNode(model);
|
||||
else if (type === 'edge') graphCore.addEdge(model as EdgeModel);
|
||||
// TODO: combo
|
||||
});
|
||||
} else {
|
||||
// situation 2: idMaps is complete
|
||||
// calculate the final idMap which maps the ids from final transformed data to their comes from ids in userData
|
||||
const finalIdMap = {};
|
||||
const newModelMap = {};
|
||||
const prevModelMap = {};
|
||||
nodes.concat(edges).forEach((model) => {
|
||||
finalIdMap[model.id] = getComesFromLinkedList(model.id, idMaps);
|
||||
newModelMap[model.id] = model;
|
||||
});
|
||||
prevNodes.concat(graphCore.getAllEdges()).forEach((model) => {
|
||||
prevModelMap[model.id] = model;
|
||||
});
|
||||
// TODO: combo
|
||||
|
||||
// map changes for search
|
||||
const changeMap = {};
|
||||
const { changes } = event;
|
||||
changes.forEach((change) => {
|
||||
const { value, id, type } = change;
|
||||
// TODO: temporary skip. how to handle tree change events?
|
||||
if (
|
||||
['TreeStructureAttached', 'TreeStructureDetached', 'TreeStructureChanged'].includes(
|
||||
type,
|
||||
)
|
||||
)
|
||||
return;
|
||||
const dataId = id || value.id;
|
||||
changeMap[dataId] = changeMap[dataId] || [];
|
||||
changeMap[dataId].push(type.toLawerCase());
|
||||
});
|
||||
|
||||
// 1. remove or add model to userGraphCore according the existence
|
||||
// 2. update or keep unchanged according to the source models' changes in userGraphCore
|
||||
// if source models have any change, update the data in graphcore. Kepp unchanged otherwise
|
||||
Object.keys(newModelMap).forEach((newId) => {
|
||||
const comesFromIds = finalIdMap[newId];
|
||||
const newValue = newModelMap[newId];
|
||||
const oldValue = prevModelMap[newId];
|
||||
const isNode = graphCore.hasNode(newId);
|
||||
if (newValue && !oldValue) {
|
||||
const addFunc = isNode ? graphCore.addNode : graphCore.addEdge;
|
||||
addFunc(newValue);
|
||||
} else if (!newValue && oldValue) {
|
||||
const removeFunc = isNode ? graphCore.removeNode : graphCore.removeEdge;
|
||||
removeFunc(newId);
|
||||
} else {
|
||||
if (!comesFromIds?.length) {
|
||||
// no comesForm, find same id in userGraphCore to follow the change, if it not found, diff new and old data value of graphCore (inner data)
|
||||
const diff = diffAt(newValue, oldValue, isNode);
|
||||
if (diff?.length) syncUpdateToGraphCore(newId, newValue, oldValue, isNode, diff);
|
||||
} else {
|
||||
// follow the corresponding data event in userGraphCore
|
||||
const comesFromChanges = changeMap[comesFromIds[0]];
|
||||
if (comesFromChanges?.length)
|
||||
syncUpdateToGraphCore(newId, newValue, oldValue, isNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return useModel;
|
||||
})
|
||||
let dataCloned: GraphData = clone(userData);
|
||||
this.extensions.forEach(({ func, config }) => {
|
||||
dataCloned = func(dataCloned, config);
|
||||
});
|
||||
|
||||
const updateIds = models.map(model => model.id);
|
||||
const formattedModels = (dataCloned[`${type}s`] as any).filter(model => updateIds.includes(model.id));
|
||||
|
||||
// TODO: batch
|
||||
switch (type) {
|
||||
case 'node':
|
||||
formattedModels.forEach(model => {
|
||||
this.graphCore.mergeNodeData(model.id, model.data);
|
||||
});
|
||||
break;
|
||||
case 'edge':
|
||||
formattedModels.forEach(model => {
|
||||
this.graphCore.mergeEdgeData(model.id, model.data);
|
||||
});
|
||||
break;
|
||||
case 'combo':
|
||||
//TODO
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone data from userGraphCore, and run transforms
|
||||
* @returns transformed data and the id map list
|
||||
*/
|
||||
private transformData() {
|
||||
const { userGraphCore } = this;
|
||||
// === step 1: clone data from userGraphCore (userData) ===
|
||||
const userData = {
|
||||
// TODO: should be deepClone
|
||||
nodes: userGraphCore.getAllNodes(),
|
||||
edges: userGraphCore.getAllEdges(),
|
||||
// combos:
|
||||
};
|
||||
let dataCloned: GraphData = clone(userData);
|
||||
|
||||
// === step 2: transform the data with transform extensions, output innerData and idMaps ===
|
||||
const idMaps = [];
|
||||
this.extensions.forEach(({ func, config }) => {
|
||||
const result = func(dataCloned, config);
|
||||
dataCloned = result.data;
|
||||
const idMap = result.idMap;
|
||||
if (idMap) idMaps.push(idMap);
|
||||
});
|
||||
return { data: dataCloned, idMaps };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source id list of the id from tail to head in linkedList.
|
||||
* @param id target id to find its source id list
|
||||
* @param linkedList id map list
|
||||
* @param index index in linkedList to start from, from the tail by defailt
|
||||
* @returns source id list
|
||||
*/
|
||||
const getComesFromLinkedList = (id, linkedList, index = linkedList.length - 1) => {
|
||||
let comesFrom = [];
|
||||
linkedList[index][id]?.forEach((comesFromId) => {
|
||||
if (index === 0) comesFrom.push(comesFromId);
|
||||
else comesFrom = comesFrom.concat(getComesFromLinkedList(comesFromId, linkedList, index - 1));
|
||||
});
|
||||
return comesFrom;
|
||||
};
|
||||
|
||||
/**
|
||||
* Diff new and old model.
|
||||
* @param newModel
|
||||
* @param oldModel
|
||||
* @param isNode
|
||||
* @returns false for no different, ['data'] for data different
|
||||
*/
|
||||
const diffAt = (newModel, oldModel, isNode): ('data' | 'source' | 'target')[] => {
|
||||
// edge's source or target is changed
|
||||
const diff = [];
|
||||
if (!isNode) {
|
||||
if (newModel.source !== oldModel.source) diff.push('source');
|
||||
if (newModel.target !== oldModel.target) diff.push('target');
|
||||
}
|
||||
if (!newModel.data) return diff;
|
||||
// value in data is changed
|
||||
const newKeys = Object.keys(newModel.data);
|
||||
const oldKeys = Object.keys(oldModel.data);
|
||||
if (oldKeys.length === 0 && oldKeys.length === newKeys.length) return diff;
|
||||
if (oldKeys.length !== newKeys.length) return diff.concat('data');
|
||||
for (let i = 0; i < newKeys.length; i++) {
|
||||
const key = newKeys[i];
|
||||
const newValue = newModel.data[key];
|
||||
const oldValue = oldModel.data[key];
|
||||
const newValueIsObject = isObject(newValue);
|
||||
const oldValueIsObject = isObject(oldValue);
|
||||
if (newValueIsObject !== oldValueIsObject) return diff.concat('data');
|
||||
if (newValueIsObject && oldValueIsObject) {
|
||||
if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) return diff.concat('data');
|
||||
else continue;
|
||||
}
|
||||
if (newValue !== oldValue) return diff.concat('data');
|
||||
}
|
||||
return diff;
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge the first level fields in data of model
|
||||
* @param prevModel previous model
|
||||
* @param newModel incoming new model
|
||||
* @returns merged model data
|
||||
*/
|
||||
const mergeOneLevelData = (
|
||||
prevModel: NodeUserModel | EdgeUserModel | ComboUserModel,
|
||||
newModel: NodeUserModel | EdgeUserModel | ComboUserModel,
|
||||
): NodeUserModelData | EdgeUserModelData | ComboUserModelData => {
|
||||
const { data: newData } = newModel;
|
||||
const { data: prevData } = prevModel;
|
||||
const mergedData = {};
|
||||
Object.keys(newData).forEach((key) => {
|
||||
if (isObject(prevData[key]) && isObject(newData[key])) {
|
||||
mergedData[key] = {
|
||||
...(prevData[key] as object),
|
||||
...(newData[key] as object),
|
||||
};
|
||||
} else {
|
||||
mergedData[key] = newData[key];
|
||||
}
|
||||
});
|
||||
return mergedData;
|
||||
};
|
||||
|
@ -71,7 +71,6 @@ export class InteractionController {
|
||||
behaviors.forEach(config => {
|
||||
if (isObject(config) && config.hasOwnProperty('key')) {
|
||||
const behaviorItem = self.extensions[mode].find(behavior => behavior.getKey() === config.key);
|
||||
debugger
|
||||
if (behaviorItem) behaviorItem.updateConfig(config);
|
||||
}
|
||||
});
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { Graph as GraphLib } from "@antv/graphlib";
|
||||
import { IGraph } from "../../types";
|
||||
import { GraphChange, ID } from '@antv/graphlib';
|
||||
import { ComboModel, IGraph } from '../../types';
|
||||
import { registery } from '../../stdlib';
|
||||
import { getExtension } from "../../util/extension";
|
||||
import { DisplayGraphCore, GraphCore } from "../../types/data";
|
||||
import { NodeDisplayModelData } from "../../types/node";
|
||||
import { EdgeDisplayModelData } from "../../types/edge";
|
||||
import Edge from "../../item/edge";
|
||||
import Combo from "../../item/combo";
|
||||
import { getExtension } from '../../util/extension';
|
||||
import { GraphCore } from '../../types/data';
|
||||
import { NodeDisplayModel, NodeEncode, NodeModel, NodeModelData } from '../../types/node';
|
||||
import { EdgeDisplayModel, EdgeEncode, EdgeModel, EdgeModelData } from '../../types/edge';
|
||||
import Node from '../../item/node';
|
||||
import Edge from '../../item/edge';
|
||||
import Combo from '../../item/combo';
|
||||
import { Group } from '@antv/g';
|
||||
import { ITEM_TYPE } from '../../types/item';
|
||||
import { ComboDisplayModel, ComboEncode } from '../../types/combo';
|
||||
|
||||
/**
|
||||
* Manages and stores the node / edge / combo items.
|
||||
@ -16,21 +20,44 @@ export class ItemController {
|
||||
public nodeExtensions = [];
|
||||
public edgeExtensions = [];
|
||||
public comboExtensions = [];
|
||||
/**
|
||||
* Display data stored in graphCore structure.
|
||||
*/
|
||||
public graphCore: DisplayGraphCore;
|
||||
|
||||
/**
|
||||
* Node / edge / combo items array
|
||||
* Node / edge / combo items map
|
||||
*/
|
||||
public nodes: Node[];
|
||||
public edges: Edge[];
|
||||
public combos: Combo[];
|
||||
private itemMap: { [id: ID]: Node | Edge | Combo } = {};
|
||||
|
||||
/**
|
||||
* node / edge / combo 's mapper in graph config
|
||||
*/
|
||||
private nodeMapper: ((data: NodeModel) => NodeDisplayModel) | NodeEncode;
|
||||
private edgeMapper: ((data: EdgeModel) => EdgeDisplayModel) | EdgeEncode;
|
||||
private comboMapper: ((data: ComboModel) => ComboDisplayModel) | ComboEncode;
|
||||
|
||||
private nodeStateMapper: {
|
||||
[stateName: string]: ((data: NodeModel) => NodeDisplayModel) | NodeEncode;
|
||||
};
|
||||
private edgeStateMapper: {
|
||||
[stateName: string]: ((data: EdgeModel) => EdgeDisplayModel) | EdgeEncode;
|
||||
};
|
||||
private comboStateMapper: {
|
||||
[stateName: string]: ((data: ComboModel) => ComboDisplayModel) | ComboEncode;
|
||||
};
|
||||
|
||||
private nodeGroup: Group;
|
||||
private edgeGroup: Group;
|
||||
// TODO: combo? not a independent group
|
||||
|
||||
constructor(graph: IGraph<any>) {
|
||||
this.graph = graph;
|
||||
this.graphCore = new GraphLib<NodeDisplayModelData, EdgeDisplayModelData>();
|
||||
// get mapper for node / edge / combo
|
||||
const { node, edge, combo, nodeState, edgeState, comboState } = graph.getSpecification();
|
||||
this.nodeMapper = node;
|
||||
this.edgeMapper = edge;
|
||||
this.comboMapper = combo;
|
||||
this.nodeStateMapper = nodeState;
|
||||
this.edgeStateMapper = edgeState;
|
||||
this.comboStateMapper = comboState;
|
||||
|
||||
this.tap();
|
||||
}
|
||||
|
||||
@ -43,28 +70,286 @@ export class ItemController {
|
||||
this.nodeExtensions = extensions.node;
|
||||
this.edgeExtensions = extensions.edge;
|
||||
this.comboExtensions = extensions.combo;
|
||||
this.graph.hooks.render.tap(this.onRender);
|
||||
this.graph.hooks.render.tap(this.onRender.bind(this));
|
||||
this.graph.hooks.itemchange.tap(this.onChange.bind(this));
|
||||
this.graph.hooks.itemstatechange.tap(this.onItemStateChange.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extensions from useLib, stdLib is a subset of useLib.
|
||||
*/
|
||||
private getExtensions() {
|
||||
const { transform = [] } = this.graph.getSpecification();
|
||||
// TODO: user need to config using node/edge/combo types from useLib to spec?
|
||||
const { node, edge, combo } = this.graph.getSpecification();
|
||||
|
||||
const nodeTypes = ['circle-node', 'custom-node']; // TODO: WIP
|
||||
const edgeTypes = ['line-edge', 'custom-edge']; // TODO: WIP
|
||||
const comboTypes = ['circle-combo', 'rect-combo']; // TODO: WIP
|
||||
return {
|
||||
node: transform.map(config => getExtension(config, registery.useLib, 'node')).filter(transformer => !!transformer),
|
||||
edge: transform.map(config => getExtension(config, registery.useLib, 'edge')).filter(transformer => !!transformer),
|
||||
combo: transform.map(config => getExtension(config, registery.useLib, 'combo')).filter(transformer => !!transformer),
|
||||
}
|
||||
node: nodeTypes
|
||||
.map((config) => getExtension(config, registery.useLib, 'node'))
|
||||
.filter(Boolean),
|
||||
edge: edgeTypes
|
||||
.map((config) => getExtension(config, registery.useLib, 'edge'))
|
||||
.filter(Boolean),
|
||||
combo: comboTypes
|
||||
.map((config) => getExtension(config, registery.useLib, 'combo'))
|
||||
.filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener of graph's render hook.
|
||||
* Listener of runtime's render hook.
|
||||
* @param param contains inner data stored in graphCore structure
|
||||
*/
|
||||
private onRender(param: { graphCore: GraphCore }) {
|
||||
const { graphCore } = param;
|
||||
const { graph } = this;
|
||||
// TODO: 0. clear groups on canvas, and create new groups
|
||||
graph.canvas.removeChildren();
|
||||
const edgeGroup = new Group({ id: 'edge-group' });
|
||||
const nodeGroup = new Group({ id: 'node-group' });
|
||||
graph.canvas.appendChild(edgeGroup);
|
||||
graph.canvas.appendChild(nodeGroup);
|
||||
this.nodeGroup = nodeGroup;
|
||||
this.edgeGroup = edgeGroup;
|
||||
|
||||
// TODO: 1. create node / edge / combo items, classes from ../../item, and element drawing and updating fns from node/edge/comboExtensions
|
||||
// TODO: 2. draw them
|
||||
const nodeModels = graphCore.getAllNodes();
|
||||
const edgeModels = graphCore.getAllEdges();
|
||||
// const combos = graphCore.getAllCombos();
|
||||
|
||||
this.renderNodes(nodeModels);
|
||||
this.renderEdges(edgeModels);
|
||||
// TODO: combo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener of runtime's itemchange lifecycle hook.
|
||||
* @param param
|
||||
*/
|
||||
private onChange(param: {
|
||||
type: ITEM_TYPE;
|
||||
changes: GraphChange<NodeModelData, EdgeModelData>[];
|
||||
graphCore: GraphCore;
|
||||
}) {
|
||||
const { changes, graphCore } = param;
|
||||
const groupedChanges = {
|
||||
NodeRemoved: [],
|
||||
EdgeRemoved: [],
|
||||
NodeAdded: [],
|
||||
EdgeAdded: [],
|
||||
NodeDataUpdated: [],
|
||||
EdgeUpdated: [],
|
||||
EdgeDataUpdated: [],
|
||||
};
|
||||
changes.forEach((change) => {
|
||||
const { type: changeType } = change;
|
||||
groupedChanges[changeType].push(change);
|
||||
});
|
||||
const { itemMap } = this;
|
||||
// change items according to the order of the keys in groupedChanges
|
||||
|
||||
// === 1. remove edges; 2. remove nodes ===
|
||||
groupedChanges.EdgeRemoved.concat(groupedChanges.NodeRemoved).forEach(({ value }) => {
|
||||
const { id } = value;
|
||||
const item = itemMap[id];
|
||||
if (item) {
|
||||
item.destroy();
|
||||
delete itemMap[id];
|
||||
}
|
||||
});
|
||||
// === 3. add nodes ===
|
||||
if (groupedChanges.NodeAdded.length) {
|
||||
this.renderNodes(groupedChanges.NodeAdded.map((change) => change.value));
|
||||
}
|
||||
// === 4. add edges ===
|
||||
if (groupedChanges.EdgeAdded.length) {
|
||||
this.renderEdges(groupedChanges.EdgeAdded.map((change) => change.value));
|
||||
}
|
||||
|
||||
// === 5. update nodes's data ===
|
||||
// merge changes for each node
|
||||
if (groupedChanges.NodeDataUpdated.length) {
|
||||
const nodeUpdate = {};
|
||||
groupedChanges.NodeDataUpdated.forEach((change) => {
|
||||
const { id, propertyName, newValue, oldValue } = change;
|
||||
nodeUpdate[id] = nodeUpdate[id] || { previous: {}, current: {} };
|
||||
if (!propertyName) {
|
||||
nodeUpdate[id] = {
|
||||
isReplace: true, // whether replace the whole data
|
||||
previous: oldValue,
|
||||
current: newValue,
|
||||
};
|
||||
} else {
|
||||
nodeUpdate[id].previous[propertyName] = oldValue;
|
||||
nodeUpdate[id].current[propertyName] = newValue;
|
||||
}
|
||||
});
|
||||
const edgeToUpdate = {};
|
||||
Object.keys(nodeUpdate).forEach((id) => {
|
||||
const { isReplace, previous, current } = nodeUpdate[id];
|
||||
const item = itemMap[id];
|
||||
const innerModel = graphCore.getNode(id);
|
||||
item.update(innerModel, { previous, current }, isReplace);
|
||||
const relatedEdgeInnerModels = graphCore.getRelatedEdges(id);
|
||||
relatedEdgeInnerModels.forEach((edge) => (edgeToUpdate[edge.id] = edge));
|
||||
});
|
||||
Object.keys(edgeToUpdate).forEach((id) => {
|
||||
const item = itemMap[id] as Edge;
|
||||
item.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
// === 6. update edges' data ===
|
||||
if (groupedChanges.EdgeDataUpdated.length) {
|
||||
const edgeUpdate = {};
|
||||
groupedChanges.EdgeDataUpdated.forEach((change) => {
|
||||
const { id, propertyName, newValue, oldValue } = change;
|
||||
edgeUpdate[id] = edgeUpdate[id] || { previous: {}, current: {} };
|
||||
if (!propertyName) {
|
||||
edgeUpdate[id] = {
|
||||
isReplace: true, // whether replace the whole data
|
||||
previous: oldValue,
|
||||
current: newValue,
|
||||
};
|
||||
} else {
|
||||
edgeUpdate[id].previous[propertyName] = oldValue;
|
||||
edgeUpdate[id].current[propertyName] = newValue;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(edgeUpdate).forEach((id) => {
|
||||
const { isReplace, current, previous } = edgeUpdate[id];
|
||||
const item = itemMap[id];
|
||||
const innerModel = graphCore.getEdge(id);
|
||||
item.update(innerModel, { current, previous }, isReplace);
|
||||
});
|
||||
}
|
||||
|
||||
// === 7. update edges' source target ===
|
||||
if (groupedChanges.EdgeUpdated.length) {
|
||||
const edgeUpdate = {};
|
||||
groupedChanges.EdgeUpdated.forEach((change) => {
|
||||
// propertyName is 'source' or 'target'
|
||||
const { id, propertyName, newValue } = change;
|
||||
edgeUpdate[id] = edgeUpdate[id] || {};
|
||||
edgeUpdate[id][propertyName] = newValue;
|
||||
});
|
||||
|
||||
Object.keys(edgeUpdate).forEach((id) => {
|
||||
const { source, target } = edgeUpdate[id];
|
||||
const item = itemMap[id] as Edge;
|
||||
if (source !== undefined) item.updateEnd('source', this.itemMap[source] as Node);
|
||||
if (target !== undefined) item.updateEnd('target', this.itemMap[target] as Node);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener for item state changing.
|
||||
* @param param
|
||||
* {
|
||||
* ids: ids of the items to be set state
|
||||
* states: state names to set
|
||||
* value: state value
|
||||
* }
|
||||
*/
|
||||
private onItemStateChange(param: { ids: ID[], states: string[], value: boolean }) {
|
||||
const { ids, states, value } = param;
|
||||
ids.forEach(id => {
|
||||
const item = this.itemMap[id];
|
||||
if (!item) {
|
||||
console.warn(`Fail to set state for item ${id}, which is not exist.`);
|
||||
return;
|
||||
}
|
||||
if (!states || !value) {
|
||||
// clear all the states
|
||||
item.clearStates(states);
|
||||
} else {
|
||||
states.forEach(state => item.setState(state, value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create nodes with inner data to canvas.
|
||||
* @param models nodes' inner datas
|
||||
*/
|
||||
private renderNodes(models: NodeModel[]) {
|
||||
const { nodeExtensions, nodeGroup } = this;
|
||||
models.forEach((node) => {
|
||||
// TODO: get mapper from theme controller which is analysed from graph spec;
|
||||
this.itemMap[node.id] = new Node({
|
||||
model: node,
|
||||
renderExtensions: nodeExtensions,
|
||||
containerGroup: nodeGroup,
|
||||
mapper: this.nodeMapper,
|
||||
stateMapper: this.nodeStateMapper,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create edges with inner data to canvas.
|
||||
* @param models edges' inner datas
|
||||
*/
|
||||
private renderEdges(models: EdgeModel[]) {
|
||||
const { edgeExtensions, edgeGroup, itemMap } = this;
|
||||
models.forEach((edge) => {
|
||||
const { source, target, id } = edge;
|
||||
const sourceItem = itemMap[source] as Node;
|
||||
const targetItem = itemMap[target] as Node;
|
||||
if (!sourceItem) {
|
||||
console.warn(
|
||||
`The source node ${source} is not exist in the graph for edge ${id}, please add the node first`,
|
||||
);
|
||||
}
|
||||
if (!targetItem) {
|
||||
console.warn(
|
||||
`The source node ${source} is not exist in the graph for edge ${id}, please add the node first`,
|
||||
);
|
||||
}
|
||||
itemMap[id] = new Edge({
|
||||
model: edge,
|
||||
renderExtensions: edgeExtensions,
|
||||
containerGroup: edgeGroup,
|
||||
mapper: this.edgeMapper,
|
||||
stateMapper: this.edgeStateMapper,
|
||||
sourceItem,
|
||||
targetItem,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the item which have the state with true value
|
||||
* @param itemType item's type
|
||||
* @param state state name
|
||||
* @param value state value, true by default
|
||||
* @returns
|
||||
*/
|
||||
public findIdByState(itemType: ITEM_TYPE, state: string, value: string | boolean = true) {
|
||||
const ids = [];
|
||||
Object.values(this.itemMap).forEach(item => {
|
||||
if (item.getType() !== itemType) return;
|
||||
if (item.hasState(state) === value) ids.push(item.getID());
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state value for the item with id
|
||||
* @param id item' id
|
||||
* @param state state name
|
||||
* @returns {boolean | string} the state value
|
||||
*/
|
||||
public getItemState(id: ID, state: string) {
|
||||
const item = this.itemMap[id];
|
||||
if (!item) {
|
||||
console.warn(`Fail to item state, the item with id ${id} does not exist.`);
|
||||
return false;
|
||||
}
|
||||
return item.hasState(state);
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,49 @@
|
||||
import EventEmitter from '@antv/event-emitter';
|
||||
import { clone, isArray, isObject } from '@antv/util';
|
||||
import { ComboUserModel, EdgeUserModel, GraphData, IGraph, NodeUserModel, Specification } from '../types';
|
||||
import { Canvas } from '@antv/g';
|
||||
import { GraphChange, ID } from '@antv/graphlib';
|
||||
import { isArray, isNumber, isObject, isString } from '@antv/util';
|
||||
import {
|
||||
ComboUserModel,
|
||||
EdgeUserModel,
|
||||
GraphData,
|
||||
IGraph,
|
||||
NodeUserModel,
|
||||
Specification,
|
||||
} from '../types';
|
||||
import { AnimateCfg } from '../types/animate';
|
||||
import { BehaviorObjectOptionsOf, BehaviorOptionsOf, BehaviorRegistry } from '../types/behavior';
|
||||
import { ComboModel } from '../types/combo';
|
||||
import { Padding, Point } from '../types/common';
|
||||
import { GraphCore } from '../types/data';
|
||||
import { EdgeModel } from '../types/edge';
|
||||
import { EdgeModel, EdgeModelData } from '../types/edge';
|
||||
import { Hooks } from '../types/hook';
|
||||
import { ITEM_TYPE } from '../types/item';
|
||||
import { LayoutCommonConfig } from '../types/layout';
|
||||
import { NodeModel } from '../types/node';
|
||||
import { NodeModel, NodeModelData } from '../types/node';
|
||||
import { FitViewRules, GraphAlignment } from '../types/view';
|
||||
import { DataController, InteractionController, ItemController, LayoutController, ThemeController, ExtensionController } from './controller';
|
||||
import { createCanvas } from '../util/canvas';
|
||||
import {
|
||||
DataController,
|
||||
InteractionController,
|
||||
ItemController,
|
||||
LayoutController,
|
||||
ThemeController,
|
||||
ExtensionController,
|
||||
} from './controller';
|
||||
import Hook from './hooks';
|
||||
|
||||
export default class Graph<B extends BehaviorRegistry> extends EventEmitter implements IGraph<B> {
|
||||
public hooks: Hooks;
|
||||
// for nodes and edges, which will be separate into groups
|
||||
public canvas: Canvas;
|
||||
// the tag to indicate whether the graph instance is destroyed
|
||||
public destroyed: boolean;
|
||||
// for background shapes, e.g. grid, pipe indices
|
||||
private backgroundCanvas: Canvas;
|
||||
// for transient shapes for interactions, e.g. transient node and related edges while draging, delegates
|
||||
private transientCanvas: Canvas;
|
||||
// the tag indicates all the three canvases are all ready
|
||||
private canvasReady: boolean;
|
||||
private specification: Specification<B>;
|
||||
private dataController: DataController;
|
||||
private interactionController: InteractionController;
|
||||
@ -31,7 +58,14 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
|
||||
this.specification = spec;
|
||||
this.initHooks();
|
||||
this.initCanvas();
|
||||
this.initControllers();
|
||||
|
||||
const { data } = spec;
|
||||
if (data) {
|
||||
// TODO: handle multiple type data configs
|
||||
this.read(data as GraphData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,23 +80,44 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
this.extensionController = new ExtensionController(this);
|
||||
}
|
||||
|
||||
private initCanvas() {
|
||||
const { renderer, container, width, height } = this.specification;
|
||||
let rendererType;
|
||||
let pixelRatio;
|
||||
if (renderer && !isString(renderer)) {
|
||||
rendererType = renderer.type || 'canvas';
|
||||
pixelRatio = renderer.pixelRatio;
|
||||
} else {
|
||||
rendererType = renderer || 'canvas';
|
||||
}
|
||||
this.backgroundCanvas = createCanvas(rendererType, container, width, height, pixelRatio);
|
||||
this.canvas = createCanvas(rendererType, container, width, height, pixelRatio);
|
||||
this.transientCanvas = createCanvas(rendererType, container, width, height, pixelRatio);
|
||||
Promise.all(
|
||||
[this.backgroundCanvas, this.canvas, this.transientCanvas].map((canvas) => canvas.ready),
|
||||
).then(() => (this.canvasReady = true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the hooks for graph's lifecycles.
|
||||
*/
|
||||
private initHooks() {
|
||||
this.hooks = {
|
||||
init: new Hook<void>({ name: 'init' }),
|
||||
datachange: new Hook<{ data: GraphData }>({ name: 'datachange' }),
|
||||
additems: new Hook<{ type: ITEM_TYPE, models: NodeUserModel[] | EdgeUserModel[] | ComboUserModel[] }>({ name: 'additems' }),
|
||||
removeitems: new Hook<{ type: ITEM_TYPE, ids: (string | number)[] }>({ name: 'removeitems' }),
|
||||
updateitems: new Hook<{ type: ITEM_TYPE, models: NodeUserModel[] | EdgeUserModel[] | ComboUserModel[] }>({ name: 'updateitems' }),
|
||||
datachange: new Hook<{ data: GraphData; type: 'replace' }>({ name: 'datachange' }),
|
||||
itemchange: new Hook<{
|
||||
type: ITEM_TYPE;
|
||||
changes: GraphChange<NodeModelData, EdgeModelData>[];
|
||||
graphCore: GraphCore;
|
||||
}>({ name: 'itemchange' }),
|
||||
render: new Hook<{ graphCore: GraphCore }>({ name: 'render' }),
|
||||
modechange: new Hook<{ mode: string }>({ name: 'modechange' }),
|
||||
behaviorchange: new Hook<{
|
||||
action: 'update' | 'add' | 'remove',
|
||||
modes: string[],
|
||||
behaviors: BehaviorOptionsOf<{}>[]
|
||||
action: 'update' | 'add' | 'remove';
|
||||
modes: string[];
|
||||
behaviors: BehaviorOptionsOf<{}>[];
|
||||
}>({ name: 'behaviorchange' }),
|
||||
itemstatechange: new Hook<{ ids: ID[], state: string, value: boolean }>({ name: 'itemstatechange' })
|
||||
};
|
||||
}
|
||||
|
||||
@ -78,26 +133,51 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @returns graph specs
|
||||
*/
|
||||
public getSpecification(): Specification<B> {
|
||||
return clone(this.specification);
|
||||
return this.specification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input data and render the graph.
|
||||
* If there is old data, diffs and changes it.
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
* @group Data
|
||||
*/
|
||||
public read(data: GraphData) {
|
||||
this.hooks.datachange.emit({ data });
|
||||
this.hooks.datachange.emit({ data, type: 'replace' });
|
||||
const emitRender = () => {
|
||||
this.hooks.render.emit({
|
||||
graphCore: this.dataController.graphCore,
|
||||
});
|
||||
this.emit('afterrender');
|
||||
};
|
||||
if (this.canvasReady) {
|
||||
emitRender();
|
||||
} else {
|
||||
Promise.all(
|
||||
[this.backgroundCanvas, this.canvas, this.transientCanvas].map((canvas) => canvas.ready),
|
||||
).then(emitRender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change graph data.
|
||||
* @param data new data
|
||||
* @param type the way to change data, 'replace' means discard the old data and use the new one; 'mergeReplace' means merge the common part, remove (old - new), add (new - old)
|
||||
* @returns
|
||||
* @group Data
|
||||
*/
|
||||
public changeData(data: GraphData, type: 'replace' | 'mergeReplace' = 'mergeReplace') {
|
||||
this.hooks.datachange.emit({ data, type });
|
||||
this.hooks.render.emit({
|
||||
graphCore: this.dataController.graphCore
|
||||
graphCore: this.dataController.graphCore,
|
||||
});
|
||||
this.emit('afterrender');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the graph, means remove all the items on the graph.
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
public clear() {
|
||||
// TODO
|
||||
@ -108,7 +188,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param dx x of the relative vector
|
||||
* @param dy y of the relative vector
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public move(dx: number, dy: number, animateCfg?: AnimateCfg) {
|
||||
@ -121,7 +201,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param y position on the canvas to align
|
||||
* @param alignment alignment of the graph content
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public moveTo(x: number, y: number, alignment: GraphAlignment, animateCfg?: AnimateCfg) {
|
||||
@ -133,7 +213,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param ratio relative ratio to zoom
|
||||
* @param center zoom center
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public zoom(ratio: number, center?: Point, animateCfg?: AnimateCfg) {
|
||||
@ -145,7 +225,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param toRatio specified ratio
|
||||
* @param center zoom center
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public zoomTo(toRatio: number, center?: Point, animateCfg?: AnimateCfg) {
|
||||
@ -157,7 +237,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param padding padding while fitting
|
||||
* @param rules rules for fitting
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public fitView(padding?: Padding, rules?: FitViewRules, animateCfg?: AnimateCfg) {
|
||||
@ -166,7 +246,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
/**
|
||||
* Fit the graph center to the view center.
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public fitCenter(animateCfg?: AnimateCfg) {
|
||||
@ -176,41 +256,68 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* Move the graph to make the item align the view center.
|
||||
* @param item node/edge/combo item or its id
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
public focusItem(ids: string | number | (string | number)[], animateCfg?: AnimateCfg) {
|
||||
public focusItem(ids: ID | ID[], animateCfg?: AnimateCfg) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
// ===== item operations =====
|
||||
/**
|
||||
* Find a node's inner data according to id or function.
|
||||
* @param { string | Function} condition id or condition function
|
||||
* @returns result node
|
||||
* @group Item
|
||||
* @param { ID | Function } condition id or condition function
|
||||
* @returns result node's inner data
|
||||
* @group Data
|
||||
*/
|
||||
public getNodeData(condition: string | Function): NodeModel | undefined {
|
||||
return this.dataController.findData('node', condition);
|
||||
public getNodeData(condition: ID | Function): NodeModel | undefined {
|
||||
const conds = isString(condition) || isNumber(condition) ? [condition] : condition;
|
||||
return this.dataController.findData('node', conds)?.[0] as NodeModel;
|
||||
}
|
||||
/**
|
||||
* Find an edge's inner data according to id or function.
|
||||
* @param { string | Function} condition id or condition function
|
||||
* @returns result edge
|
||||
* @group Item
|
||||
* @param { ID | Function } condition id or condition function
|
||||
* @returns result edge's inner data
|
||||
* @group Data
|
||||
*/
|
||||
public getEdgeData(condition: string | Function): EdgeModel | undefined {
|
||||
return this.dataController.findData('edge', condition);
|
||||
public getEdgeData(condition: ID | Function): EdgeModel | undefined {
|
||||
const conds =
|
||||
isString(condition) || isNumber(condition) || isNumber(condition) ? [condition] : condition;
|
||||
return this.dataController.findData('edge', conds)?.[0] as EdgeModel;
|
||||
}
|
||||
/**
|
||||
* Find an combo's inner data according to id or function.
|
||||
* @param { string | Function} condition id or condition function
|
||||
* @returns result combo
|
||||
* @group Item
|
||||
* @param { ID | Function } condition id or condition function
|
||||
* @returns result combo's inner data
|
||||
* @group Data
|
||||
*/
|
||||
public getComboData(condition: string | Function): ComboModel | undefined {
|
||||
return this.dataController.findData('combo', condition);
|
||||
public getComboData(condition: ID | Function): ComboModel | undefined {
|
||||
const conds = isString(condition) || isNumber(condition) ? [condition] : condition;
|
||||
return this.dataController.findData('combo', conds)?.[0] as ComboModel;
|
||||
}
|
||||
/**
|
||||
* Get all the nodes' inner data
|
||||
* @returns all nodes' inner data on the graph
|
||||
* @group Data
|
||||
*/
|
||||
public getAllNodesData(): NodeModel[] {
|
||||
return this.dataController.findAllData('node');
|
||||
}
|
||||
/**
|
||||
* Get all the edges' inner data
|
||||
* @returns all edges' inner data on the graph
|
||||
* @group Data
|
||||
*/
|
||||
public getAllEdgesData(): EdgeModel[] {
|
||||
return this.dataController.findAllData('edge') as EdgeModel[];
|
||||
}
|
||||
/**
|
||||
* Get all the combos' inner data
|
||||
* @returns all combos' inner data on the graph
|
||||
* @group Data
|
||||
*/
|
||||
public getAllCombosData(): ComboModel[] {
|
||||
return this.dataController.findAllData('combo') as ComboModel[];
|
||||
}
|
||||
/**
|
||||
* Find items which has the state.
|
||||
@ -220,74 +327,146 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @returns items that is the type and has the state
|
||||
* @group Item
|
||||
*/
|
||||
public findIdByState(itemType: ITEM_TYPE, state: string, additionalFilter?: (item: NodeModel | EdgeModel | ComboModel) => boolean): (string | number)[] {
|
||||
// TODO
|
||||
return;
|
||||
public findIdByState(
|
||||
itemType: ITEM_TYPE,
|
||||
state: string,
|
||||
value: string | boolean = true,
|
||||
additionalFilter?: (item: NodeModel | EdgeModel | ComboModel) => boolean,
|
||||
): ID[] {
|
||||
let ids = this.itemController.findIdByState(itemType, state, value);
|
||||
if (additionalFilter) {
|
||||
const getDataFunc = itemType === 'node' ? this.getNodeData : this.getEdgeData; // TODO: combo
|
||||
ids = ids.filter(id => additionalFilter(getDataFunc(id)));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
/**
|
||||
* Add an item or items to the graph.
|
||||
* Add one or more node/edge/combo data to the graph.
|
||||
* @param itemType item type
|
||||
* @param model user data
|
||||
* @param stack whether push this operation to stack
|
||||
* @returns whether success
|
||||
* @group Item
|
||||
* @group Data
|
||||
*/
|
||||
public addItem(itemType: ITEM_TYPE, models: NodeUserModel | EdgeUserModel | ComboUserModel | NodeUserModel[] | EdgeUserModel[] | ComboUserModel[], stack?: boolean): boolean {
|
||||
public addData(
|
||||
itemType: ITEM_TYPE,
|
||||
models:
|
||||
| NodeUserModel
|
||||
| EdgeUserModel
|
||||
| ComboUserModel
|
||||
| NodeUserModel[]
|
||||
| EdgeUserModel[]
|
||||
| ComboUserModel[],
|
||||
stack?: boolean,
|
||||
): NodeModel | EdgeModel | ComboModel | NodeModel[] | EdgeModel[] | ComboModel[] {
|
||||
// data controller and item controller subscribe additem in order
|
||||
const modelArr = isArray(models) ? models : [models];
|
||||
this.hooks.additems.emit({
|
||||
type: itemType,
|
||||
models: modelArr
|
||||
|
||||
const { graphCore } = this.dataController;
|
||||
graphCore.once('changed', (event) => {
|
||||
this.hooks.itemchange.emit({
|
||||
type: itemType,
|
||||
changes: graphCore.reduceChanges(event.changes),
|
||||
graphCore,
|
||||
});
|
||||
});
|
||||
return this.dataController.findData(itemType, modelArr.map(model => model.id)).every(Boolean);
|
||||
};
|
||||
|
||||
const modelArr = isArray(models) ? models : [models];
|
||||
const data = { nodes: [], edges: [], combos: [] };
|
||||
data[`${itemType}s`] = modelArr;
|
||||
this.hooks.datachange.emit({
|
||||
data,
|
||||
type: 'union',
|
||||
});
|
||||
const dataList = this.dataController.findData(
|
||||
itemType,
|
||||
modelArr.map((model) => model.id),
|
||||
);
|
||||
return isArray(models) ? dataList : dataList[0];
|
||||
}
|
||||
/**
|
||||
* Remove an item or items from the graph.
|
||||
* Remove one or more node/edge/combo data from the graph.
|
||||
* @param item the item to be removed
|
||||
* @param stack whether push this operation to stack
|
||||
* @returns whether success
|
||||
* @group Item
|
||||
* @group Data
|
||||
*/
|
||||
public removeItem(itemType: ITEM_TYPE, ids: string | number | (string | number)[], stack?: boolean): boolean {
|
||||
const idArr = isArray(ids) ? ids : [ids]
|
||||
this.hooks.removeitems.emit({
|
||||
type: itemType,
|
||||
ids: idArr
|
||||
public removeData(itemType: ITEM_TYPE, ids: ID | ID[], stack?: boolean) {
|
||||
const idArr = isArray(ids) ? ids : [ids];
|
||||
const data = { nodes: [], edges: [], combos: [] };
|
||||
const { userGraphCore, graphCore } = this.dataController;
|
||||
const getItem = itemType === 'edge' ? userGraphCore.getEdge : userGraphCore.getNode; // TODO: combo
|
||||
data[`${itemType}s`] = idArr.map((id) => getItem.bind(userGraphCore)(id));
|
||||
graphCore.once('changed', (event) => {
|
||||
this.hooks.itemchange.emit({
|
||||
type: itemType,
|
||||
changes: event.changes,
|
||||
graphCore,
|
||||
});
|
||||
});
|
||||
this.hooks.datachange.emit({
|
||||
data,
|
||||
type: 'remove',
|
||||
});
|
||||
return this.dataController.findData(itemType, idArr).every(Boolean);
|
||||
}
|
||||
/**
|
||||
* Update an item or items on the graph.
|
||||
* Update one or more node/edge/combo data on the graph.
|
||||
* @param {Item} item item or id
|
||||
* @param {EdgeConfig | NodeConfig} cfg incremental updated configs
|
||||
* @param {boolean} stack 本次操作是否入栈,默认为 true
|
||||
* @group Item
|
||||
* @group Data
|
||||
*/
|
||||
public updateItem(itemType: ITEM_TYPE, models: Partial<NodeUserModel> | Partial<EdgeUserModel> | Partial<ComboUserModel | Partial<NodeUserModel>[] | Partial<EdgeUserModel>[] | Partial<ComboUserModel>[]>, stack?: boolean): boolean {
|
||||
public updateData(
|
||||
itemType: ITEM_TYPE,
|
||||
models:
|
||||
| Partial<NodeUserModel>
|
||||
| Partial<EdgeUserModel>
|
||||
| Partial<
|
||||
| ComboUserModel
|
||||
| Partial<NodeUserModel>[]
|
||||
| Partial<EdgeUserModel>[]
|
||||
| Partial<ComboUserModel>[]
|
||||
>,
|
||||
stack?: boolean,
|
||||
): NodeModel | EdgeModel | ComboModel | NodeModel[] | EdgeModel[] | ComboModel[] {
|
||||
const modelArr = isArray(models) ? models : [models];
|
||||
this.hooks.updateitems.emit({
|
||||
type: itemType,
|
||||
models: modelArr
|
||||
const data = { nodes: [], edges: [], combos: [] };
|
||||
data[`${itemType}s`] = modelArr;
|
||||
|
||||
const { graphCore } = this.dataController;
|
||||
graphCore.once('changed', (event) => {
|
||||
this.hooks.itemchange.emit({
|
||||
type: itemType,
|
||||
changes: event.changes,
|
||||
graphCore,
|
||||
});
|
||||
});
|
||||
// TODO
|
||||
return true;
|
||||
|
||||
this.hooks.datachange.emit({
|
||||
data,
|
||||
type: 'update',
|
||||
});
|
||||
const dataList = this.dataController.findData(
|
||||
itemType,
|
||||
modelArr.map((model) => model.id),
|
||||
);
|
||||
return isArray(models) ? dataList : dataList[0];
|
||||
}
|
||||
/**
|
||||
* Show the item(s).
|
||||
* @param item the item to be shown
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
public showItem(ids: string | number | (string | number)[]) {
|
||||
public showItem(ids: ID | ID[]) {
|
||||
// TODO
|
||||
}
|
||||
/**
|
||||
* Hide the item(s).
|
||||
* @param item the item to be hidden
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
public hideItem(ids: string | number | (string | number)[]) {
|
||||
public hideItem(ids: ID | ID[]) {
|
||||
// TODO
|
||||
}
|
||||
/**
|
||||
@ -295,21 +474,52 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param item the item to be set
|
||||
* @param state the state name
|
||||
* @param value state value
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
public setItemState(ids: string | number | (string | number)[], state: string, value: boolean) {
|
||||
// TODO
|
||||
public setItemState(ids: ID | ID[], states: string | string[], value: boolean) {
|
||||
const idArr = isArray(ids) ? ids : [ids];
|
||||
const stateArr = isArray(states) ? states : [states];
|
||||
this.hooks.itemstatechange.emit({
|
||||
ids: idArr as ID[],
|
||||
states: stateArr as string[],
|
||||
value
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get the state value for an item.
|
||||
* @param id the id for the item
|
||||
* @param states the state name
|
||||
* @returns {boolean} the state value
|
||||
* @group Item
|
||||
*/
|
||||
public getItemState(id: ID, state: string) {
|
||||
return this.itemController.getItemState(id, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the states for item(s).
|
||||
* @param ids the id(s) for the item(s) to be clear
|
||||
* @param states the states' names, all the states wil be cleared if states is not assigned
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
public clearItemState(ids: ID | ID[], states?: string[]) {
|
||||
const idArr = isArray(ids) ? ids : [ids];
|
||||
this.hooks.itemstatechange.emit({
|
||||
ids: idArr as ID[],
|
||||
states,
|
||||
value: false
|
||||
});
|
||||
}
|
||||
|
||||
// ===== combo operations =====
|
||||
/**
|
||||
* Create a new combo with existing child nodes and combos.
|
||||
* @param combo combo ID or Combo model
|
||||
* @param childrenIds id array of children of the new combo
|
||||
* @group Combo
|
||||
*/
|
||||
* Create a new combo with existing child nodes and combos.
|
||||
* @param combo combo ID or Combo model
|
||||
* @param childrenIds id array of children of the new combo
|
||||
* @group Combo
|
||||
*/
|
||||
public createCombo(combo: string | ComboUserModel, childrenIds: string[], stack?: boolean) {
|
||||
// TODO
|
||||
}
|
||||
@ -318,27 +528,26 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param {String | ICombo} item combo item or id to be dissolve
|
||||
* @group Combo
|
||||
*/
|
||||
public uncombo(comboId: string | number, stack?: boolean) {
|
||||
public uncombo(comboId: ID, stack?: boolean) {
|
||||
// TODO
|
||||
}
|
||||
/**
|
||||
* Collapse a combo.
|
||||
* @param comboId combo id or item
|
||||
* @group Combo
|
||||
*/
|
||||
public collapseCombo(comboId: string | number, stack?: boolean) {
|
||||
* Collapse a combo.
|
||||
* @param comboId combo id or item
|
||||
* @group Combo
|
||||
*/
|
||||
public collapseCombo(comboId: ID, stack?: boolean) {
|
||||
// TODO
|
||||
}
|
||||
/**
|
||||
* Expand a combo.
|
||||
* @param combo combo ID 或 combo 实例
|
||||
* @group Combo
|
||||
*/
|
||||
public expandCombo(comboId: string | number, stack?: boolean) {
|
||||
* Expand a combo.
|
||||
* @param combo combo ID 或 combo 实例
|
||||
* @group Combo
|
||||
*/
|
||||
public expandCombo(comboId: ID, stack?: boolean) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
// ===== layout =====
|
||||
/**
|
||||
* Layout the graph (with current configurations if cfg is not assigned).
|
||||
@ -348,16 +557,20 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* @param {boolean} stack push it into stack
|
||||
* @group Layout
|
||||
*/
|
||||
public layout(cfg?: LayoutCommonConfig, align?: GraphAlignment, canvasPoint?: Point, stack?: boolean) {
|
||||
public layout(
|
||||
cfg?: LayoutCommonConfig,
|
||||
align?: GraphAlignment,
|
||||
canvasPoint?: Point,
|
||||
stack?: boolean,
|
||||
) {
|
||||
// TODO: LayoutConfig combination instead of LayoutCommonConfig
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Switch mode.
|
||||
* @param mode mode name
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
public setMode(mode: string) {
|
||||
@ -368,7 +581,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* Add behavior(s) to mode(s).
|
||||
* @param behaviors behavior names or configs
|
||||
* @param modes mode names
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
public addBehaviors(behaviors: BehaviorOptionsOf<B>[], modes: string | string[]) {
|
||||
@ -377,10 +590,10 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
this.hooks.behaviorchange.emit({
|
||||
action: 'add',
|
||||
modes: modesArr,
|
||||
behaviors: behaviorsArr
|
||||
behaviors: behaviorsArr,
|
||||
});
|
||||
// update the graph specification
|
||||
modesArr.forEach(mode => {
|
||||
modesArr.forEach((mode) => {
|
||||
this.specification.modes[mode] = this.specification.modes[mode].concat(behaviorsArr);
|
||||
});
|
||||
}
|
||||
@ -388,7 +601,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
* Remove behavior(s) from mode(s).
|
||||
* @param behaviors behavior configs with unique key
|
||||
* @param modes mode names
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
public removeBehaviors(behaviorKeys: string[], modes: string | string[]) {
|
||||
@ -396,22 +609,24 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
this.hooks.behaviorchange.emit({
|
||||
action: 'remove',
|
||||
modes: modesArr,
|
||||
behaviors: behaviorKeys
|
||||
behaviors: behaviorKeys,
|
||||
});
|
||||
// update the graph specification
|
||||
modesArr.forEach(mode => {
|
||||
behaviorKeys.forEach(key => {
|
||||
const oldBehavior = this.specification.modes[mode].find(behavior => isObject(behavior) && behavior.key === key);
|
||||
modesArr.forEach((mode) => {
|
||||
behaviorKeys.forEach((key) => {
|
||||
const oldBehavior = this.specification.modes[mode].find(
|
||||
(behavior) => isObject(behavior) && behavior.key === key,
|
||||
);
|
||||
const indexOfOldBehavior = this.specification.modes[mode].indexOf(oldBehavior);
|
||||
this.specification.modes[mode].splice(indexOfOldBehavior, 1);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Update a behavior on a mode.
|
||||
* @param behavior behavior configs, whose name indicates the behavior to be updated
|
||||
* @param mode mode name
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
public updateBehavior(behavior: BehaviorObjectOptionsOf<B>, mode?: string) {
|
||||
@ -420,11 +635,32 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
||||
modes: [mode],
|
||||
behaviors: [behavior],
|
||||
});
|
||||
// no need to update specification since the corresponding part is the same object as the behavior's option
|
||||
// no need to update specification since the corresponding part is the same object as the behavior's option
|
||||
// this.specification.modes[mode].forEach((b, i) => {
|
||||
// if (isObject(b) && b.key === behavior.key) {
|
||||
// this.specification.modes[mode][i] = behavior;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the graph instance and remove the related canvases.
|
||||
* @returns
|
||||
* @group Graph Instance
|
||||
*/
|
||||
public destroy() {
|
||||
this.canvas.destroy();
|
||||
this.backgroundCanvas.destroy();
|
||||
this.transientCanvas.destroy();
|
||||
|
||||
// TODO: destroy controllers and off the listeners
|
||||
// this.dataController.destroy();
|
||||
// this.interactionController.destroy();
|
||||
// this.layoutController.destroy();
|
||||
// this.themeController.destroy();
|
||||
// this.itemController.destroy();
|
||||
// this.extensionController.destroy();
|
||||
|
||||
this.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { comboFromNode } from "./data/comboFromNode"
|
||||
import DragCanvas from "./behavior/drag-canvas";
|
||||
import { Lib } from "../types/stdlib";
|
||||
import { CircleNode } from "./item/node";
|
||||
import { LineEdge } from "./item/edge";
|
||||
|
||||
const stdLib = {
|
||||
transforms: {
|
||||
@ -10,11 +12,15 @@ const stdLib = {
|
||||
layouts: {}, // from @antv/layout
|
||||
behaviors: {
|
||||
'drag-canvas': DragCanvas
|
||||
}, // @antv/g6-pc
|
||||
plugins: {}, // @antv/g6-plugin
|
||||
nodes: {}, // @antv/g6-element
|
||||
edges: {}, // @antv/g6-element
|
||||
combos: {}, // @antv/g6-element
|
||||
},
|
||||
plugins: {},
|
||||
nodes: {
|
||||
'circle-node': CircleNode
|
||||
},
|
||||
edges: {
|
||||
'line-edge': LineEdge
|
||||
},
|
||||
combos: {},
|
||||
}
|
||||
|
||||
const useLib: Lib = {
|
||||
|
231
packages/g6/src/stdlib/item/edge/base.ts
Normal file
231
packages/g6/src/stdlib/item/edge/base.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { DisplayObject, Line, Polyline } from '@antv/g';
|
||||
import { isNumber } from '@antv/util';
|
||||
import { Point } from '../../../types/common';
|
||||
import {
|
||||
EdgeDisplayModel,
|
||||
EdgeLabelShapeStyle,
|
||||
EdgeModelData,
|
||||
EdgeShapeMap,
|
||||
} from '../../../types/edge';
|
||||
import { ShapeStyle, State } from '../../../types/item';
|
||||
import {
|
||||
DEFAULT_LABEL_BG_PADDING,
|
||||
DEFAULT_SHAPE_STYLE,
|
||||
DEFAULT_TEXT_STYLE,
|
||||
formatPadding,
|
||||
upsertShape,
|
||||
} from '../../../util/shape';
|
||||
|
||||
export abstract class BaseEdge {
|
||||
type: string;
|
||||
baseDefaultStyles: {
|
||||
keyShape: ShapeStyle;
|
||||
labelShape: EdgeLabelShapeStyle;
|
||||
iconShape: ShapeStyle;
|
||||
[shapeId: string]: ShapeStyle;
|
||||
} = {
|
||||
keyShape: {
|
||||
...DEFAULT_SHAPE_STYLE,
|
||||
lineWidth: 1,
|
||||
stroke: '#fff',
|
||||
},
|
||||
labelShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
fill: '#000',
|
||||
},
|
||||
iconShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wAmHQJbNVdwAAAAAAAAAAABkARQnAQ',
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
};
|
||||
defaultStyles: {
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: EdgeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
[shapeId: string]: ShapeStyle;
|
||||
} = {};
|
||||
protected getDefaultStyles() {
|
||||
return this.defaultStyles;
|
||||
}
|
||||
abstract draw(
|
||||
model: EdgeDisplayModel,
|
||||
sourcePoint: Point,
|
||||
targetPoint: Point,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): {
|
||||
keyShape: DisplayObject;
|
||||
labelShape?: DisplayObject;
|
||||
iconShape?: DisplayObject;
|
||||
[otherShapeId: string]: DisplayObject;
|
||||
};
|
||||
public afterDraw(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
shapesChanged?: string[],
|
||||
): { [otherShapeId: string]: DisplayObject } {
|
||||
return {};
|
||||
}
|
||||
// shouldUpdate: (model: EdgeDisplayModel, prevModel: EdgeDisplayModel) => boolean = () => true;
|
||||
public setState: (
|
||||
name: string,
|
||||
value: boolean,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
) => void;
|
||||
|
||||
public drawLabelShape(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): {
|
||||
labelShape: DisplayObject;
|
||||
[id: string]: DisplayObject;
|
||||
} {
|
||||
const { keyShape } = shapeMap;
|
||||
|
||||
const shapeStyle = Object.assign({}, model.data?.labelShape);
|
||||
const {
|
||||
position,
|
||||
background,
|
||||
offsetX: propsOffsetX,
|
||||
offsetY: propsOffsetY,
|
||||
autoRotate = true,
|
||||
...otherStyle
|
||||
} = shapeStyle;
|
||||
|
||||
const positionPreset = {
|
||||
textAlign: 'center',
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
pointRatio: [0.5, 0.501],
|
||||
};
|
||||
if (isNumber(position)) {
|
||||
positionPreset.pointRatio = [position, position + 0.01];
|
||||
}
|
||||
switch (position) {
|
||||
case 'start':
|
||||
positionPreset.pointRatio = [0, 0.01];
|
||||
positionPreset.textAlign = 'left';
|
||||
positionPreset.offsetX = 4;
|
||||
break;
|
||||
case 'end':
|
||||
positionPreset.pointRatio = [0.99, 1];
|
||||
positionPreset.textAlign = 'right';
|
||||
positionPreset.offsetX = -4;
|
||||
break;
|
||||
default: // at middle by default
|
||||
break;
|
||||
}
|
||||
|
||||
const point = (keyShape as Line | Polyline).getPoint(positionPreset.pointRatio[0]);
|
||||
let positionStyle: any = { x: point.x, y: point.y };
|
||||
if (autoRotate) {
|
||||
const pointOffset = (keyShape as Line | Polyline).getPoint(positionPreset.pointRatio[1]);
|
||||
const angle = Math.atan((point.y - pointOffset.y) / (point.x - pointOffset.x)); // TODO: NaN
|
||||
const offsetX = propsOffsetX === undefined ? positionPreset.offsetX : propsOffsetX;
|
||||
const offsetY = propsOffsetY === undefined ? positionPreset.offsetY : propsOffsetY;
|
||||
// the projection is |offsetX| away from point, along the tangent line of the keyShape's path at point
|
||||
const projection = {
|
||||
x: point.x + offsetX * Math.cos(angle),
|
||||
y: point.y + offsetX * Math.sin(angle),
|
||||
};
|
||||
// the position of the text is |offsetY| away from projection, along the vertical line of the tangent line at point
|
||||
positionStyle = {
|
||||
x: projection.x + offsetY * Math.cos(Math.PI - angle),
|
||||
y: projection.y + offsetY * Math.sin(Math.PI - angle),
|
||||
transform: `rotate(${(angle / Math.PI) * 180})`,
|
||||
};
|
||||
}
|
||||
const style = {
|
||||
...this.defaultStyles.labelShape,
|
||||
textAlign: positionPreset.textAlign,
|
||||
...positionStyle,
|
||||
...otherStyle,
|
||||
};
|
||||
|
||||
const labelShape = upsertShape('text', 'labelShape', style, shapeMap);
|
||||
const shapes = { labelShape };
|
||||
if (background) {
|
||||
const textBBox = labelShape.getGeometryBounds();
|
||||
const { padding: propsPadding, ...backgroundStyle } = background;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const bgStyle = {
|
||||
fill: '#fff',
|
||||
radius: 4,
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3] + style.x,
|
||||
y: textBBox.min[1] - padding[0] + style.y,
|
||||
width: textBBox.max[0] - textBBox.min[0] + padding[1] + padding[3],
|
||||
height: textBBox.max[1] - textBBox.min[1] + padding[0] + padding[2],
|
||||
transform: positionStyle.transform,
|
||||
transformOrigin: 'center',
|
||||
};
|
||||
if (position === 'start') {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${padding[0] + bgStyle.height / 2}`;
|
||||
}
|
||||
if (position === 'end') {
|
||||
bgStyle.transformOrigin = `${padding[3] + bgStyle.width} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
|
||||
shapes['labelBgShape'] = upsertShape('rect', 'labelBgShape', bgStyle, shapeMap);
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public drawIconShape(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): DisplayObject {
|
||||
const { labelShape, labelBgShape, keyShape } = shapeMap;
|
||||
|
||||
const { iconShape: propsStyle, labelShape: labelShapeProps } = model.data || {};
|
||||
const shapeStyle = Object.assign({}, this.defaultStyles.iconShape, propsStyle);
|
||||
const iconShapeType = shapeStyle.text ? 'text' : 'image';
|
||||
if (iconShapeType === 'text') {
|
||||
shapeStyle.textAlign = 'left';
|
||||
shapeStyle.textBaseline = 'top';
|
||||
}
|
||||
const { width, height, fontSize } = shapeStyle;
|
||||
const w = (width || fontSize) as number;
|
||||
const h = (height || fontSize) as number;
|
||||
|
||||
if (labelShapeProps) {
|
||||
const referShape = labelBgShape || labelShape;
|
||||
const { min: referMin, halfExtents: referHalExtents } = referShape.getGeometryBounds();
|
||||
const {
|
||||
x: referX,
|
||||
y: referY,
|
||||
transform: referTransform,
|
||||
textAlign: labelAlign,
|
||||
} = referShape.attributes;
|
||||
shapeStyle.x = referMin[0] - w - 4 + referX;
|
||||
shapeStyle.y = referMin[1] + 2 + referY;
|
||||
if (referTransform) {
|
||||
shapeStyle.transform = referTransform;
|
||||
if (labelAlign === 'right') {
|
||||
shapeStyle.transformOrigin = `${w + 4 + referHalExtents[0] * 2}px ${h / 2}px`;
|
||||
} else if (labelAlign === 'left') {
|
||||
shapeStyle.transformOrigin = `${w + 4}px ${h / 2}px`;
|
||||
} else {
|
||||
// labelShape align 'center'
|
||||
shapeStyle.transformOrigin = `${w + 4 + referHalExtents[0]}px ${h / 2}px`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const midPoint = (keyShape as Line | Polyline).getPoint(0.5);
|
||||
shapeStyle.x = midPoint.x;
|
||||
shapeStyle.y = midPoint.y;
|
||||
// TODO: rotate
|
||||
}
|
||||
|
||||
return upsertShape(iconShapeType, 'iconShape', shapeStyle, shapeMap);
|
||||
}
|
||||
}
|
1
packages/g6/src/stdlib/item/edge/index.ts
Normal file
1
packages/g6/src/stdlib/item/edge/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./line";
|
70
packages/g6/src/stdlib/item/edge/line.ts
Normal file
70
packages/g6/src/stdlib/item/edge/line.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { Point } from '../../../types/common';
|
||||
import { EdgeDisplayModel, EdgeModelData, EdgeShapeMap } from '../../../types/edge';
|
||||
import { State } from '../../../types/item';
|
||||
import { mergeStyles, upsertShape } from '../../../util/shape';
|
||||
import { BaseEdge } from './base';
|
||||
|
||||
export class LineEdge extends BaseEdge {
|
||||
public type = 'line-edge';
|
||||
public defaultStyles = {
|
||||
keyShape: {
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
x2: 0,
|
||||
y2: 0,
|
||||
stroke: '#ccc',
|
||||
lineWidth: 1,
|
||||
}
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
// suggest to merge default styles like this to avoid style value missing
|
||||
this.defaultStyles = mergeStyles(this.baseDefaultStyles, this.defaultStyles);
|
||||
}
|
||||
public draw(
|
||||
model: EdgeDisplayModel,
|
||||
sourcePoint: Point,
|
||||
targetPoint: Point,
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): EdgeShapeMap {
|
||||
const { data = {} } = model;
|
||||
|
||||
let shapes: EdgeShapeMap = { keyShape: undefined };
|
||||
shapes.keyShape = this.drawKeyShape(model, sourcePoint, targetPoint, shapeMap, diffData);
|
||||
if (data.labelShape)
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawLabelShape(model, shapeMap, diffData),
|
||||
};
|
||||
if (data.iconShape) shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
|
||||
|
||||
// TODO: other shapes
|
||||
|
||||
return shapes;
|
||||
}
|
||||
public drawKeyShape(
|
||||
model: EdgeDisplayModel,
|
||||
sourcePoint: Point,
|
||||
targetPoint: Point,
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
) {
|
||||
const keyShapeStyle = Object.assign({}, this.defaultStyles.keyShape, model.data?.keyShape);
|
||||
const keyShape = upsertShape(
|
||||
'line',
|
||||
'keyShape',
|
||||
{
|
||||
...keyShapeStyle,
|
||||
x1: sourcePoint.x,
|
||||
y1: sourcePoint.y,
|
||||
x2: targetPoint.x,
|
||||
y2: targetPoint.y,
|
||||
},
|
||||
shapeMap,
|
||||
);
|
||||
return keyShape;
|
||||
}
|
||||
}
|
205
packages/g6/src/stdlib/item/node/base.ts
Normal file
205
packages/g6/src/stdlib/item/node/base.ts
Normal file
@ -0,0 +1,205 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { NodeDisplayModel } from '../../../types';
|
||||
import { ShapeStyle, State } from '../../../types/item';
|
||||
import { NodeLabelShapeStyle, NodeModelData, NodeShapeMap } from '../../../types/node';
|
||||
import {
|
||||
DEFAULT_LABEL_BG_PADDING,
|
||||
DEFAULT_SHAPE_STYLE,
|
||||
DEFAULT_TEXT_STYLE,
|
||||
formatPadding,
|
||||
upsertShape,
|
||||
} from '../../../util/shape';
|
||||
|
||||
export abstract class BaseNode {
|
||||
type: string;
|
||||
baseDefaultStyles: {
|
||||
keyShape: ShapeStyle;
|
||||
labelShape: NodeLabelShapeStyle;
|
||||
iconShape: ShapeStyle;
|
||||
[shapeId: string]: ShapeStyle;
|
||||
} = {
|
||||
keyShape: {
|
||||
...DEFAULT_SHAPE_STYLE,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#f00',
|
||||
lineWidth: 0,
|
||||
stroke: '#0f0',
|
||||
},
|
||||
labelShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
fill: '#000',
|
||||
position: 'bottom',
|
||||
},
|
||||
iconShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wAmHQJbNVdwAAAAAAAAAAABkARQnAQ',
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
};
|
||||
defaultStyles: {
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: NodeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
[shapeId: string]: ShapeStyle;
|
||||
} = {};
|
||||
abstract draw(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): {
|
||||
keyShape: DisplayObject;
|
||||
labelShape?: DisplayObject;
|
||||
iconShape?: DisplayObject;
|
||||
[otherShapeId: string]: DisplayObject;
|
||||
};
|
||||
|
||||
public afterDraw(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
shapesChanged?: string[],
|
||||
): { [otherShapeId: string]: DisplayObject } {
|
||||
return {};
|
||||
}
|
||||
// shouldUpdate: (model: NodeDisplayModel, prevModel: NodeDisplayModel) => boolean = () => true;
|
||||
public setState: (
|
||||
name: string,
|
||||
value: boolean,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
) => void;
|
||||
|
||||
abstract drawKeyShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): DisplayObject;
|
||||
|
||||
public drawLabelShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[], newState: State[] }
|
||||
): {
|
||||
labelShape: DisplayObject;
|
||||
[id: string]: DisplayObject;
|
||||
} {
|
||||
const { keyShape } = shapeMap;
|
||||
const keyShapeBox = keyShape.getGeometryBounds();
|
||||
const shapeStyle = Object.assign({}, model.data?.labelShape);
|
||||
const {
|
||||
position,
|
||||
background,
|
||||
offsetX: propsOffsetX,
|
||||
offsetY: propsOffsetY,
|
||||
...otherStyle
|
||||
} = shapeStyle;
|
||||
const positionPreset = {
|
||||
x: keyShapeBox.center[0],
|
||||
y: keyShapeBox.max[1],
|
||||
textBaseline: 'top',
|
||||
textAlign: 'center',
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
};
|
||||
switch (position) {
|
||||
case 'center':
|
||||
positionPreset.y = keyShapeBox.center[1];
|
||||
break;
|
||||
case 'top':
|
||||
positionPreset.y = keyShapeBox.min[1];
|
||||
positionPreset.textBaseline = 'bottom';
|
||||
positionPreset.offsetY = -4;
|
||||
break;
|
||||
case 'left':
|
||||
positionPreset.x = keyShapeBox.min[0];
|
||||
positionPreset.y = keyShapeBox.center[1];
|
||||
positionPreset.textAlign = 'right';
|
||||
positionPreset.textBaseline = 'middle';
|
||||
positionPreset.offsetX = -4;
|
||||
break;
|
||||
case 'right':
|
||||
positionPreset.x = keyShapeBox.max[0];
|
||||
positionPreset.y = keyShapeBox.center[1];
|
||||
positionPreset.textAlign = 'left';
|
||||
positionPreset.textBaseline = 'middle';
|
||||
positionPreset.offsetX = 4;
|
||||
break;
|
||||
default: // at bottom by default
|
||||
positionPreset.offsetY = 4;
|
||||
break;
|
||||
}
|
||||
const offsetX = propsOffsetX === undefined ? positionPreset.offsetX : propsOffsetX;
|
||||
const offsetY = propsOffsetY === undefined ? positionPreset.offsetY : propsOffsetY;
|
||||
positionPreset.x += offsetX;
|
||||
positionPreset.y += offsetY;
|
||||
|
||||
const style: any = {
|
||||
...this.defaultStyles.labelShape,
|
||||
...positionPreset,
|
||||
...otherStyle,
|
||||
};
|
||||
const labelShape = upsertShape('text', 'labelShape', style, shapeMap);
|
||||
const shapes = { labelShape };
|
||||
if (background) {
|
||||
const textBBox = labelShape.getGeometryBounds();
|
||||
const { padding: propsPadding, ...backgroundStyle } = background;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const bgStyle: any = {
|
||||
fill: '#fff',
|
||||
radius: 4,
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3] + style.x,
|
||||
y: textBBox.min[1] - padding[0] + style.y,
|
||||
width: textBBox.max[0] - textBBox.min[0] + padding[1] + padding[3],
|
||||
height: textBBox.max[1] - textBBox.min[1] + padding[0] + padding[2],
|
||||
};
|
||||
if (style.stransform) {
|
||||
bgStyle.transform = style.transform;
|
||||
bgStyle.transformOrigin = 'center';
|
||||
if (style.textAlign === 'left') {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${padding[0] + bgStyle.height / 2}`;
|
||||
}
|
||||
if (style.textAlign === 'right') {
|
||||
bgStyle.transformOrigin = `${padding[3] + bgStyle.width} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
shapes['labelBgShape'] = upsertShape('rect', 'labelBgShape', bgStyle, shapeMap);
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public drawIconShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[], newState: State[] }
|
||||
): DisplayObject {
|
||||
const { iconShape } = model.data || {};
|
||||
const shapeStyle = Object.assign({}, this.defaultStyles.iconShape, iconShape);
|
||||
const iconShapeType = shapeStyle.text ? 'text' : 'image';
|
||||
if (iconShapeType === 'image') {
|
||||
const { width, height } = shapeStyle;
|
||||
if (!iconShape.hasOwnProperty('x')) shapeStyle.x = -width / 2;
|
||||
if (!iconShape.hasOwnProperty('y')) shapeStyle.y = -height / 2;
|
||||
} else {
|
||||
shapeStyle.textAlign = 'center';
|
||||
shapeStyle.textBaseline = 'middle';
|
||||
}
|
||||
return upsertShape(iconShapeType, 'iconShape', shapeStyle, shapeMap);
|
||||
}
|
||||
|
||||
public drawOtherShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): { [id: string]: DisplayObject; } {
|
||||
return {}
|
||||
}
|
||||
}
|
61
packages/g6/src/stdlib/item/node/circle.ts
Normal file
61
packages/g6/src/stdlib/item/node/circle.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { NodeDisplayModel } from '../../../types';
|
||||
import { State } from '../../../types/item';
|
||||
import { NodeModelData, NodeShapeMap } from '../../../types/node';
|
||||
import { mergeStyles, upsertShape } from '../../../util/shape';
|
||||
import { BaseNode } from './base';
|
||||
|
||||
export class CircleNode extends BaseNode {
|
||||
override defaultStyles = {
|
||||
keyShape: {
|
||||
r: 15,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#f00',
|
||||
lineWidth: 0,
|
||||
stroke: '#0f0',
|
||||
}
|
||||
};
|
||||
constructor() {
|
||||
super();
|
||||
// suggest to merge default styles like this to avoid style value missing
|
||||
this.defaultStyles = mergeStyles(this.baseDefaultStyles, this.defaultStyles);
|
||||
}
|
||||
public draw(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): NodeShapeMap {
|
||||
const { data = {} } = model;
|
||||
let shapes: NodeShapeMap = { keyShape: undefined };
|
||||
|
||||
shapes.keyShape = this.drawKeyShape(model, shapeMap, diffData);
|
||||
if (data.labelShape) {
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawLabelShape(model, shapeMap, diffData),
|
||||
};
|
||||
}
|
||||
if (data.iconShape) {
|
||||
shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
|
||||
}
|
||||
if (data.otherShapes && this.drawOtherShapes) {
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawOtherShapes(model, shapeMap, diffData)
|
||||
}
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public drawKeyShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[], current: State[] }
|
||||
): DisplayObject {
|
||||
const shapeStyle = Object.assign({}, this.defaultStyles.keyShape, model.data?.keyShape);
|
||||
return upsertShape('circle', 'keyShape', shapeStyle, shapeMap);
|
||||
}
|
||||
}
|
1
packages/g6/src/stdlib/item/node/index.ts
Normal file
1
packages/g6/src/stdlib/item/node/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./circle";
|
@ -1,7 +1,7 @@
|
||||
import { Node as GNode, PlainObject } from '@antv/graphlib';
|
||||
import { AnimateAttr } from "./animate";
|
||||
import { Padding } from "./common";
|
||||
import { Encode, IItem, LabelBackground, ShapeAttrEncode, ShapesEncode } from "./item";
|
||||
import { Encode, IItem, LabelBackground, ShapeAttrEncode, ShapesEncode, ShapeStyle } from "./item";
|
||||
import { AnchorPoint } from "./node";
|
||||
|
||||
export type ComboLabelPosition = 'bottom' | 'top' | 'left' | 'left-top' | 'right' | 'ouside-top' | 'outside-left' | 'outside-right' | 'outside-bottom';
|
||||
@ -18,29 +18,20 @@ export interface ComboModelData extends ComboUserModelData {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface ComboLabelShapeStyle extends ShapeStyle {
|
||||
position?: ComboLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
}
|
||||
|
||||
/** Displayed data, only for drawing and not received by users. */
|
||||
export interface ComboDisplayModelData extends ComboModelData {
|
||||
keyShape?: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
labelShape?: {
|
||||
position?: ComboLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
iconShape?: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: ComboLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
otherShapes?: {
|
||||
[shapeName: string]: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
}
|
||||
[shapeId: string]: ShapeStyle;
|
||||
};
|
||||
anchorPoints?: AnchorPoint[];
|
||||
fixSize?: number | number[];
|
||||
|
@ -22,4 +22,6 @@ export interface FetchDataConfig {
|
||||
export type GraphCore = IGraph<NodeModelData, EdgeModelData>;
|
||||
export type DisplayGraphCore = IGraph<NodeDisplayModelData, EdgeDisplayModelData>;
|
||||
|
||||
export type TransformerFn = (data: GraphData) => GraphData
|
||||
export type TransformerFn = (data: GraphData) => GraphData
|
||||
|
||||
export type DataChangeType = 'replace' | 'mergeReplace' | 'union' | 'remove' | 'update';
|
@ -1,6 +1,7 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { Edge as GEdge, PlainObject } from '@antv/graphlib';
|
||||
import { AnimateAttr } from "./animate";
|
||||
import { Encode, IItem, LabelBackground, ShapeAttrEncode, ShapesEncode } from "./item";
|
||||
import { Encode, IItem, LabelBackground, ShapeAttrEncode, ShapesEncode, ShapeStyle } from "./item";
|
||||
|
||||
|
||||
export interface EdgeUserModelData extends PlainObject { };
|
||||
@ -8,29 +9,20 @@ export interface EdgeModelData extends EdgeUserModelData {
|
||||
visible?: boolean;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export interface EdgeLabelShapeStyle extends ShapeStyle {
|
||||
position?: EdgeLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
autoRotate?: boolean;
|
||||
}
|
||||
export interface EdgeDisplayModelData extends EdgeModelData {
|
||||
keyShape?: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
labelShape?: {
|
||||
position?: EdgeLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
autoRotate?: boolean;
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
iconShape?: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: EdgeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
otherShapes?: {
|
||||
[shapeName: string]: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
}
|
||||
[shapeId: string]: ShapeStyle
|
||||
};
|
||||
sourceAnchor?: number;
|
||||
targetAnchor?: number;
|
||||
@ -64,6 +56,13 @@ export interface EdgeEncode extends EdgeShapesEncode {
|
||||
type?: string | Encode<string>;
|
||||
}
|
||||
|
||||
export interface EdgeShapeMap {
|
||||
keyShape: DisplayObject,
|
||||
labelShape?: DisplayObject,
|
||||
iconShape?: DisplayObject,
|
||||
[otherShapeId: string]: DisplayObject
|
||||
}
|
||||
|
||||
// TODO
|
||||
export interface IEdge extends IItem {
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import EventEmitter from '@antv/event-emitter';
|
||||
import { Canvas } from '@antv/g';
|
||||
import { ID } from '@antv/graphlib';
|
||||
import { Hooks } from '../types/hook';
|
||||
import { AnimateCfg } from './animate';
|
||||
import { BehaviorObjectOptionsOf, BehaviorOptionsOf, BehaviorRegistry } from './behavior';
|
||||
import { ComboModel, ComboUserModel } from './combo';
|
||||
import { Padding, Point } from './common';
|
||||
import { GraphData } from './data';
|
||||
import { DataChangeType, GraphData } from './data';
|
||||
import { EdgeModel, EdgeUserModel } from './edge';
|
||||
import { ITEM_TYPE } from './item';
|
||||
import { LayoutCommonConfig } from './layout';
|
||||
@ -13,9 +15,17 @@ import { Specification } from './spec';
|
||||
import { FitViewRules, GraphAlignment } from './view';
|
||||
|
||||
export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends EventEmitter {
|
||||
|
||||
hooks: Hooks;
|
||||
canvas: Canvas;
|
||||
destroyed: boolean;
|
||||
|
||||
// ===== graph instance ===
|
||||
/**
|
||||
* Destroy the graph instance and remove the related canvases.
|
||||
* @returns
|
||||
* @group Graph Instance
|
||||
*/
|
||||
destroy: () => void;
|
||||
/**
|
||||
* Update the specs(configurations).
|
||||
*/
|
||||
@ -30,38 +40,125 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
/**
|
||||
* Find a node's inner data according to id or function.
|
||||
* @param { string | Function} condition id or condition function
|
||||
* @returns result node
|
||||
* @group Item
|
||||
* @returns result node's inner data
|
||||
* @group Data
|
||||
*/
|
||||
getNodeData: (condition: string | Function) => NodeModel | undefined;
|
||||
/**
|
||||
* Find an edge's inner data according to id or function.
|
||||
* @param { string | Function} condition id or condition function
|
||||
* @returns result edge
|
||||
* @group Item
|
||||
* @returns result edge's inner data
|
||||
* @group Data
|
||||
*/
|
||||
getEdgeData: (condition: string | Function) => EdgeModel | undefined;
|
||||
/**
|
||||
* Find a combo's inner data according to id or function.
|
||||
* @param { string | Function} condition id or condition function
|
||||
* @returns result combo
|
||||
* @group Item
|
||||
* @returns result combo's inner data
|
||||
* @group Data
|
||||
*/
|
||||
getComboData: (condition: string | Function) => ComboModel | undefined;
|
||||
/**
|
||||
* Get all the nodes' inner data
|
||||
* @returns all nodes' inner data on the graph
|
||||
* @group Data
|
||||
*/
|
||||
getAllNodesData: () => NodeModel[];
|
||||
/**
|
||||
* Get all the edges' inner data
|
||||
* @returns all edges' inner data on the graph
|
||||
* @group Data
|
||||
*/
|
||||
getAllEdgesData: () => EdgeModel[];
|
||||
/**
|
||||
* Get all the combos' inner data
|
||||
* @returns all combos' inner data on the graph
|
||||
* @group Data
|
||||
*/
|
||||
getAllCombosData: () => ComboModel[];
|
||||
/**
|
||||
* Input data and render the graph.
|
||||
* If there is old data, diffs and changes it.
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
* @group Data
|
||||
*/
|
||||
read: (data: GraphData) => void;
|
||||
/**
|
||||
* Change graph data.
|
||||
* @param data new data
|
||||
* @param type the way to change data, 'replace' means discard the old data and use the new one; 'mergeReplace' means merge the common part, remove (old - new), add (new - old)
|
||||
* @returns
|
||||
* @group Data
|
||||
*/
|
||||
changeData: (data: GraphData, type: 'replace' | 'mergeReplace') => void;
|
||||
/**
|
||||
* Clear the graph, means remove all the items on the graph.
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
clear: () => void;
|
||||
|
||||
/**
|
||||
* Find items which has the state.
|
||||
* @param itemType item type
|
||||
* @param state state name
|
||||
* @param value state value, true by default
|
||||
* @param additionalFilter additional filter function
|
||||
* @returns items that is the type and has the state
|
||||
* @group Item
|
||||
*/
|
||||
findIdByState: (
|
||||
itemType: ITEM_TYPE,
|
||||
state: string,
|
||||
value?: string | boolean,
|
||||
additionalFilter?: (model: NodeModel | EdgeModel | ComboModel) => boolean,
|
||||
) => ID[];
|
||||
/**
|
||||
* Add one or more node/edge/combo data to the graph.
|
||||
* @param itemType item type
|
||||
* @param model user data
|
||||
* @param stack whether push this operation to stack
|
||||
* @returns whehter success
|
||||
* @group Data
|
||||
*/
|
||||
addData: (
|
||||
itemType: ITEM_TYPE,
|
||||
model:
|
||||
| NodeUserModel
|
||||
| EdgeUserModel
|
||||
| ComboUserModel
|
||||
| NodeUserModel[]
|
||||
| EdgeUserModel[]
|
||||
| ComboUserModel[],
|
||||
stack?: boolean,
|
||||
) => NodeModel | EdgeModel | ComboModel | NodeModel[] | EdgeModel[] | ComboModel[];
|
||||
/**
|
||||
* Remove one or more node/edge/combo data from the graph.
|
||||
* @param item the item to be removed
|
||||
* @param stack whether push this operation to stack
|
||||
* @returns whehter success
|
||||
* @group Data
|
||||
*/
|
||||
removeData: (itemType: ITEM_TYPE, id: ID | ID[], stack?: boolean) => void;
|
||||
/**
|
||||
* Update one or more node/edge/combo data on the graph.
|
||||
* @param item the item to be updated
|
||||
* @param model update configs
|
||||
* @param {boolean} stack whether push this operation to stack
|
||||
* @group Data
|
||||
*/
|
||||
updateData: (
|
||||
itemType: ITEM_TYPE,
|
||||
model:
|
||||
| Partial<NodeUserModel>
|
||||
| Partial<EdgeUserModel>
|
||||
| Partial<
|
||||
| ComboUserModel
|
||||
| Partial<NodeUserModel>[]
|
||||
| Partial<EdgeUserModel>[]
|
||||
| Partial<ComboUserModel>[]
|
||||
>,
|
||||
stack?: boolean,
|
||||
) => NodeModel | EdgeModel | ComboModel | NodeModel[] | EdgeModel[] | ComboModel[];
|
||||
|
||||
// ===== view operations =====
|
||||
|
||||
@ -70,7 +167,7 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* @param dx x of the relative vector
|
||||
* @param dy y of the relative vector
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
move: (dx: number, dy: number, animateCfg?: AnimateCfg) => void;
|
||||
@ -80,7 +177,7 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* @param y position on the canvas to align
|
||||
* @param alignment alignment of the graph content
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
moveTo: (x: number, y: number, alignment: GraphAlignment, animateCfg?: AnimateCfg) => void;
|
||||
@ -89,7 +186,7 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* @param ratio relative ratio to zoom
|
||||
* @param center zoom center
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
zoom: (ratio: number, center?: Point, animateCfg?: AnimateCfg) => void;
|
||||
@ -98,7 +195,7 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* @param toRatio specified ratio
|
||||
* @param center zoom center
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
zoomTo: (toRatio: number, center?: Point, animateCfg?: AnimateCfg) => void;
|
||||
@ -107,14 +204,14 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* @param padding padding while fitting
|
||||
* @param rules rules for fitting
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
fitView: (padding?: Padding, rules?: FitViewRules, animateCfg?: AnimateCfg) => void;
|
||||
/**
|
||||
* Fit the graph center to the view center.
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
fitCenter: (animateCfg?: AnimateCfg) => void;
|
||||
@ -122,100 +219,78 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* Move the graph to make the item align the view center.
|
||||
* @param item node/edge/combo item or its id
|
||||
* @param animateCfg animation configurations
|
||||
* @returns
|
||||
* @returns
|
||||
* @group View
|
||||
*/
|
||||
focusItem: (ids: string | number | (string | number)[], animateCfg?: AnimateCfg) => void;
|
||||
|
||||
focusItem: (ids: ID | ID[], animateCfg?: AnimateCfg) => void;
|
||||
|
||||
// ===== item operations =====
|
||||
/**
|
||||
* Find items which has the state.
|
||||
* @param itemType item type
|
||||
* @param state state name
|
||||
* @param additionalFilter additional filter function
|
||||
* @returns items that is the type and has the state
|
||||
* @group Item
|
||||
*/
|
||||
findIdByState: (itemType: ITEM_TYPE, state: string, additionalFilter?: (model: NodeModel | EdgeModel | ComboModel) => boolean) => (string | number)[];
|
||||
/**
|
||||
* Add an item or items to the graph.
|
||||
* @param itemType item type
|
||||
* @param model user data
|
||||
* @param stack whether push this operation to stack
|
||||
* @returns whehter success
|
||||
* @group Item
|
||||
*/
|
||||
addItem: (itemType: ITEM_TYPE, model: NodeUserModel | EdgeUserModel | ComboUserModel | NodeUserModel[] | EdgeUserModel[] | ComboUserModel[], stack?: boolean) => boolean;
|
||||
|
||||
/**
|
||||
* Remove an item or items from the graph.
|
||||
* @param item the item to be removed
|
||||
* @param stack whether push this operation to stack
|
||||
* @returns whehter success
|
||||
* @group Item
|
||||
*/
|
||||
removeItem: (itemType: ITEM_TYPE, id: string | number | (string | number)[], stack?: boolean) => boolean;
|
||||
/**
|
||||
* Update an item or items on the graph.
|
||||
* @param item the item to be updated
|
||||
* @param model update configs
|
||||
* @param {boolean} stack whether push this operation to stack
|
||||
* @group Item
|
||||
*/
|
||||
updateItem: (itemType: ITEM_TYPE, model: Partial<NodeUserModel> | Partial<EdgeUserModel> | Partial<ComboUserModel | Partial<NodeUserModel>[] | Partial<EdgeUserModel>[] | Partial<ComboUserModel>[]>, stack?: boolean) => boolean;
|
||||
/**
|
||||
* Show the item(s).
|
||||
* @param ids the item id(s) to be shown
|
||||
* @returns
|
||||
* @group Item
|
||||
* @returns
|
||||
* @group Data
|
||||
*/
|
||||
showItem: (ids: string | number | (string | number)[]) => void;
|
||||
showItem: (ids: ID | ID[]) => void;
|
||||
/**
|
||||
* Hide the item(s).
|
||||
* @param ids the item id(s) to be hidden
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
hideItem: (ids: string | number | (string | number)[]) => void;
|
||||
hideItem: (ids: ID | ID[]) => void;
|
||||
/**
|
||||
* Set state for the item(s).
|
||||
* @param ids the id(s) for the item(s) to be set
|
||||
* @param state the state name
|
||||
* @param value state value
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
setItemState: (ids: string | number | (string | number)[], state: string, value: boolean) => void;
|
||||
|
||||
setItemState: (ids: ID | ID[], state: string, value: boolean) => void;
|
||||
/**
|
||||
* Get the state value for an item.
|
||||
* @param id the id for the item
|
||||
* @param states the state name
|
||||
* @returns {boolean | string} the state value
|
||||
* @group Item
|
||||
*/
|
||||
getItemState: (id: ID, state: string) => boolean | string;
|
||||
/**
|
||||
* Clear all the states for item(s).
|
||||
* @param ids the id(s) for the item(s) to be clear
|
||||
* @param states the states' names, all the states wil be cleared if states is not assigned
|
||||
* @returns
|
||||
* @group Item
|
||||
*/
|
||||
clearItemState: (ids: ID | ID[], states?: string[]) => void;
|
||||
|
||||
// ===== combo operations =====
|
||||
/**
|
||||
* Create a new combo with existing child nodes and combos.
|
||||
* @param combo combo ID or Combo model
|
||||
* @param childrenIds id array of children of the new combo
|
||||
* @group Combo
|
||||
*/
|
||||
* Create a new combo with existing child nodes and combos.
|
||||
* @param combo combo ID or Combo model
|
||||
* @param childrenIds id array of children of the new combo
|
||||
* @group Combo
|
||||
*/
|
||||
createCombo: (combo: string | ComboUserModel, childrenIds: string[], stack?: boolean) => void;
|
||||
/**
|
||||
* dissolve combo
|
||||
* @param {String | ICombo} item combo item or id to be dissolve
|
||||
*/
|
||||
uncombo: (comboId: string | number, stack?: boolean) => void;
|
||||
uncombo: (comboId: ID, stack?: boolean) => void;
|
||||
/**
|
||||
* Collapse a combo.
|
||||
* @param comboId combo id or item
|
||||
* @group Combo
|
||||
*/
|
||||
collapseCombo: (comboId: string | number, stack?: boolean) => void;
|
||||
* Collapse a combo.
|
||||
* @param comboId combo id or item
|
||||
* @group Combo
|
||||
*/
|
||||
collapseCombo: (comboId: ID, stack?: boolean) => void;
|
||||
/**
|
||||
* Expand a combo.
|
||||
* @group Combo
|
||||
* @param combo combo ID 或 combo 实例
|
||||
* @group Combo
|
||||
*/
|
||||
expandCombo: (comboId: string | number, stack?: boolean) => void;
|
||||
|
||||
* Expand a combo.
|
||||
* @group Combo
|
||||
* @param combo combo ID 或 combo 实例
|
||||
* @group Combo
|
||||
*/
|
||||
expandCombo: (comboId: ID, stack?: boolean) => void;
|
||||
|
||||
// ===== layout =====
|
||||
/**
|
||||
@ -226,14 +301,18 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* @param {boolean} stack push it into stack
|
||||
* @group Layout
|
||||
*/
|
||||
layout: (cfg?: LayoutCommonConfig, align?: GraphAlignment, canvasPoint?: Point, stack?: boolean) => void;
|
||||
|
||||
layout: (
|
||||
cfg?: LayoutCommonConfig,
|
||||
align?: GraphAlignment,
|
||||
canvasPoint?: Point,
|
||||
stack?: boolean,
|
||||
) => void;
|
||||
|
||||
// ===== interaction =====
|
||||
/**
|
||||
* Switch mode.
|
||||
* @param mode mode name
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
setMode: (mode: string) => void;
|
||||
@ -241,14 +320,14 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* Add behavior(s) to mode(s).
|
||||
* @param behaviors behavior names or configs
|
||||
* @param modes mode names
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
addBehaviors: (behaviors: BehaviorOptionsOf<B>[], modes: string | string[]) => void;
|
||||
/**
|
||||
* Remove behavior(s) from mode(s).
|
||||
* @param behaviors behavior names or configs
|
||||
* @param modes mode names
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
removeBehaviors: (behaviorKeys: string[], modes: string | string[]) => void;
|
||||
@ -256,8 +335,8 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
||||
* Update a behavior on a mode.
|
||||
* @param behavior behavior configs, whose name indicates the behavior to be updated
|
||||
* @param mode mode name
|
||||
* @returns
|
||||
* @returns
|
||||
* @group Interaction
|
||||
*/
|
||||
updateBehavior: (behavior: BehaviorObjectOptionsOf<B>, mode?: string) => void;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { GraphCore, GraphData } from "./data";
|
||||
import { NodeUserModel } from "./node";
|
||||
import { ComboUserModel } from "./combo";
|
||||
import { EdgeUserModel } from "./edge";
|
||||
import { DataChangeType, GraphCore, GraphData } from "./data";
|
||||
import { NodeModel, NodeModelData, NodeUserModel } from "./node";
|
||||
import { EdgeModel, EdgeModelData, EdgeUserModel } from "./edge";
|
||||
import { ITEM_TYPE } from "./item";
|
||||
import { GraphChange, ID } from "@antv/graphlib";
|
||||
|
||||
export interface IHook<T> {
|
||||
name: string;
|
||||
@ -15,14 +15,15 @@ export interface IHook<T> {
|
||||
export interface Hooks {
|
||||
'init': IHook<void>;
|
||||
// data
|
||||
'datachange': IHook<{ data: GraphData }>;
|
||||
// data, item
|
||||
'additems': IHook<{ type: ITEM_TYPE, models: NodeUserModel[] | EdgeUserModel[] | ComboUserModel[] }>
|
||||
// data, item
|
||||
'removeitems': IHook<{ type: ITEM_TYPE, ids: (string | number)[] }>
|
||||
// data, item
|
||||
'updateitems': IHook<{ type: ITEM_TYPE, models: Partial<NodeUserModel>[] | Partial<EdgeUserModel>[] | Partial<ComboUserModel>[] }>
|
||||
|
||||
'datachange': IHook<{
|
||||
type: DataChangeType;
|
||||
data: GraphData
|
||||
}>;
|
||||
'itemchange': IHook<{
|
||||
type: ITEM_TYPE;
|
||||
changes: GraphChange<NodeModelData, EdgeModelData>[];
|
||||
graphCore: GraphCore;
|
||||
}>;
|
||||
'render': IHook<{ graphCore: GraphCore }>; // TODO: define param template
|
||||
// 'layout': IHook<any>; // TODO: define param template
|
||||
// 'updatelayout': IHook<any>; // TODO: define param template
|
||||
@ -32,8 +33,12 @@ export interface Hooks {
|
||||
modes: string[];
|
||||
behaviors: (string | { type: string, key: string })[];
|
||||
}>;
|
||||
'itemstatechange': IHook<{
|
||||
ids: ID[],
|
||||
states?: string[],
|
||||
value?: boolean
|
||||
}>; // TODO: define param template
|
||||
// 'viewportchange': IHook<any>; // TODO: define param template
|
||||
// 'itemstatechange': IHook<any>; // TODO: define param template
|
||||
// 'destroy': IHook<any>; // TODO: define param template
|
||||
// TODO: more timecycles here
|
||||
};
|
||||
|
@ -1,18 +1,34 @@
|
||||
import { Group } from "@antv/g";
|
||||
import { AnimateAttr } from "./animate";
|
||||
import { ComboDisplayModel, ComboModel } from "./combo";
|
||||
import { EdgeDisplayModel, EdgeModel } from "./edge";
|
||||
import { NodeDisplayModel, NodeModel } from "./node";
|
||||
import { ID } from '@antv/graphlib';
|
||||
import { AnimateAttr } from './animate';
|
||||
import {
|
||||
ComboDisplayModel,
|
||||
ComboEncode,
|
||||
ComboModel,
|
||||
ComboModelData,
|
||||
ComboUserModel,
|
||||
} from './combo';
|
||||
import { EdgeDisplayModel, EdgeEncode, EdgeModel, EdgeModelData, EdgeUserModel } from './edge';
|
||||
import { NodeDisplayModel, NodeEncode, NodeModel, NodeModelData, NodeUserModel } from './node';
|
||||
|
||||
export interface ShapeStyle {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate?: AnimateAttr;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
r?: number;
|
||||
}
|
||||
|
||||
export interface Encode<T> {
|
||||
fields: string[],
|
||||
formatter: (values: unknown[]) => T;
|
||||
fields: string[];
|
||||
formatter: (values: NodeUserModel | EdgeUserModel | ComboUserModel) => T;
|
||||
}
|
||||
|
||||
export interface ShapeAttrEncode {
|
||||
[shapeAttr: string]: unknown | Encode<unknown>;
|
||||
animate: AnimateAttr | Encode<AnimateAttr>;
|
||||
};
|
||||
animate?: AnimateAttr | Encode<AnimateAttr>;
|
||||
}
|
||||
|
||||
export interface LabelBackground {
|
||||
fill?: string;
|
||||
@ -20,25 +36,38 @@ export interface LabelBackground {
|
||||
lineWidth?: number;
|
||||
radius?: number[] | number;
|
||||
padding?: number[] | number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ShapesEncode {
|
||||
keyShape?: ShapeAttrEncode;
|
||||
iconShape?: ShapeAttrEncode;
|
||||
otherShapes?: {
|
||||
[shapeName: string]: {
|
||||
[shapeId: string]: {
|
||||
[shapeAtrr: string]: unknown | Encode<unknown>;
|
||||
animate: AnimateAttr | Encode<AnimateAttr>;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type ITEM_TYPE = 'node' | 'edge' | 'combo';
|
||||
|
||||
export type ItemModelData = NodeModelData | EdgeModelData | ComboModelData;
|
||||
|
||||
export type ItemModel = NodeModel | EdgeModel | ComboModel;
|
||||
|
||||
export type ItemDisplayModel = NodeDisplayModel | EdgeDisplayModel | ComboDisplayModel;
|
||||
|
||||
export type DisplayMapper =
|
||||
| ((model: ItemModel) => ItemDisplayModel)
|
||||
| NodeEncode
|
||||
| EdgeEncode
|
||||
| ComboEncode;
|
||||
|
||||
export type State = {
|
||||
name: string;
|
||||
value: boolean | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base item of node / edge / combo.
|
||||
*/
|
||||
@ -60,21 +89,29 @@ export interface IItem {
|
||||
// type: 'node' | 'edge' | 'combo';
|
||||
|
||||
/** Gets the inner model. */
|
||||
getModel: () => ItemModel;
|
||||
// getModel: () => ItemModel;
|
||||
/** Gets the id in model. */
|
||||
getID: () => string | number;
|
||||
getID: () => ID;
|
||||
/** Gets the item's type. */
|
||||
getType: () => 'node' | 'edge' | 'combo';
|
||||
/**
|
||||
* Draws the shapes.
|
||||
* @internal
|
||||
* */
|
||||
draw: () => void;
|
||||
draw: (
|
||||
displayModel: ItemDisplayModel,
|
||||
diffData?: { previous: ItemModelData; current: ItemModelData },
|
||||
diffState?: { previous: State[], current: State[] },
|
||||
) => void;
|
||||
/**
|
||||
* Updates the shapes.
|
||||
* @internal
|
||||
* */
|
||||
update: (model: ItemModel) => void;
|
||||
update: (
|
||||
model: ItemModel,
|
||||
diffData: { previous: ItemModelData; current: ItemModelData },
|
||||
isUpdate?: boolean,
|
||||
) => void;
|
||||
/** Puts the item to the front in its graphic group. */
|
||||
toFront: () => void;
|
||||
/** Puts the item to the back in its graphic group. */
|
||||
@ -88,14 +125,14 @@ export interface IItem {
|
||||
/** Sets a state value to the item. */
|
||||
setState: (state: string, value: string | boolean) => void;
|
||||
/** Returns the state if it is true/string. Returns false otherwise. */
|
||||
hasState: (state: string) => { name: string, value: boolean | string } | false;
|
||||
hasState: (state: string) => string | boolean;
|
||||
/** Get all the true or string states of the item. */
|
||||
getStates: () => {
|
||||
name: string,
|
||||
value: boolean | string
|
||||
name: string;
|
||||
value: boolean | string;
|
||||
}[];
|
||||
/** Set all the state to false. */
|
||||
clearStates: (states?: string[]) => void;
|
||||
/** Destroy the item. */
|
||||
destroy: () => void;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { Node as GNode, PlainObject } from '@antv/graphlib';
|
||||
import { AnimateAttr } from "./animate";
|
||||
import { Encode, IItem, LabelBackground, ShapeAttrEncode, ShapesEncode } from "./item";
|
||||
import { AnimateAttr } from './animate';
|
||||
import { Encode, IItem, LabelBackground, ShapeAttrEncode, ShapesEncode, ShapeStyle } from './item';
|
||||
|
||||
export type NodeLabelPosition = 'bottom' | 'center' | 'top' | 'left' | 'right';
|
||||
|
||||
@ -15,32 +16,22 @@ export interface NodeModelData extends NodeUserModelData {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface NodeLabelShapeStyle extends ShapeStyle {
|
||||
position?: NodeLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
}
|
||||
|
||||
/** Data in display model. */
|
||||
export interface NodeDisplayModelData extends NodeModelData {
|
||||
keyShape?: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
labelShape?: {
|
||||
position?: NodeLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
iconShape?: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
};
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: NodeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
otherShapes?: {
|
||||
[shapeName: string]: {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
}
|
||||
[shapeId: string]: ShapeStyle;
|
||||
};
|
||||
anchorPoints?: AnchorPoint[]
|
||||
anchorPoints?: AnchorPoint[];
|
||||
}
|
||||
|
||||
/** User input model. */
|
||||
@ -60,7 +51,8 @@ export interface AnchorPoint {
|
||||
animate: AnimateAttr;
|
||||
}
|
||||
|
||||
interface NodeLabelShapeAttrEncode extends ShapeAttrEncode { // TODO: extends Text shape attr, import from G
|
||||
interface NodeLabelShapeAttrEncode extends ShapeAttrEncode {
|
||||
// TODO: extends Text shape attr, import from G
|
||||
position?: NodeLabelPosition | Encode<NodeLabelPosition>;
|
||||
offsetX?: number | Encode<number>;
|
||||
offsetY?: number | Encode<number>;
|
||||
@ -68,13 +60,18 @@ interface NodeLabelShapeAttrEncode extends ShapeAttrEncode { // TODO: extends Te
|
||||
}
|
||||
export interface NodeShapesEncode extends ShapesEncode {
|
||||
labelShape?: NodeLabelShapeAttrEncode;
|
||||
anchorPoints?: AnchorPoint[] | Encode<AnchorPoint[]>
|
||||
anchorPoints?: AnchorPoint[] | Encode<AnchorPoint[]>;
|
||||
}
|
||||
export interface NodeEncode extends NodeShapesEncode {
|
||||
type?: string | Encode<string>;
|
||||
}
|
||||
|
||||
// TODO
|
||||
export interface INode extends IItem {
|
||||
export interface NodeShapeMap {
|
||||
keyShape: DisplayObject;
|
||||
labelShape?: DisplayObject;
|
||||
iconShape?: DisplayObject;
|
||||
[otherShapeId: string]: DisplayObject;
|
||||
}
|
||||
|
||||
}
|
||||
// TODO
|
||||
export interface INode extends IItem {}
|
||||
|
@ -5,13 +5,14 @@ import { EdgeDisplayModel, EdgeEncode, EdgeModel, EdgeShapesEncode } from "./edg
|
||||
import { NodeDisplayModel, NodeEncode, NodeModel, NodeShapesEncode } from "./node";
|
||||
import { GraphAlignment } from "./view";
|
||||
import { LayoutCommonConfig } from "./layout";
|
||||
import { ComboDisplayModel, ComboEncode, ComboModel } from "./combo";
|
||||
import { ComboDisplayModel, ComboEncode, ComboModel, ComboShapesEncode } from "./combo";
|
||||
import { BehaviorOptionsOf, BehaviorRegistry } from "./behavior";
|
||||
|
||||
type rendererName = 'canvas' | 'svg' | 'webgl';
|
||||
|
||||
export interface Specification<B extends BehaviorRegistry> {
|
||||
type: 'graph' | 'tree';
|
||||
container: string | HTMLElement;
|
||||
width?: number;
|
||||
height?: number;
|
||||
renderer?: rendererName | {
|
||||
@ -34,19 +35,19 @@ export interface Specification<B extends BehaviorRegistry> {
|
||||
}[] | TransformerFn[];
|
||||
|
||||
/** item */
|
||||
node?: (data: NodeModel) => NodeDisplayModel | NodeEncode;
|
||||
edge?: (data: EdgeModel) => EdgeDisplayModel | EdgeEncode;
|
||||
combo?: (data: ComboModel) => ComboDisplayModel | ComboEncode;
|
||||
node?: ((data: NodeModel) => NodeDisplayModel) | NodeEncode;
|
||||
edge?: ((data: EdgeModel) => EdgeDisplayModel) | EdgeEncode;
|
||||
combo?: ((data: ComboModel) => ComboDisplayModel) | ComboEncode;
|
||||
|
||||
/** item state styles */
|
||||
nodeState?: {
|
||||
[state: string]: (data: NodeModel) => NodeDisplayModel | NodeShapesEncode;
|
||||
[stateName: string]: ((data: NodeModel) => NodeDisplayModel) | NodeShapesEncode;
|
||||
};
|
||||
edgeState?: {
|
||||
[state: string]: (data: EdgeModel) => EdgeDisplayModel | EdgeShapesEncode;
|
||||
[stateName: string]: ((data: EdgeModel) => EdgeDisplayModel) | EdgeShapesEncode;
|
||||
};
|
||||
comboState?: {
|
||||
[state: string]: (data: NodeModel) => NodeDisplayModel | NodeShapesEncode;
|
||||
[stateName: string]: ((data: ComboModel) => ComboDisplayModel) | ComboShapesEncode;
|
||||
};
|
||||
|
||||
/** layout */
|
||||
|
9
packages/g6/src/util/array.ts
Normal file
9
packages/g6/src/util/array.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const isArrayOverlap = (arr1, arr2): boolean => {
|
||||
if (!arr1?.length || !arr2.length) return false;
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
for (let j = 0; j < arr2.length; j++) {
|
||||
if (arr1[i] === arr2[j]) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
61
packages/g6/src/util/canvas.ts
Normal file
61
packages/g6/src/util/canvas.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Canvas } from '@antv/g';
|
||||
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
|
||||
import { Renderer as SVGRenderer } from '@antv/g-svg';
|
||||
// import { Renderer as WebGLRenderer } from '@antv/g-webgl';
|
||||
import { isString } from '@antv/util';
|
||||
|
||||
/**
|
||||
* Create a canvas
|
||||
* @param { 'canvas' | 'svg' | 'webgl' } rendererType
|
||||
* @param {string | HTMLElement} container
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
* @param {number} pixelRatio optional
|
||||
* @param {boolean} customCanvasTag whether create a <canvas /> for multiple canvas under the container
|
||||
* @returns
|
||||
*/
|
||||
export const createCanvas = (
|
||||
rendererType: 'canvas' | 'svg' | 'webgl',
|
||||
container: string | HTMLElement,
|
||||
width: number,
|
||||
height: number,
|
||||
pixelRatio?: number,
|
||||
customCanvasTag: boolean = true
|
||||
) => {
|
||||
let Renderer;
|
||||
switch (rendererType.toLowerCase()) {
|
||||
case 'svg':
|
||||
Renderer = SVGRenderer;
|
||||
break;
|
||||
case 'webgl':
|
||||
// Renderer = WebGLRenderer;
|
||||
// TODO
|
||||
break;
|
||||
default:
|
||||
Renderer = CanvasRenderer;
|
||||
break;
|
||||
}
|
||||
if (typeof document !== 'undefined' && customCanvasTag) {
|
||||
const canvasTag = document.createElement('canvas');
|
||||
const dpr = pixelRatio || window.devicePixelRatio;
|
||||
canvasTag.width = dpr * width;
|
||||
canvasTag.height = dpr * height;
|
||||
canvasTag.style.width = `${width}px`;
|
||||
canvasTag.style.height = `${height}px`;
|
||||
canvasTag.style.position = 'fixed';
|
||||
const containerDOM = isString(container) ? document.getElementById('container') : container;
|
||||
containerDOM.appendChild(canvasTag);
|
||||
return new Canvas({
|
||||
canvas: canvasTag,
|
||||
devicePixelRatio: pixelRatio,
|
||||
renderer: new Renderer(),
|
||||
});
|
||||
}
|
||||
return new Canvas({
|
||||
container,
|
||||
width,
|
||||
height,
|
||||
devicePixelRatio: pixelRatio,
|
||||
renderer: new Renderer()
|
||||
});
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { BehaviorRegistry } from "../types/behavior";
|
||||
import { BehaviorRegistry } from '../types/behavior';
|
||||
import Graph from '../runtime/graph';
|
||||
import registery from '../stdlib';
|
||||
|
||||
@ -13,13 +13,19 @@ import registery from '../stdlib';
|
||||
export const extend = <B1 extends BehaviorRegistry, B2 extends BehaviorRegistry>(
|
||||
GraphClass: typeof Graph<B2>,
|
||||
extendLibrary: {
|
||||
behaviors?: B1,
|
||||
}
|
||||
behaviors?: B1;
|
||||
nodes?: any; // TODO
|
||||
edges?: any; // TODO
|
||||
},
|
||||
): typeof Graph<B1 & B2> => {
|
||||
// merged the extendLibrary to useLib for global usage
|
||||
Object.keys(extendLibrary).forEach(cat => {
|
||||
Object.keys(extendLibrary).forEach((cat) => {
|
||||
registery.useLib[cat] = Object.assign({}, registery.useLib[cat], extendLibrary[cat] || {});
|
||||
Object.keys(registery.useLib[cat]).forEach((type) => {
|
||||
const extension = registery.useLib[cat][type];
|
||||
extension.type = type;
|
||||
});
|
||||
});
|
||||
// @ts-expect-error
|
||||
return GraphClass;
|
||||
}
|
||||
};
|
||||
|
@ -17,6 +17,5 @@ export const getExtension = (config: string | Function | object, lib, cat: StdLi
|
||||
}
|
||||
const type = typeof config === 'string' ? config : (config as any).type;
|
||||
const ext = lib[catKey]?.[type];
|
||||
if (isFunction(ext)) ext;
|
||||
return new ext(config);
|
||||
return ext;
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import { extend } from './extend';
|
||||
const Util = { extend };
|
||||
import { isEncode } from './type';
|
||||
const Util = { extend, isEncode };
|
||||
export default Util;
|
||||
|
173
packages/g6/src/util/shape.ts
Normal file
173
packages/g6/src/util/shape.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import {
|
||||
Circle,
|
||||
DisplayObject,
|
||||
Ellipse,
|
||||
Group,
|
||||
IElement,
|
||||
Line,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Rect,
|
||||
Text,
|
||||
Image,
|
||||
} from '@antv/g';
|
||||
import { clone, isArray, isNumber } from '@antv/util';
|
||||
import { EdgeShapeMap } from '../types/edge';
|
||||
import { NodeShapeMap } from '../types/node';
|
||||
|
||||
const shapeTagMap = {
|
||||
circle: Circle,
|
||||
rect: Rect,
|
||||
ellipse: Ellipse,
|
||||
polygon: Polygon,
|
||||
line: Line,
|
||||
polyline: Polyline,
|
||||
text: Text,
|
||||
image: Image,
|
||||
};
|
||||
|
||||
const createShape = (type: string, style: { [shapeAttr: string]: unknown }, id: string) => {
|
||||
const ShapeClass = shapeTagMap[type];
|
||||
return new ShapeClass({ style, id, autoUpdate: true });
|
||||
};
|
||||
|
||||
export const upsertShape = (
|
||||
type: string,
|
||||
id: string,
|
||||
style: { [shapeAttr: string]: unknown },
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
): DisplayObject => {
|
||||
let shape = shapeMap[id];
|
||||
if (!shape) {
|
||||
shape = createShape(type, style, id);
|
||||
} else if (shape.nodeName !== type) {
|
||||
shape.remove();
|
||||
shape = createShape(type, style, id);
|
||||
} else {
|
||||
Object.keys(style).forEach((key) => {
|
||||
shape.style[key] = style[key];
|
||||
});
|
||||
}
|
||||
shapeMap[id] = shape;
|
||||
return shape;
|
||||
};
|
||||
|
||||
export const getGroupSucceedMap = (group: IElement, map?: { [id: string]: IElement }) => {
|
||||
let useMap = map || {};
|
||||
group.children.forEach((child) => {
|
||||
if (child.tagName === 'group') getGroupSucceedMap(child, useMap);
|
||||
useMap[child.id] = child;
|
||||
});
|
||||
return useMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update shapes in the intersaction of prevShapeMap and newShapeMap;
|
||||
* Remove shapes in the prevShapeMap - newShapeMap (if removeDiff is true);
|
||||
* Add shapes in the newShapeMap - prevShapeMap;
|
||||
* @param prevShapeMap previous shape map
|
||||
* @param newShapeMap new shape map
|
||||
* @param group container group
|
||||
* @returns merged shape map
|
||||
*/
|
||||
export const updateShapes = (
|
||||
prevShapeMap: { [id: string]: DisplayObject },
|
||||
newShapeMap: { [id: string]: DisplayObject },
|
||||
group: Group,
|
||||
removeDiff: boolean = true,
|
||||
shouldUpdate: (id: string) => boolean = () => true,
|
||||
): NodeShapeMap | EdgeShapeMap => {
|
||||
const tolalMap = {
|
||||
...prevShapeMap,
|
||||
...newShapeMap,
|
||||
};
|
||||
const finalShapeMap = {
|
||||
...prevShapeMap,
|
||||
};
|
||||
Object.keys(tolalMap).forEach((id) => {
|
||||
const prevShape = prevShapeMap[id];
|
||||
const newShape = newShapeMap[id];
|
||||
if (newShape && !shouldUpdate(id)) return;
|
||||
if (prevShape && newShape) {
|
||||
// update intersaction
|
||||
finalShapeMap[id] = newShape;
|
||||
if (prevShape !== newShape) {
|
||||
prevShape.remove();
|
||||
}
|
||||
group.appendChild(newShape);
|
||||
} else if (!prevShape && newShape) {
|
||||
// add newShapeMap - prevShapeMap
|
||||
finalShapeMap[id] = newShape;
|
||||
group.appendChild(newShape);
|
||||
} else if (prevShape && !newShape && removeDiff) {
|
||||
// remove prevShapeMap - newShapeMap
|
||||
delete finalShapeMap[id];
|
||||
prevShape.remove();
|
||||
}
|
||||
});
|
||||
return finalShapeMap as NodeShapeMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format the number or array padding to an array with length 4, [padding-top, padding-right, padding-bottom, padding-left].
|
||||
* @param value value to be formatted
|
||||
* @param defaultArr default value
|
||||
* @returns [padding-top, padding-right, padding-bottom, padding-left]
|
||||
*/
|
||||
export const formatPadding = (value, defaultArr = [4, 4, 4, 4]) => {
|
||||
if (isArray(value)) {
|
||||
switch (value.length) {
|
||||
case 0:
|
||||
return defaultArr;
|
||||
case 1:
|
||||
return [value[0], value[0], value[0], value[0]];
|
||||
case 2:
|
||||
return value.concat(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
if (isNumber(value)) return [value, value, value, value];
|
||||
return defaultArr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge two shape map including undefined value in incoming map.
|
||||
* @param styleMap1 shapes' styles map as current map
|
||||
* @param styleMap2 shapes' styles map as incoming map
|
||||
* @returns
|
||||
*/
|
||||
export const mergeStyles = (styleMap1, styleMap2) => {
|
||||
const mergedStyle = clone(styleMap1);
|
||||
Object.keys(styleMap2).forEach(shapeId => {
|
||||
const style = styleMap2[shapeId];
|
||||
mergedStyle[shapeId] = mergedStyle[shapeId] || {};
|
||||
Object.keys(style).forEach(styleName => {
|
||||
const value = style[styleName];
|
||||
mergedStyle[shapeId][styleName] = value;
|
||||
});
|
||||
});
|
||||
return mergedStyle;
|
||||
}
|
||||
|
||||
export const DEFAULT_LABEL_BG_PADDING = [4, 4, 4, 4];
|
||||
/** Default shape style to avoid shape value missing */
|
||||
export const DEFAULT_SHAPE_STYLE = {
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
shadowColor: undefined,
|
||||
shadowBlur: undefined,
|
||||
lineDash: undefined,
|
||||
};
|
||||
/** Default text style to avoid shape value missing */
|
||||
export const DEFAULT_TEXT_STYLE = {
|
||||
...DEFAULT_SHAPE_STYLE,
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
fontWeight: 'normal',
|
||||
fontVariant: 'normal',
|
||||
fontStyle: 'normal',
|
||||
textBaseline: 'middle',
|
||||
textAlign: 'center',
|
||||
lineWidth: 0,
|
||||
};
|
3
packages/g6/src/util/type.ts
Normal file
3
packages/g6/src/util/type.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Encode } from "../types/item"
|
||||
|
||||
export const isEncode = (value): value is Encode<any> => Boolean((value as Encode<any>).fields && (value as Encode<any>).formatter)
|
@ -8,6 +8,7 @@ document.querySelector('body').appendChild(container);
|
||||
describe('behavior', () => {
|
||||
it('behavior in spec, add / remove / update a behavior in defualt mode', () => {
|
||||
const graph = new G6.Graph({
|
||||
container,
|
||||
type: 'graph',
|
||||
data: { nodes: [], edges: [] },
|
||||
modes: {
|
||||
@ -22,8 +23,11 @@ describe('behavior', () => {
|
||||
});
|
||||
let graphSpec = graph.getSpecification();
|
||||
expect(graphSpec.modes.default[0]).toBe('drag-canvas');
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[1].key).toBe('dragcanvaskey2');
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[1].type).toBe('drag-canvas');
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[1].assistKey).toBe('shift');
|
||||
|
||||
graph.addBehaviors([{
|
||||
@ -34,8 +38,11 @@ describe('behavior', () => {
|
||||
// graph.getSpecification() returns a copy, need to be called again to fetch the lastest
|
||||
graphSpec = graph.getSpecification();
|
||||
expect(graphSpec.modes.default.length).toBe(3);
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[2].key).toBe('dragcanvaskey3');
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[2].type).toBe('drag-canvas');
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[2].assistKey).toBe('ctrl');
|
||||
|
||||
graph.removeBehaviors(['dragcanvaskey3', 'dragcanvaskey2'], 'default');
|
||||
@ -59,6 +66,7 @@ describe('behavior', () => {
|
||||
}], 'default');
|
||||
graphSpec = graph.getSpecification();
|
||||
expect(graphSpec.modes.default.length).toBe(2);
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[1].assistKey).toBe('ctrl');
|
||||
graph.updateBehavior({
|
||||
key: 'newbehavior',
|
||||
@ -67,6 +75,7 @@ describe('behavior', () => {
|
||||
}, 'default');
|
||||
graphSpec = graph.getSpecification();
|
||||
expect(graphSpec.modes.default.length).toBe(2);
|
||||
// @ts-ignore
|
||||
expect(graphSpec.modes.default[1].assistKey).toBe('shift');
|
||||
});
|
||||
it('register behavior and extend G6', () => {
|
||||
@ -86,6 +95,7 @@ describe('behavior', () => {
|
||||
}
|
||||
});
|
||||
const graph = new CustomGraph({
|
||||
container,
|
||||
type: 'graph',
|
||||
data: { nodes: [], edges: [] },
|
||||
modes: {
|
||||
|
322
packages/g6/tests/unit/data-spec.ts
Normal file
322
packages/g6/tests/unit/data-spec.ts
Normal file
@ -0,0 +1,322 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import G6, { GraphData, IGraph } from '../../src/index';
|
||||
const container = document.createElement('div');
|
||||
document.querySelector('body').appendChild(container);
|
||||
|
||||
const data: GraphData = {
|
||||
nodes: [
|
||||
{ id: 'node1', data: { x: 100, y: 200 } },
|
||||
{ id: 'node2', data: { x: 200, y: 250 } },
|
||||
{ id: 'node3', data: { x: 300, y: 200 } },
|
||||
{ id: 'node4', data: { x: 300, y: 250 } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge1', source: 'node1', target: 'node2', data: {} },
|
||||
{ id: 'edge2', source: 'node1', target: 'node3', data: {} },
|
||||
],
|
||||
};
|
||||
|
||||
describe('data', () => {
|
||||
let graph: IGraph<any>;
|
||||
it('new graph with data', (done) => {
|
||||
graph = new G6.Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data, // with data, graph will be rendered in constructor
|
||||
});
|
||||
graph.on('afterrender', () => {
|
||||
expect(graph.canvas.document.childNodes[0].childNodes.length).toBe(2);
|
||||
done();
|
||||
});
|
||||
expect(graph.dataController.graphCore.getAllNodes().length).toBe(4);
|
||||
expect(graph.dataController.graphCore.getAllEdges().length).toBe(2);
|
||||
expect(graph.backgroundCanvas).not.toBe(undefined);
|
||||
expect(graph.transientCanvas).not.toBe(undefined);
|
||||
expect(graph.canvas).not.toBe(undefined);
|
||||
});
|
||||
it('updateData', () => {
|
||||
// === update node ===
|
||||
const node1UpdateUserData = {
|
||||
id: 'node1',
|
||||
data: {
|
||||
x: 350,
|
||||
},
|
||||
};
|
||||
graph.updateData('node', node1UpdateUserData);
|
||||
const newNode1UserData = {
|
||||
...data.nodes[0],
|
||||
...node1UpdateUserData,
|
||||
data: {
|
||||
...data.nodes[0].data,
|
||||
...node1UpdateUserData.data,
|
||||
},
|
||||
};
|
||||
const node1InnerData = graph.getNodeData('node1');
|
||||
expect(JSON.stringify(newNode1UserData)).toBe(JSON.stringify(node1InnerData));
|
||||
|
||||
// === update edge data ===
|
||||
const edge2UpdateUserData = {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
keyShape: {
|
||||
lineWidth: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
graph.updateData('edge', edge2UpdateUserData);
|
||||
const newEdge2UserData = {
|
||||
...data.edges[1],
|
||||
...edge2UpdateUserData,
|
||||
data: {
|
||||
...data.edges[1].data,
|
||||
...edge2UpdateUserData.data,
|
||||
},
|
||||
};
|
||||
const edge2InnerData = graph.getEdgeData('edge2');
|
||||
expect(JSON.stringify(newEdge2UserData)).toBe(JSON.stringify(edge2InnerData));
|
||||
expect(graph.itemController.itemMap['edge2'].shapeMap['keyShape'].attributes.lineWidth).toBe(
|
||||
edge2UpdateUserData.data.keyShape.lineWidth,
|
||||
);
|
||||
|
||||
// === update edge source ===
|
||||
const edge1UpdateUserData = {
|
||||
id: 'edge1',
|
||||
source: 'node3',
|
||||
};
|
||||
graph.updateData('edge', edge1UpdateUserData);
|
||||
const newSourceData = graph.getNodeData('node3');
|
||||
expect(graph.itemController.itemMap['edge1'].shapeMap['keyShape'].attributes.x1).toBe(
|
||||
newSourceData.data.x,
|
||||
);
|
||||
expect(graph.itemController.itemMap['edge1'].shapeMap['keyShape'].attributes.y1).toBe(
|
||||
newSourceData.data.y,
|
||||
);
|
||||
expect(graph.itemController.itemMap['edge1'].sourceItem).toBe(
|
||||
graph.itemController.itemMap['node3'],
|
||||
);
|
||||
|
||||
// === update edge source, target, and data in the same time ===
|
||||
const edge1UpdateUserData2 = {
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node4',
|
||||
data: {
|
||||
keyShape: {
|
||||
stroke: '#f00',
|
||||
},
|
||||
},
|
||||
};
|
||||
graph.updateData('edge', edge1UpdateUserData2);
|
||||
const sourceData = graph.getNodeData('node1');
|
||||
const targetData = graph.getNodeData('node4');
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
expect(edgeItem.shapeMap['keyShape'].attributes.x1).toBe(sourceData.data.x);
|
||||
expect(edgeItem.shapeMap['keyShape'].attributes.y1).toBe(sourceData.data.y);
|
||||
expect(edgeItem.shapeMap['keyShape'].attributes.x2).toBe(targetData.data.x);
|
||||
expect(edgeItem.shapeMap['keyShape'].attributes.y2).toBe(targetData.data.y);
|
||||
expect(edgeItem.shapeMap['keyShape'].attributes.stroke).toBe(
|
||||
edge1UpdateUserData2.data.keyShape.stroke,
|
||||
);
|
||||
|
||||
// === update nodes ===
|
||||
graph.updateData('node', [
|
||||
{
|
||||
id: 'node1',
|
||||
data: {
|
||||
keyShape: {
|
||||
fill: '#0f0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: {
|
||||
keyShape: {
|
||||
lineWidth: 2,
|
||||
stroke: '#0f0',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const node1Item = graph.itemController.itemMap['node1'];
|
||||
const node2Item = graph.itemController.itemMap['node2'];
|
||||
expect(node1Item.shapeMap['keyShape'].attributes.fill).toBe('#0f0');
|
||||
expect(node2Item.shapeMap['keyShape'].attributes.lineWidth).toBe(2);
|
||||
expect(node2Item.shapeMap['keyShape'].attributes.stroke).toBe('#0f0');
|
||||
|
||||
// === update edges ===
|
||||
graph.updateData('edge', [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node2',
|
||||
data: {
|
||||
keyShape: {
|
||||
stroke: '#0f0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
data: {
|
||||
keyShape: {
|
||||
lineDash: [5, 5],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const edge1Item = graph.itemController.itemMap['edge1'];
|
||||
const edge2Item = graph.itemController.itemMap['edge2'];
|
||||
expect(edge1Item.shapeMap['keyShape'].attributes.stroke).toBe('#0f0');
|
||||
expect(edge1Item.sourceItem).toBe(graph.itemController.itemMap['node2']);
|
||||
expect(JSON.stringify(edge2Item.shapeMap['keyShape'].attributes.lineDash)).toBe('[5,5]');
|
||||
});
|
||||
it('addData', () => {
|
||||
graph.addData('node', {
|
||||
id: 'node5',
|
||||
data: {
|
||||
x: 300,
|
||||
y: 100,
|
||||
},
|
||||
});
|
||||
graph.addData('node', {
|
||||
id: 'node6',
|
||||
data: {
|
||||
x: 300,
|
||||
y: 200,
|
||||
},
|
||||
});
|
||||
graph.addData('edge', {
|
||||
id: 'edge3',
|
||||
source: 'node5',
|
||||
target: 'node6',
|
||||
data: {},
|
||||
});
|
||||
expect(graph.dataController.graphCore.getAllNodes().length).toBe(6);
|
||||
expect(graph.dataController.graphCore.getAllEdges().length).toBe(3);
|
||||
|
||||
// === add nodes ===
|
||||
graph.addData('node', [
|
||||
{
|
||||
id: 'node7',
|
||||
data: {
|
||||
x: 100,
|
||||
y: 400,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node8',
|
||||
data: {
|
||||
x: 200,
|
||||
y: 400,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node9',
|
||||
data: {
|
||||
x: 300,
|
||||
y: 400,
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(graph.dataController.graphCore.getAllNodes().length).toBe(9);
|
||||
graph.addData('edge', [
|
||||
{
|
||||
id: 'edge4',
|
||||
source: 'node7',
|
||||
target: 'node8',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: 'edge5',
|
||||
source: 'node8',
|
||||
target: 'node9',
|
||||
data: {},
|
||||
},
|
||||
]);
|
||||
expect(graph.dataController.graphCore.getAllEdges().length).toBe(5);
|
||||
});
|
||||
it('removeData', () => {
|
||||
// === remove node ===
|
||||
// remoev a node, related edges will be removed in the same time
|
||||
graph.removeData('node', 'node5');
|
||||
expect(graph.dataController.graphCore.getAllNodes().length).toBe(8);
|
||||
expect(graph.dataController.graphCore.getAllEdges().length).toBe(4);
|
||||
|
||||
// === remove nodes ===
|
||||
graph.removeData('node', ['node7', 'node8']);
|
||||
expect(graph.dataController.graphCore.getAllNodes().length).toBe(6);
|
||||
expect(graph.dataController.graphCore.getAllEdges().length).toBe(2);
|
||||
|
||||
// === remove edge ===
|
||||
graph.removeData('edge', 'edge1');
|
||||
expect(graph.dataController.graphCore.getAllEdges().length).toBe(1);
|
||||
});
|
||||
it('getNodeData', () => {
|
||||
const foundNode = graph.getNodeData('node1');
|
||||
expect(foundNode).toBe(graph.dataController.graphCore.getNode('node1'));
|
||||
|
||||
const inexisNode = graph.getNodeData('inexistnode');
|
||||
expect(inexisNode).toBe(undefined);
|
||||
});
|
||||
it('getEdgeData', () => {
|
||||
const foundEdge = graph.getEdgeData('edge2');
|
||||
expect(foundEdge).toBe(graph.dataController.graphCore.getEdge('edge2'));
|
||||
|
||||
const removedEdge = graph.getEdgeData('edge1');
|
||||
expect(removedEdge).toBe(undefined);
|
||||
|
||||
const inexisEdge = graph.getEdgeData('inexistedge');
|
||||
expect(inexisEdge).toBe(undefined);
|
||||
});
|
||||
it('getAllNodesData getAllEdgesDat', () => {
|
||||
expect(graph.getAllNodesData().length).toBe(6);
|
||||
expect(graph.getAllEdgesData().length).toBe(1);
|
||||
});
|
||||
|
||||
it('changeData with replace', () => {
|
||||
const newData = {
|
||||
nodes: [
|
||||
{ id: 'node1', data: { x: 100, y: 200 } },
|
||||
{ id: 'node2', data: { x: 400, y: 450 } },
|
||||
{ id: 'node11', data: { x: 300, y: 200 } },
|
||||
{ id: 'node12', data: { x: 300, y: 250 } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge1', source: 'node1', target: 'node2', data: {} },
|
||||
{ id: 'edge11', source: 'node1', target: 'node11', data: {} },
|
||||
],
|
||||
};
|
||||
graph.changeData(newData, 'replace');
|
||||
expect(graph.getNodeData('node2').data.x).toBe(400);
|
||||
expect(graph.getNodeData('node2').data.y).toBe(450);
|
||||
expect(graph.getNodeData('node3')).toBe(undefined);
|
||||
expect(graph.getNodeData('node11')).not.toBe(undefined);
|
||||
expect(graph.getNodeData('node12')).not.toBe(undefined);
|
||||
});
|
||||
|
||||
it('changeData with mergeReplace', () => {
|
||||
const newData = {
|
||||
nodes: [{ id: 'node13', data: { x: 50, y: 50 } }],
|
||||
edges: [{ id: 'edge1', source: 'node13', target: 'node13', data: {} }],
|
||||
};
|
||||
graph.changeData(newData, 'mergeReplace');
|
||||
const allNodes = graph.getAllNodesData();
|
||||
expect(allNodes.length).toBe(1);
|
||||
expect(allNodes[0].id).toBe('node13');
|
||||
|
||||
const allEdges = graph.getAllEdgesData();
|
||||
expect(allEdges.length).toBe(1);
|
||||
expect(allEdges[0].id).toBe('edge1');
|
||||
expect(allEdges[0].source).toBe('node13');
|
||||
expect(allEdges[0].target).toBe('node13');
|
||||
|
||||
graph.destroy();
|
||||
expect(graph.destroyed).toBe(true);
|
||||
// expect(graph.canvas.destroyed).toBe(true);
|
||||
// expect(graph.backgroundCanvas.destroyed).toBe(true);
|
||||
// expect(graph.transientCanvas.destroyed).toBe(true);
|
||||
});
|
||||
});
|
911
packages/g6/tests/unit/item-spec.ts
Normal file
911
packages/g6/tests/unit/item-spec.ts
Normal file
@ -0,0 +1,911 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { clone } from '@antv/util';
|
||||
import G6, { EdgeDisplayModel, Graph, GraphData, IGraph, NodeDisplayModel } from '../../src/index';
|
||||
import { LineEdge } from '../../src/stdlib/item/edge';
|
||||
import { CircleNode } from '../../src/stdlib/item/node';
|
||||
import { BaseNode } from '../../src/stdlib/item/node/base';
|
||||
import { NodeModelData, NodeShapeMap } from '../../src/types/node';
|
||||
import { extend } from '../../src/util/extend';
|
||||
import { upsertShape } from '../../src/util/shape';
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.querySelector('body').appendChild(container);
|
||||
|
||||
describe('node item', () => {
|
||||
let graph: IGraph<any>;
|
||||
it('new graph with one node', (done) => {
|
||||
graph = new G6.Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200 },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
graph.on('afterrender', () => {
|
||||
const nodeItem = graph.itemController.itemMap['node1'];
|
||||
expect(nodeItem).not.toBe(undefined);
|
||||
expect(nodeItem.shapeMap.labelShape).toBe(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('update node label', () => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
labelShape: {
|
||||
text: 'node-label',
|
||||
position: 'left',
|
||||
background: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
const nodeItem = graph.itemController.itemMap['node1'];
|
||||
expect(nodeItem.shapeMap.labelShape).not.toBe(undefined);
|
||||
expect(nodeItem.shapeMap.labelShape.attributes.text).toBe('node-label');
|
||||
expect(nodeItem.shapeMap.labelShape.attributes.fill).toBe('#000');
|
||||
expect(nodeItem.shapeMap.labelBgShape).not.toBe(undefined);
|
||||
const labelBounds = nodeItem.shapeMap.labelShape.getGeometryBounds();
|
||||
expect(nodeItem.shapeMap.labelBgShape.attributes.x).toBe(
|
||||
labelBounds.min[0] + nodeItem.shapeMap.labelShape.attributes.x - 4,
|
||||
);
|
||||
expect(nodeItem.shapeMap.labelBgShape.attributes.y).toBe(
|
||||
labelBounds.min[1] + nodeItem.shapeMap.labelShape.attributes.y - 4,
|
||||
);
|
||||
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
labelShape: {
|
||||
fill: '#00f',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(nodeItem.shapeMap.labelShape.attributes.fill).toBe('#00f');
|
||||
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
labelShape: undefined,
|
||||
},
|
||||
});
|
||||
expect(nodeItem.shapeMap.labelShape).toBe(undefined);
|
||||
expect(nodeItem.shapeMap.labelBgShape).toBe(undefined);
|
||||
});
|
||||
it('update node icon', () => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
iconShape: {
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
},
|
||||
},
|
||||
});
|
||||
const nodeItem = graph.itemController.itemMap['node1'];
|
||||
expect(nodeItem.shapeMap.iconShape).not.toBe(undefined);
|
||||
expect(nodeItem.shapeMap.iconShape.attributes.width).toBe(15);
|
||||
expect(nodeItem.shapeMap.iconShape.nodeName).toBe('image');
|
||||
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
iconShape: {
|
||||
text: 'A',
|
||||
fill: '#fff',
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(nodeItem.shapeMap.iconShape).not.toBe(undefined);
|
||||
expect(nodeItem.shapeMap.iconShape.attributes.text).toBe('A');
|
||||
expect(nodeItem.shapeMap.iconShape.attributes.fontSize).toBe(12);
|
||||
expect(nodeItem.shapeMap.iconShape.nodeName).toBe('text');
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge item', () => {
|
||||
let graph: IGraph<any>;
|
||||
it('new graph with two nodes and one edge', (done) => {
|
||||
graph = new G6.Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 100, keyShape: { opacity: 0.1 } },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 300, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
graph.on('afterrender', () => {
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
expect(edgeItem).not.toBe(undefined);
|
||||
expect(edgeItem.shapeMap.labelShape).toBe(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('update edge label', () => {
|
||||
const padding = [4, 8, 4, 8];
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
text: 'edge-label',
|
||||
position: 'middle',
|
||||
background: {
|
||||
radius: 10,
|
||||
padding,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
expect(edgeItem.shapeMap.labelShape).not.toBe(undefined);
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.text).toBe('edge-label');
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.fill).toBe('#000');
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.transform).toBe('rotate(45)');
|
||||
expect(edgeItem.shapeMap.labelBgShape.attributes.transform).toBe('rotate(45)');
|
||||
let labelBounds = edgeItem.shapeMap.labelShape.getGeometryBounds();
|
||||
expect(edgeItem.shapeMap.labelBgShape.attributes.width).toBe(
|
||||
labelBounds.max[0] - labelBounds.min[0] + padding[1] + padding[3],
|
||||
);
|
||||
expect(edgeItem.shapeMap.labelBgShape.attributes.height).toBe(
|
||||
labelBounds.max[1] - labelBounds.min[1] + padding[0] + padding[2],
|
||||
);
|
||||
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
fill: '#00f',
|
||||
position: 'start',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.fill).toBe('#00f');
|
||||
expect(
|
||||
edgeItem.shapeMap.labelShape.attributes.x - edgeItem.shapeMap.labelBgShape.attributes.x,
|
||||
).toBe(padding[3]);
|
||||
labelBounds = edgeItem.shapeMap.labelShape.getGeometryBounds();
|
||||
const labelWidth = labelBounds.max[0] - labelBounds.min[0];
|
||||
const labelHeight = labelBounds.max[1] - labelBounds.min[1];
|
||||
const labelBgBounds = edgeItem.shapeMap.labelBgShape.getGeometryBounds();
|
||||
const labelBgWidth = labelBgBounds.max[0] - labelBgBounds.min[0];
|
||||
const labelBgHeight = labelBgBounds.max[1] - labelBgBounds.min[1];
|
||||
expect(labelBgWidth - labelWidth).toBe(padding[1] + padding[3]);
|
||||
expect(labelBgHeight - labelHeight).toBe(padding[0] + padding[2]);
|
||||
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: undefined,
|
||||
},
|
||||
});
|
||||
expect(edgeItem.shapeMap.labelShape).toBe(undefined);
|
||||
expect(edgeItem.shapeMap.labelBgShape).toBe(undefined);
|
||||
});
|
||||
it('update edge icon', () => {
|
||||
// add image icon to follow the label at path's center
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
text: 'abcddd',
|
||||
fill: '#f00',
|
||||
position: 'center',
|
||||
},
|
||||
iconShape: {
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
// text: 'A',
|
||||
fill: '#fff',
|
||||
},
|
||||
},
|
||||
});
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
let labelShape = edgeItem.shapeMap['labelShape'];
|
||||
let iconShape = edgeItem.shapeMap['iconShape'];
|
||||
expect(iconShape.attributes.x + iconShape.attributes.width + 4).toBe(
|
||||
labelShape.getGeometryBounds().min[0] + labelShape.attributes.x,
|
||||
);
|
||||
expect(iconShape.attributes.transform).toBe(labelShape.attributes.transform);
|
||||
expect(iconShape.attributes.y + iconShape.attributes.height / 2 - 2).toBe(
|
||||
labelShape.getGeometryBounds().center[1] + labelShape.attributes.y,
|
||||
);
|
||||
|
||||
// update icon to be a text
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
iconShape: {
|
||||
text: 'A',
|
||||
fill: '#fff',
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
});
|
||||
labelShape = edgeItem.shapeMap['labelShape'];
|
||||
iconShape = edgeItem.shapeMap['iconShape'];
|
||||
expect(iconShape.attributes.x + iconShape.attributes.width + 4).toBe(
|
||||
labelShape.getGeometryBounds().min[0] + labelShape.attributes.x,
|
||||
);
|
||||
expect(iconShape.attributes.transform).toBe(labelShape.attributes.transform);
|
||||
expect(iconShape.attributes.y + iconShape.attributes.height / 2 - 2).toBe(
|
||||
labelShape.getGeometryBounds().center[1] + labelShape.attributes.y,
|
||||
);
|
||||
|
||||
// move label to the start, and the icon follows
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
position: 'start',
|
||||
},
|
||||
},
|
||||
});
|
||||
labelShape = edgeItem.shapeMap['labelShape'];
|
||||
iconShape = edgeItem.shapeMap['iconShape'];
|
||||
expect(iconShape.attributes.x + iconShape.attributes.width + 4).toBe(
|
||||
labelShape.getGeometryBounds().min[0] + labelShape.attributes.x,
|
||||
);
|
||||
expect(iconShape.attributes.transform).toBe(labelShape.attributes.transform);
|
||||
expect(iconShape.attributes.y + iconShape.attributes.height / 2 - 2).toBe(
|
||||
labelShape.getGeometryBounds().center[1] + labelShape.attributes.y,
|
||||
);
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('node mapper', () => {
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200, buStatus: true, buType: 1 },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300, buStatus: false, buType: 0 },
|
||||
},
|
||||
],
|
||||
};
|
||||
const graphConfig = {
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
};
|
||||
it('function mapper', (done) => {
|
||||
const graph = new G6.Graph({
|
||||
...graphConfig,
|
||||
node: (innerModel) => {
|
||||
const { x, y, buStatus } = innerModel.data;
|
||||
return {
|
||||
...innerModel,
|
||||
data: {
|
||||
x,
|
||||
y,
|
||||
keyShape: {
|
||||
fill: buStatus ? '#0f0' : '#f00',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any);
|
||||
graph.read(clone(data));
|
||||
graph.on('afterrender', () => {
|
||||
const node1 = graph.itemController.itemMap['node1'];
|
||||
expect(node1.shapeMap.keyShape.attributes.fill).toBe('#0f0');
|
||||
let node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.keyShape.attributes.fill).toBe('#f00');
|
||||
|
||||
// update user data
|
||||
graph.updateData('node', {
|
||||
id: 'node2',
|
||||
data: {
|
||||
buStatus: true,
|
||||
},
|
||||
});
|
||||
node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.keyShape.attributes.fill).toBe('#0f0');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('value and encode mapper', (done) => {
|
||||
const graph = new G6.Graph({
|
||||
...graphConfig,
|
||||
node: {
|
||||
keyShape: {
|
||||
fill: {
|
||||
fields: ['buStatus'],
|
||||
formatter: (innerModel) => (innerModel.data.buStatus ? '#0f0' : '#f00'),
|
||||
},
|
||||
lineWidth: 5,
|
||||
stroke: {
|
||||
fields: ['buStatus', 'buType'],
|
||||
formatter: (innerModel) =>
|
||||
innerModel.data.buStatus || innerModel.data.buType ? '#fff' : '#000',
|
||||
},
|
||||
},
|
||||
labelShape: {},
|
||||
},
|
||||
} as any);
|
||||
graph.read(clone(data));
|
||||
graph.on('afterrender', () => {
|
||||
const node1 = graph.itemController.itemMap['node1'];
|
||||
expect(node1.shapeMap.keyShape.attributes.fill).toBe('#0f0');
|
||||
expect(node1.shapeMap.keyShape.attributes.lineWidth).toBe(5);
|
||||
expect(node1.shapeMap.keyShape.attributes.stroke).toBe('#fff');
|
||||
let node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.keyShape.attributes.fill).toBe('#f00');
|
||||
expect(node2.shapeMap.keyShape.attributes.lineWidth).toBe(5);
|
||||
expect(node2.shapeMap.keyShape.attributes.stroke).toBe('#000');
|
||||
|
||||
// update user data
|
||||
graph.updateData('node', {
|
||||
id: 'node2',
|
||||
data: {
|
||||
buStatus: true,
|
||||
},
|
||||
});
|
||||
node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.keyShape.attributes.fill).toBe('#0f0');
|
||||
expect(node2.shapeMap.keyShape.attributes.stroke).toBe('#fff');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge mapper', () => {
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: { buStatus: true, buType: 1, buName: 'edge-1' },
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: { buStatus: false, buType: 0, buName: 'edge-2' },
|
||||
},
|
||||
],
|
||||
};
|
||||
const graphConfig = {
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
};
|
||||
it('function mapper', (done) => {
|
||||
const graph = new G6.Graph({
|
||||
...graphConfig,
|
||||
edge: (innerModel) => {
|
||||
const { x, y, buStatus } = innerModel.data;
|
||||
return {
|
||||
...innerModel,
|
||||
data: {
|
||||
x,
|
||||
y,
|
||||
keyShape: {
|
||||
stroke: buStatus ? '#0f0' : '#f00',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any);
|
||||
graph.read(clone(data));
|
||||
graph.on('afterrender', () => {
|
||||
const edge1 = graph.itemController.itemMap['edge1'];
|
||||
expect(edge1.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
let edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#f00');
|
||||
|
||||
// update user data
|
||||
graph.updateData('edge', {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
buStatus: true,
|
||||
},
|
||||
});
|
||||
edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('value and encode mapper', (done) => {
|
||||
const graph = new G6.Graph({
|
||||
...graphConfig,
|
||||
edge: {
|
||||
keyShape: {
|
||||
stroke: {
|
||||
fields: ['buStatus'],
|
||||
formatter: (innerModel) => (innerModel.data.buStatus ? '#0f0' : '#f00'),
|
||||
},
|
||||
lineWidth: 5,
|
||||
lineDash: {
|
||||
fields: ['buStatus', 'buType'],
|
||||
formatter: (innerModel) =>
|
||||
innerModel.data.buStatus || innerModel.data.buType ? undefined : [5, 5],
|
||||
},
|
||||
},
|
||||
labelShape: {
|
||||
text: {
|
||||
fields: ['buName', 'buType'],
|
||||
formatter: (innerModel) => `${innerModel.data.buName}-${innerModel.data.buType}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
graph.read(clone(data));
|
||||
graph.on('afterrender', () => {
|
||||
const edge1 = graph.itemController.itemMap['edge1'];
|
||||
expect(edge1.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
expect(edge1.shapeMap.keyShape.attributes.lineWidth).toBe(5);
|
||||
expect(edge1.shapeMap.keyShape.attributes.lineDash).toBe('');
|
||||
expect(edge1.shapeMap.labelShape.attributes.text).toBe('edge-1-1');
|
||||
let edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#f00');
|
||||
expect(edge2.shapeMap.keyShape.attributes.lineWidth).toBe(5);
|
||||
expect(JSON.stringify(edge2.shapeMap.keyShape.attributes.lineDash)).toBe('[5,5]');
|
||||
expect(edge2.shapeMap.labelShape.attributes.text).toBe('edge-2-0');
|
||||
|
||||
// update user data
|
||||
graph.updateData('edge', {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
buStatus: true,
|
||||
buName: 'newedge2name',
|
||||
},
|
||||
});
|
||||
edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
expect(edge2.shapeMap.keyShape.attributes.lineDash).toBe('');
|
||||
expect(edge2.shapeMap.labelShape.attributes.text).toBe('newedge2name-0');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('register node', () => {
|
||||
it('custom node extends circle', (done) => {
|
||||
class CustomNode extends CircleNode {
|
||||
public defaultStyles = {
|
||||
keyShape: {
|
||||
r: 25,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#ff0',
|
||||
lineWidth: 0,
|
||||
stroke: '#0f0',
|
||||
},
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.defaultStyles = Object.assign({}, this.baseDefaultStyles, this.defaultStyles);
|
||||
}
|
||||
public drawLabelShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { oldData: NodeModelData; newData: NodeModelData },
|
||||
) {
|
||||
const extraShape = upsertShape(
|
||||
'circle',
|
||||
'extraShape',
|
||||
{
|
||||
r: 4,
|
||||
fill: '#0f0',
|
||||
x: -20,
|
||||
y: 0,
|
||||
},
|
||||
shapeMap,
|
||||
);
|
||||
const { labelShape: propsLabelStyle } = model.data;
|
||||
const labelStyle = Object.assign({}, this.defaultStyles.labelShape, propsLabelStyle);
|
||||
const labelShape = upsertShape(
|
||||
'text',
|
||||
'labelShape',
|
||||
{
|
||||
...labelStyle,
|
||||
text: model.id,
|
||||
},
|
||||
shapeMap,
|
||||
);
|
||||
return { labelShape, extraShape };
|
||||
}
|
||||
}
|
||||
class CustomEdge extends LineEdge {
|
||||
public afterDraw(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: { [shapeId: string]: DisplayObject<any, any> },
|
||||
shapesChanged?: string[],
|
||||
): { [otherShapeId: string]: DisplayObject } {
|
||||
const { keyShape } = shapeMap;
|
||||
const point = keyShape.getPoint(0.3);
|
||||
return {
|
||||
buShape: upsertShape(
|
||||
'rect',
|
||||
'buShape',
|
||||
{
|
||||
width: 6,
|
||||
height: 6,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
fill: '#0f0',
|
||||
},
|
||||
shapeMap,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
const CustomGraph = extend(G6.Graph, {
|
||||
nodes: {
|
||||
'custom-node': CustomNode,
|
||||
},
|
||||
edges: {
|
||||
'custom-edge': CustomEdge,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: G6.Graph is modified unexpectively
|
||||
const graph = new CustomGraph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200, type: 'custom-node' },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300, type: 'circle-node' },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300, labelShape: undefined },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: { type: 'custom-edge' },
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
// affect the nodes without type field in their data object, which means configurations in the user data has higher priority than that in the mapper
|
||||
type: 'custom-node',
|
||||
// affect the nodes without labelShape field in their data object, which means configurations in the user data has higher priority than that in the mapper
|
||||
labelShape: {}
|
||||
},
|
||||
});
|
||||
graph.on('afterrender', () => {
|
||||
const node1 = graph.itemController.itemMap['node1'];
|
||||
expect(node1.shapeMap.extraShape).not.toBe(undefined);
|
||||
expect(node1.shapeMap.keyShape.style.r).toBe(25);
|
||||
const node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.extraShape).toBe(undefined);
|
||||
const node3 = graph.itemController.itemMap['node3'];
|
||||
// labelShape is assigned with undefined in node3's data, shapes defined in drawLabelShape will be undefined
|
||||
expect(node3.shapeMap.extraShape).toBe(undefined);
|
||||
expect(node3.shapeMap.keyShape.style.r).toBe(25);
|
||||
|
||||
const edge1 = graph.itemController.itemMap['edge1'];
|
||||
expect(edge1.shapeMap.buShape).not.toBe(undefined);
|
||||
const edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.buShape).toBe(undefined);
|
||||
|
||||
|
||||
// update node type
|
||||
graph.updateData('node', {
|
||||
id: 'node2',
|
||||
data: {
|
||||
type: 'custom-node'
|
||||
}
|
||||
});
|
||||
expect(node2.shapeMap.extraShape).not.toBe(undefined);
|
||||
expect(node2.shapeMap.keyShape.style.r).toBe(25);
|
||||
|
||||
// update edge type
|
||||
graph.updateData('edge', {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
type: 'custom-edge'
|
||||
}
|
||||
});
|
||||
expect(edge2.shapeMap.buShape).not.toBe(undefined);
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('update node type with different keyShape shape type and other shapes', (done) => {
|
||||
class CustomNode extends CircleNode {
|
||||
public defaultStyles = {
|
||||
keyShape: {
|
||||
width: 50,
|
||||
height: 25,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#ff0',
|
||||
lineWidth: 0,
|
||||
stroke: '#0f0',
|
||||
opacity: 0.6
|
||||
},
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.defaultStyles = Object.assign({}, this.baseDefaultStyles, this.defaultStyles);
|
||||
}
|
||||
public drawKeyShape(model: NodeDisplayModel, shapeMap: NodeShapeMap, diffData?: { oldData: NodeModelData; newData: NodeModelData; }) {
|
||||
const keyShapeStyle = Object.assign({}, this.defaultStyles.keyShape, model.data.labelShape);
|
||||
const keyShape = upsertShape(
|
||||
'rect',
|
||||
'keyShape',
|
||||
keyShapeStyle,
|
||||
shapeMap
|
||||
);
|
||||
return keyShape
|
||||
}
|
||||
public drawOtherShapes(model: NodeDisplayModel, shapeMap: NodeShapeMap, diffData?: { oldData: NodeModelData; newData: NodeModelData; }) {
|
||||
console.log('drawothershape')
|
||||
const testShape = upsertShape(
|
||||
'line',
|
||||
'testShape',
|
||||
{
|
||||
x1: 0,
|
||||
y1: 10,
|
||||
x2: 50,
|
||||
y2: 10,
|
||||
stroke: '#f00',
|
||||
lineWidth: 2
|
||||
},
|
||||
shapeMap
|
||||
);
|
||||
return { testShape };
|
||||
}
|
||||
}
|
||||
const CustomGraph = extend(G6.Graph, {
|
||||
nodes: {
|
||||
'custom-node': CustomNode,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: G6.Graph is modified unexpectively
|
||||
const graph = new CustomGraph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200, type: 'custom-node' },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300, type: 'circle-node' },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: { },
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
// affect the nodes without type field in their data object, which means configurations in the user data has higher priority than that in the mapper
|
||||
type: 'custom-node',
|
||||
// affect the nodes without labelShape field in their data object, which means configurations in the user data has higher priority than that in the mapper
|
||||
labelShape: {},
|
||||
otherShapes: {}
|
||||
},
|
||||
});
|
||||
graph.on('afterrender', e => {
|
||||
const node1 = graph.itemController.itemMap['node1'];
|
||||
expect(node1.shapeMap.testShape).not.toBe(undefined);
|
||||
expect(node1.shapeMap.keyShape.nodeName).toBe('rect');
|
||||
const node3 = graph.itemController.itemMap['node3'];
|
||||
expect(node3.shapeMap.testShape).not.toBe(undefined);
|
||||
expect(node3.shapeMap.keyShape.nodeName).toBe('rect');
|
||||
|
||||
const node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.testShape).toBe(undefined);
|
||||
expect(node2.shapeMap.keyShape.nodeName).toBe('circle');
|
||||
|
||||
console.log('goingto update')
|
||||
// update circle-node to custom-node
|
||||
graph.updateData('node', {
|
||||
id: 'node2',
|
||||
data: {
|
||||
type: 'custom-node'
|
||||
}
|
||||
});
|
||||
const node2 = graph.itemController.itemMap['node2'];
|
||||
expect(node2.shapeMap.testShape).not.toBe(undefined);
|
||||
expect(node2.shapeMap.keyShape.nodeName).toBe('rect');
|
||||
|
||||
graph.destroy();
|
||||
done()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('state', () => {
|
||||
it('node state', (done) => {
|
||||
const graph = new Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: { },
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
nodeState: {
|
||||
selected: {
|
||||
keyShape: {
|
||||
stroke: '#0f0',
|
||||
lineWidth: 2
|
||||
}
|
||||
},
|
||||
highlight: {
|
||||
keyShape: {
|
||||
stroke: '#00f',
|
||||
r: 30,
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
graph.on('afterrender', () => {
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(0);
|
||||
graph.setItemState('node1', 'selected', true);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(1);
|
||||
expect(graph.findIdByState('node', 'selected')[0]).toBe('node1');
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.stroke).toBe('#0f0');
|
||||
graph.setItemState('node1', 'selected', false);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(0);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.lineWidth).toBe(0);
|
||||
|
||||
// set multiple nodes state
|
||||
graph.setItemState(['node1', 'node2'], 'selected', true);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(2);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.stroke).toBe('#0f0');
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.stroke).toBe('#0f0');
|
||||
graph.setItemState('node1', 'selected', false);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(1);
|
||||
expect(graph.findIdByState('node', 'selected')[0]).toBe('node2');
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.lineWidth).toBe(0);
|
||||
graph.setItemState(['node1', 'node2'], 'selected', false);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(0);
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.lineWidth).toBe(0);
|
||||
|
||||
// // set multiple states
|
||||
graph.setItemState(['node1', 'node2'], ['selected', 'highlight'], true);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(2);
|
||||
expect(graph.findIdByState('node', 'highlight').length).toBe(2);
|
||||
// should be merged styles from selected and highlight
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.stroke).toBe('#00f');
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.r).toBe(30);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.opacity).toBe(0.5);
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.stroke).toBe('#00f');
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.r).toBe(30);
|
||||
expect(graph.itemController.itemMap['node2'].shapeMap.keyShape.style.opacity).toBe(0.5);
|
||||
|
||||
// clear states
|
||||
graph.clearItemState(['node1', 'node2']);
|
||||
expect(graph.findIdByState('node', 'selected').length).toBe(0);
|
||||
expect(graph.findIdByState('node', 'highlight').length).toBe(0);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.r).toBe(15);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.lineWidth).toBe(0);
|
||||
expect(graph.itemController.itemMap['node1'].shapeMap.keyShape.style.opacity).toBe(1);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
// TODO:
|
||||
xit('edge state', () => {});
|
||||
xit('custom node with setState', () => {});
|
||||
xit('custom edge with setState', () => {});
|
||||
});
|
@ -12,18 +12,25 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["jest"],
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"types": [
|
||||
"jest"
|
||||
],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src", "tests"],
|
||||
"include": [
|
||||
"src",
|
||||
"tests"
|
||||
],
|
||||
"typedocOptions": {
|
||||
"mode": "modules",
|
||||
"out": "docs/api-ts",
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeExternals": true,
|
||||
"plugin": "typedoc-plugin-markdown",
|
||||
"theme": "docusaurus2"
|
||||
"theme": "markdown"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user