fix: keyshape states bug & add tree demo

This commit is contained in:
baizn 2020-06-23 15:49:50 +08:00 committed by Moyee
parent c6c8f9cc5e
commit 755008f04b
12 changed files with 889 additions and 8 deletions

View File

@ -0,0 +1,332 @@
import G6 from '@antv/g6';
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
return [
['M', x - r, y - r],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x + 2 - r, y - r],
['L', x + r - 2, y - r],
];
};
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
return [
['M', x - r, y - r],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x + 2 - r, y - r],
['L', x + r - 2, y - r],
['M', x, y - 2 * r + 2],
['L', x, y - 2],
];
};
const data = {
id: 'root',
label: 'root',
children: [
{
id: 'c1',
label: 'c1',
children: [
{
id: 'c1-1',
label: 'c1-1',
},
{
id: 'c1-2',
label: 'c1-2',
children: [
{
id: 'c1-2-1',
label: 'c1-2-1'
},
{
id: 'c1-2-2',
label: 'c1-2-2'
},
]
},
]
},
{
id: 'c2',
label: 'c2'
},
{
id: 'c3',
label: 'c3',
children: [
{
id: 'c3-1',
label: 'c3-1'
},
{
id: 'c3-2',
label: 'c3-2',
children: [
{
id: 'c3-2-1',
label: 'c3-2-1'
},
{
id: 'c3-2-2',
label: 'c3-2-2'
},
{
id: 'c3-2-3',
label: 'c3-2-3'
},
]
},
{
id: 'c3-3',
label: 'c3-3'
},
]
}
]
}
G6.Util.traverseTree(data, d => {
d.leftIcon = {
style: {
fill: '#e6fffb',
stroke: '#e6fffb'
},
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ'
}
return true
})
G6.registerNode('icon-node', {
options: {
size: [60, 20],
stroke: '#91d5ff',
fill: '#91d5ff'
},
draw(cfg, group) {
const styles = this.getShapeStyle(cfg)
const { labelCfg = {} } = cfg
const keyShape = group.addShape('rect', {
attrs: {
...styles,
x: 0,
y: 0
}
})
/**
* leftIcon 格式如下
* {
* style: ShapeStyle;
* img: ''
* }
*/
if (cfg.leftIcon) {
const { style, img } = cfg.leftIcon
group.addShape('rect', {
attrs: {
x: 1,
y: 1,
width: 38,
height: styles.height - 2,
fill: '#8c8c8c',
...style
}
})
group.addShape('image', {
attrs: {
x: 8,
y: 8,
width: 24,
height: 24,
img: img || 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png',
},
name: 'image-shape',
});
}
// 如果不需要动态增加或删除元素,则不需要 add 这两个 marker
group.addShape('marker', {
attrs: {
x: 40,
y: 52,
r: 6,
stroke: '#73d13d',
cursor: 'pointer',
symbol: EXPAND_ICON
},
name: 'add-item'
})
group.addShape('marker', {
attrs: {
x: 80,
y: 52,
r: 6,
stroke: '#ff4d4f',
cursor: 'pointer',
symbol: COLLAPSE_ICON
},
name: 'remove-item'
})
if (cfg.label) {
group.addShape('text', {
attrs: {
...labelCfg.style,
text: cfg.label,
x: 50,
y: 25,
}
})
}
return keyShape
}
}, 'rect')
G6.registerEdge('flow-line', {
draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const { style } = cfg
const shape = group.addShape('path', {
attrs: {
stroke: style.stroke,
endArrow: style.endArrow,
path: [
['M', startPoint.x, startPoint.y],
['L', startPoint.x, (startPoint.y + endPoint.y) / 2],
['L', endPoint.x, (startPoint.y + endPoint.y) / 2,],
['L', endPoint.x, endPoint.y],
],
},
});
return shape;
}
});
const defaultStateStyles = {
hover: {
stroke: '#1890ff',
lineWidth: 2
}
}
const defaultNodeStyle = {
fill: '#91d5ff',
stroke: '#40a9ff',
radius: 5
}
const defaultEdgeStyle = {
stroke: '#91d5ff',
endArrow: {
path: 'M 0,0 L 12, 6 L 9,0 L 12, -6 Z',
fill: '#91d5ff',
d: -20
}
}
const defaultLayout = {
type: 'compactBox',
direction: 'TB',
getId: function getId(d) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 40;
},
getHGap: function getHGap() {
return 70;
},
}
const defaultLabelCfg = {
style: {
fill: '#000',
fontSize: 12
}
}
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const minimap = new G6.Minimap({
size: [150, 100]
})
const graph = new G6.TreeGraph({
container: 'container',
width,
height,
linkCenter: true,
plugins: [minimap],
modes: {
default: [
'drag-canvas',
'zoom-canvas',
],
},
defaultNode: {
type: 'icon-node',
size: [120, 40],
style: defaultNodeStyle,
labelCfg: defaultLabelCfg
},
defaultEdge: {
type: 'flow-line',
style: defaultEdgeStyle,
},
nodeStateStyles: defaultStateStyles,
edgeStateStyles: defaultStateStyles,
layout: defaultLayout
});
graph.data(data);
graph.render();
graph.fitView();
graph.on('node:mouseenter', evt => {
const { item } = evt
graph.setItemState(item, 'hover', true)
})
graph.on('node:mouseleave', evt => {
const { item } = evt
graph.setItemState(item, 'hover', false)
})
graph.on('node:click', evt => {
const { item, target } = evt
const targetType = target.get('type')
const name = target.get('name')
// 增加元素
if (targetType === 'marker') {
const model = item.getModel()
if (name === 'add-item') {
if (!model.children) {
model.children = []
}
model.children.push({
id: Math.random(),
label: Math.random()
})
graph.updateChild(model, model.id)
} else if (name === 'remove-item') {
graph.removeChild(model.id)
}
}
})

View File

@ -4,6 +4,14 @@
"en": "Category"
},
"demos": [
{
"filename": "customEdgeTree.js",
"title": {
"zh": "自定义树图中的边",
"en": "Edges on Tree"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*AncCQ5e7XncAAAAAAAAAAABkARQnAQ"
},
{
"filename": "treeEdgeLabel.js",
"title": {

View File

@ -9,5 +9,6 @@ Thanks to the item's custom mechanism of G6, users can design their own tree wit
Combining with custom items, the complex tree visualization can be realized.
- Examples 1 : Add labels for edges.
- Examples 2 : Customize the node with expand and collapse button.
- Examples 1 : Customize the node and the edge with tree graph, support add or remove node.
- Examples 2 : Add labels for edges.
- Examples 3 : Customize the node with expand and collapse button.

View File

@ -9,5 +9,6 @@ G6 自定义节点与边机制允许用户对树图视觉样式进行定制。
通过结合自定义元素,可以实现较为复杂的树可视化。
- 代码演示 1 :为边增加文本标签。
- 代码演示 2 :自定义带有收缩/扩展按钮的节点。
- 代码演示 1 自定义树图中的节点及边,支持增加和删除节点;
- 代码演示 2 :为边增加文本标签。
- 代码演示 3 :自定义带有收缩/扩展按钮的节点。

View File

@ -50,7 +50,7 @@
"site:deploy": "npm run site:build && gh-pages -d public",
"start": "npm run site:develop",
"test": "jest",
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/combo-issue-spec.ts",
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/issues-spec.ts",
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
"watch": "father build -w",
"cdn": "antv-bin upload -n @antv/g6"

View File

@ -374,6 +374,7 @@ export const shapeBase: ShapeOptions = {
mix(originStyle[keyShapeName], {
[key]: enableStyle,
});
delete originStyle[key]
}
delete enableStatesStyle[key];
}

View File

@ -271,7 +271,7 @@ export interface GraphOptions {
linkCenter?: boolean;
}
interface StateStyles {
export interface StateStyles {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}

View File

@ -175,7 +175,7 @@ const DagreLayout = () => {
graph.on('canvas:click', e => {
console.log(graph.toDataURL('image/jpeg', '#fff'));
// graph.downloadImage('test', '#eee');
graph.downloadImage('test', 'image/png');
});
}
});

View File

@ -0,0 +1,441 @@
import React, { useEffect } from 'react';
import G6 from '../../../src';
import { traverseTree } from '../../../src/util/graphic'
import { IGraph, ITreeGraph } from '../../../src/interface/graph';
import { EdgeConfig, TreeGraphData, StateStyles, ShapeStyle } from '../../../src/types';
import { INode, IEdge } from '../../../src/interface/item';
interface IFlowCharts {
data: TreeGraphData;
width?: number;
height?: number;
nodeType?: string;
edgeType?: string;
nodeStyle?: ShapeStyle;
edgeStyle?: ShapeStyle;
nodeStateStyles?: StateStyles;
edgeStateStyles?: StateStyles;
nodeSize?: number | number[];
labelCfg?: {
style: {
stroke?: string;
fontSize?: number;
}
};
layout?: any;
enableEdit?: boolean;
handleNodeClick?: (item: INode, graph: IGraph) => void;
handleEdgeClick?: (item: IEdge, graph: IGraph) => void;
handleNodeHover?: (item: INode, graph: IGraph) => void;
handleNodeUnHover?: (item: INode, graph: IGraph) => void;
handleEdgeHover?: (item: INode, graph: IGraph) => void;
handleEdgeUnHover?: (item: INode, graph: IGraph) => void;
collapseExpand?: boolean;
}
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
return [
['M', x - r, y - r],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x + 2 - r, y - r],
['L', x + r - 2, y - r],
];
};
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
return [
['M', x - r, y - r],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x + 2 - r, y - r],
['L', x + r - 2, y - r],
['M', x, y - 2 * r + 2],
['L', x, y - 2],
];
};
const data1 = {
id: 'root',
label: 'root',
children: [
{
id: 'c1',
label: 'c1',
children: [
{
id: 'c1-1',
label: 'c1-1',
},
{
id: 'c1-2',
label: 'c1-2',
children: [
{
id: 'c1-2-1',
label: 'c1-2-1'
},
{
id: 'c1-2-2',
label: 'c1-2-2'
},
]
},
]
},
{
id: 'c2',
label: 'c2'
},
{
id: 'c3',
label: 'c3',
children: [
{
id: 'c3-1',
label: 'c3-1'
},
{
id: 'c3-2',
label: 'c3-2',
children: [
{
id: 'c3-2-1',
label: 'c3-2-1'
},
{
id: 'c3-2-2',
label: 'c3-2-2'
},
{
id: 'c3-2-3',
label: 'c3-2-3'
},
]
},
{
id: 'c3-3',
label: 'c3-3'
},
]
}
]
}
traverseTree((data1 as any), d => {
d.leftIcon = {
style: {
fill: '#e6fffb',
stroke: '#e6fffb'
},
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ'
}
return true
})
G6.registerEdge('flow-line', {
draw(cfg: EdgeConfig, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const { style } = cfg
const shape = group.addShape('path', {
attrs: {
stroke: style.stroke,
endArrow: style.endArrow,
path: [
['M', startPoint.x, startPoint.y],
['L', startPoint.x, (startPoint.y + endPoint.y) / 2],
['L', endPoint.x, (startPoint.y + endPoint.y) / 2,],
['L', endPoint.x, endPoint.y],
],
},
});
return shape;
}
});
let graph: ITreeGraph = null;
const defaultStateStyles = {
hover: {
stroke: '#1890ff',
lineWidth: 2
}
}
const defaultNodeStyle = {
fill: '#91d5ff',
stroke: '#40a9ff',
radius: 5
}
const defaultEdgeStyle = {
stroke: '#91d5ff',
endArrow: {
path: 'M 0,0 L 12, 6 L 9,0 L 12, -6 Z',
fill: '#91d5ff',
d: -20
}
}
const defaultLayout = {
type: 'compactBox',
direction: 'TB',
getId: function getId(d) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 40;
},
getHGap: function getHGap() {
return 70;
},
}
const defaultLabelCfg = {
style: {
fill: '#000',
fontSize: 12
}
}
const FlowTree: React.SFC<IFlowCharts> = ({
data = data1,
width = 500,
height = 500,
nodeType = 'icon-node',
edgeType = 'flow-line',
collapseExpand = false,
nodeSize = [120, 40],
labelCfg = defaultLabelCfg,
layout = defaultLayout,
enableEdit = true,
nodeStyle = defaultNodeStyle,
edgeStyle = defaultEdgeStyle,
nodeStateStyles = defaultStateStyles,
edgeStateStyles = defaultStateStyles,
handleNodeClick,
handleEdgeClick,
handleNodeHover,
handleNodeUnHover,
handleEdgeHover,
handleEdgeUnHover
}) => {
const container = React.useRef();
useEffect(() => {
G6.registerNode('icon-node', {
options: {
size: [60, 20],
stroke: '#91d5ff',
fill: '#91d5ff'
},
draw(cfg, group) {
const styles = this.getShapeStyle(cfg)
const { labelCfg = {} } = cfg
const keyShape = group.addShape('rect', {
attrs: {
...styles,
x: 0,
y: 0
}
})
/**
* leftIcon
* {
* style: ShapeStyle;
* img: ''
* }
*/
if (cfg.leftIcon) {
const { style, img } = cfg.leftIcon as any
group.addShape('rect', {
attrs: {
x: 1,
y: 1,
width: 38,
height: styles.height - 2,
fill: '#8c8c8c',
...style
}
})
group.addShape('image', {
attrs: {
x: 8,
y: 8,
width: 24,
height: 24,
img: img || 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png',
},
name: 'image-shape',
});
}
if (enableEdit) {
group.addShape('marker', {
attrs: {
x: 40,
y: 52,
r: 6,
stroke: '#73d13d',
cursor: 'pointer',
symbol: EXPAND_ICON
},
name: 'add-item'
})
group.addShape('marker', {
attrs: {
x: 80,
y: 52,
r: 6,
stroke: '#ff4d4f',
cursor: 'pointer',
symbol: COLLAPSE_ICON
},
name: 'remove-item'
})
}
if (cfg.label) {
group.addShape('text', {
attrs: {
...labelCfg.style,
text: cfg.label,
x: 50,
y: 25,
}
})
}
return keyShape
}
}, 'rect')
if (!graph) {
const minimap = new G6.Minimap({
size: [150, 100]
})
graph = new G6.TreeGraph({
container: container.current,
width,
height,
linkCenter: true,
plugins: [minimap],
modes: {
default: [
'drag-canvas',
'zoom-canvas',
],
},
defaultNode: {
type: nodeType,
size: nodeSize,
style: nodeStyle,
labelCfg
},
defaultEdge: {
type: edgeType,
style: edgeStyle,
},
nodeStateStyles,
edgeStateStyles,
layout
});
graph.data(data);
graph.render();
graph.fitView();
if (collapseExpand) {
graph.addBehaviors({
type: 'collapse-expand',
onChange: function onChange(item, collapsed) {
const data = item.get('model').data;
data.collapsed = collapsed;
return true;
},
}, 'default')
}
graph.on('node:mouseenter', evt => {
const { item } = evt
graph.setItemState(item, 'hover', true)
if (handleNodeHover) {
handleNodeHover(item, graph)
}
})
graph.on('node:mouseleave', evt => {
const { item } = evt
graph.setItemState(item, 'hover', false)
if (handleNodeUnHover) {
handleNodeUnHover(item, graph)
}
})
graph.on('node:click', evt => {
const { item, target } = evt
const targetType = target.get('type')
const name = target.get('name')
// 增加元素
if (targetType === 'marker') {
const model = item.getModel()
if (name === 'add-item') {
if (!model.children) {
model.children = []
}
model.children.push({
id: Math.random(),
label: Math.random()
})
graph.updateChild(model, model.id)
} else if (name === 'remove-item') {
graph.removeChild(model.id)
}
} else {
if (handleNodeClick) {
handleNodeClick(item, graph)
}
}
})
graph.on('edge:mouseenter', evt => {
const { item } = evt
graph.setItemState(item, 'hover', true)
if (handleEdgeHover) {
handleEdgeHover(item, graph)
}
})
graph.on('edge:mouseleave', evt => {
const { item } = evt
graph.setItemState(item, 'hover', false)
if (handleEdgeUnHover) {
handleEdgeUnHover(item, graph)
}
})
graph.on('edge:click', evt => {
const { item } = evt
if (handleEdgeClick) {
handleEdgeClick(item, graph)
}
})
}
}, []);
return <div ref={container}></div>;
};
export default FlowTree;

View File

@ -421,7 +421,7 @@ const FlowTree = () => {
width: window.innerWidth,
height: window.innerHeight,
modes: {
default: ['drag-canvas'],
default: ['drag-canvas', 'drag-node'],
},
defaultNode: {
shape: 'operation',

View File

@ -4,6 +4,7 @@ import React from 'react';
import FlowTree from './component/flow-tree'
import TreeData from './component/tree-data';
import SlefTree from './component/self-tree'
import FlowComponent from './component/flow-component'
export default { title: 'Tree' };
@ -11,3 +12,4 @@ storiesOf('Tree', module)
.add('flow-tree', () => <FlowTree />)
.add('tree-data', () => <TreeData />)
.add('register flow tree', () => <SlefTree />)
.add('FlowComponent', () => <FlowComponent />)

View File

@ -4,6 +4,101 @@ const div = document.createElement('div');
div.id = 'container';
document.body.appendChild(div);
describe('edge click state', () => {
it.only('edge ', () => {
G6.registerBehavior("active-edge", {
getEvents() {
return {
"edge:click": "onEdgeSelect",
"edge:mouseenter": "onEdgeHover",
"edge:mouseleave": "onEdgeLeave",
"canvas:mousedown": "clearSelectedEdge"
};
},
onEdgeSelect(evt) {
console.log('mousedown')
const item = evt.item;
if (item.hasState("select")) {
this.graph.setItemState(item, 'select', false);
return;
}
this.graph.setItemState(item, 'select', true);
},
onEdgeHover(evt) {
console.log('hover')
const item = evt.item;
if (item.hasState("select")) {
return false;
}
this.graph.setItemState(item, 'hover', true);
},
onEdgeLeave(evt) {
console.log('leave')
const item = evt.item;
this.graph.setItemState(item, 'hover', false);
},
clearSelectedEdge(evt) {
console.log('clear')
const edges = this.graph.findAllByState("edge", 'select');
edges.forEach(edge => {
this.graph.setItemState(edge, 'select', false);
});
}
});
const graph = new G6.Graph({
container: "container",
width: 500,
height: 500,
defaultEdge: {
type: "line",
style: {
stroke: "#24A0FF",
radius: 4,
offset: 15,
lineWidth: 2,
lineAppendWidth: 4,
cursor: "pointer",
endArrow: {
path: "M 0,0 L 7,3 L 5,0 L 7,-3 Z",
fill: "#24A0FF"
}
}
},
edgeStateStyles: {
hover: {
stroke: "#0ff",
endArrow: {
fill: "#FF7868"
}
},
select: {
stroke: "#FF7868",
endArrow: {
fill: "#FF7868"
}
}
},
modes: {
default: [
'drag-node',
'active-edge'
]
}
});
graph.read({
nodes: [
{ id: '1', x: 100, y: 100},
{ id: '2', x: 100, y: 220}
],
edges: [
{ source: '1', target: '2' }
]
})
})
})
describe('dragenter dragleave', () => {
const data = {
nodes: [{