mirror of
https://gitee.com/antv/g6.git
synced 2024-12-05 05:09:07 +08:00
feat: update shape style
This commit is contained in:
parent
2222dd8680
commit
2930ba7080
@ -434,11 +434,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
getStateStyle(cfg) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}, 'triangle')
|
}, 'triangle')
|
||||||
G6.registerNode('triangle2', {
|
G6.registerNode('triangle2', {
|
||||||
labelPosition: 'center',
|
labelPosition: 'center',
|
||||||
getCustomConfig() {
|
getCustomConfig(cfg) {
|
||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
direction: 'down',
|
direction: 'down',
|
||||||
|
135
demos/test-graph-node-style.html
Normal file
135
demos/test-graph-node-style.html
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mountNode"></div>
|
||||||
|
<script src="../build/g6.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'triangle',
|
||||||
|
label: '主题 ModelRect Title',
|
||||||
|
description: '这里是一顿描述信息,不知道长短',
|
||||||
|
x: 150,
|
||||||
|
y: 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const graph = new G6.Graph({
|
||||||
|
container: 'mountNode',
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
defaultNode: {
|
||||||
|
shape: 'modelRect',
|
||||||
|
style: {
|
||||||
|
fill: 'red'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
show: true,
|
||||||
|
width: 30,
|
||||||
|
height: 30
|
||||||
|
},
|
||||||
|
linkPoints: {
|
||||||
|
top: false,
|
||||||
|
fill: '#fff',
|
||||||
|
size: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodeStateStyles: {
|
||||||
|
select: {
|
||||||
|
fill: 'red'
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
fill: 'blue'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 这里优先级大于graph中的nodeStyle
|
||||||
|
graph.node(node => {
|
||||||
|
let fill = 'red'
|
||||||
|
let shape = 'diamond'
|
||||||
|
if (node.inDegree > 200) {
|
||||||
|
fill = '#36cfc9'
|
||||||
|
shape = 'diamond'
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
shape,
|
||||||
|
icon: {
|
||||||
|
show: true,
|
||||||
|
width: 25,
|
||||||
|
height: 25
|
||||||
|
},
|
||||||
|
linkPoints: {
|
||||||
|
top: true,
|
||||||
|
bottom: true
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill,
|
||||||
|
stroke: '#eaff8f',
|
||||||
|
radius: 15
|
||||||
|
},
|
||||||
|
stateStyles: {
|
||||||
|
hover: {
|
||||||
|
fill: '#bae637',
|
||||||
|
stroke: '#87e8de',
|
||||||
|
width: 130,
|
||||||
|
height: 200,
|
||||||
|
r: 100
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
fill: '#ccc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
graph.data(data)
|
||||||
|
graph.render()
|
||||||
|
console.log(graph.save())
|
||||||
|
|
||||||
|
graph.on('node:click', evt => {
|
||||||
|
const { item } = evt
|
||||||
|
// graph.setItemState(item, 'select', true)
|
||||||
|
graph.updateItem(item, {
|
||||||
|
size: [ 160, 130],
|
||||||
|
style: {
|
||||||
|
fill: '#b5f5ec'
|
||||||
|
},
|
||||||
|
preRect: {
|
||||||
|
fill: '#ff4d4f'
|
||||||
|
},
|
||||||
|
stateIcon: {
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/c781088a-c635-452a-940c-0173663456d4.svg'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
graph.on('node:mouseenter', evt => {
|
||||||
|
const { item } = evt
|
||||||
|
|
||||||
|
// graph.setItemState(item, 'hover', true)
|
||||||
|
// graph.updateItem(item, {
|
||||||
|
// style: {
|
||||||
|
// r: 45,
|
||||||
|
// icon: {
|
||||||
|
// img: 'https://gw.alipayobjects.com/zos/basement_prod/c781088a-c635-452a-940c-0173663456d4.svg'
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
|
graph.on('node:mouseleave', evt => {
|
||||||
|
const { item } = evt
|
||||||
|
// graph.setItemState(item, 'hover', false)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -5,6 +5,7 @@ const NODE = 'node';
|
|||||||
const EDGE = 'edge';
|
const EDGE = 'edge';
|
||||||
const CFG_PREFIX = 'default';
|
const CFG_PREFIX = 'default';
|
||||||
const MAPPER_SUFFIX = 'Mapper';
|
const MAPPER_SUFFIX = 'Mapper';
|
||||||
|
const STATE_SUFFIX = 'stateStyles';
|
||||||
const hasOwnProperty = Object.hasOwnProperty;
|
const hasOwnProperty = Object.hasOwnProperty;
|
||||||
|
|
||||||
class ItemController {
|
class ItemController {
|
||||||
@ -16,14 +17,14 @@ class ItemController {
|
|||||||
const parent = graph.get(type + 'Group') || graph.get('group');
|
const parent = graph.get(type + 'Group') || graph.get('group');
|
||||||
const upperType = Util.upperFirst(type);
|
const upperType = Util.upperFirst(type);
|
||||||
let item;
|
let item;
|
||||||
let styles = graph.get(type + 'Style') || {};
|
let styles = graph.get(type + Util.upperFirst(STATE_SUFFIX)) || {};
|
||||||
const defaultModel = graph.get(CFG_PREFIX + upperType);
|
const defaultModel = graph.get(CFG_PREFIX + upperType);
|
||||||
const mapper = graph.get(type + MAPPER_SUFFIX);
|
const mapper = graph.get(type + MAPPER_SUFFIX);
|
||||||
if (mapper) {
|
if (mapper) {
|
||||||
const mappedModel = mapper(model);
|
const mappedModel = mapper(model);
|
||||||
if (mappedModel.styles) {
|
if (mappedModel[STATE_SUFFIX]) {
|
||||||
styles = mappedModel.styles;
|
styles = mappedModel[STATE_SUFFIX];
|
||||||
delete mappedModel.styles;
|
delete mappedModel[STATE_SUFFIX];
|
||||||
}
|
}
|
||||||
Util.each(mappedModel, (val, cfg) => {
|
Util.each(mappedModel, (val, cfg) => {
|
||||||
model[cfg] = val;
|
model[cfg] = val;
|
||||||
|
@ -401,16 +401,16 @@ class Graph extends EventEmitter {
|
|||||||
* 若是自定义节点切在各种状态下
|
* 若是自定义节点切在各种状态下
|
||||||
* graph.node(node => {
|
* graph.node(node => {
|
||||||
* return {
|
* return {
|
||||||
* default: {
|
* {
|
||||||
* fill: 'red',
|
shape: 'rect',
|
||||||
* opacity: 1
|
label: node.id,
|
||||||
* },
|
style: { fill: '#666' },
|
||||||
* selected: {
|
styles: {
|
||||||
* style: {
|
default: { fill: 'red' },
|
||||||
* fill: 'blue',
|
selected: { fill: 'blue' },
|
||||||
* opacity: 0.2
|
custom: { fill: 'green' }
|
||||||
* }
|
}
|
||||||
* }
|
}
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* @param {function} nodeFn 指定每个节点样式
|
* @param {function} nodeFn 指定每个节点样式
|
||||||
|
@ -30,7 +30,8 @@ const singleNodeDefinition = Util.mix({}, SingleShapeMixin, {
|
|||||||
* @return {Array} 宽高
|
* @return {Array} 宽高
|
||||||
*/
|
*/
|
||||||
getSize(cfg) {
|
getSize(cfg) {
|
||||||
let size = cfg.size || Global.defaultNode.size;
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
let size = cfg.size || customOptions.size || this.options.size || Global.defaultNode.size;
|
||||||
if (!Util.isArray(size)) {
|
if (!Util.isArray(size)) {
|
||||||
size = [ size, size ];
|
size = [ size, size ];
|
||||||
}
|
}
|
||||||
|
@ -5,64 +5,70 @@ const deepMix = require('@antv/util/lib/deep-mix');
|
|||||||
Shape.registerNode('circle', {
|
Shape.registerNode('circle', {
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: 30,
|
||||||
default: {
|
style: {
|
||||||
r: 20,
|
|
||||||
stroke: '#69c0ff',
|
|
||||||
fill: '#e6f7ff',
|
|
||||||
lineWidth: 1,
|
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
// 文本样式配置
|
stroke: '#87e8de',
|
||||||
labelCfg: {
|
fill: '#36cfc9',
|
||||||
style: {
|
lineWidth: 1
|
||||||
fill: '#595959'
|
},
|
||||||
}
|
labelCfg: {
|
||||||
|
style: {
|
||||||
|
fill: '#595959'
|
||||||
},
|
},
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
offset: 15
|
||||||
linkPoints: {
|
},
|
||||||
top: false,
|
stateStyles: {
|
||||||
right: false,
|
// 鼠标hover状态下的配置
|
||||||
bottom: false,
|
hover: {
|
||||||
left: false,
|
lineWidth: 3
|
||||||
// circle的大小
|
|
||||||
size: 3,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#72CC4A',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
},
|
||||||
// 节点中icon配置
|
// 选中节点状态下的配置
|
||||||
icon: {
|
select: {
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
lineWidth: 5
|
||||||
show: false,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
|
||||||
width: 16,
|
|
||||||
height: 16
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 鼠标hover状态下的配置
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
hover: {
|
linkPoints: {
|
||||||
lineWidth: 3
|
top: false,
|
||||||
|
right: false,
|
||||||
|
bottom: false,
|
||||||
|
left: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 3,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#72CC4A',
|
||||||
|
stroke: '#72CC4A'
|
||||||
},
|
},
|
||||||
// 选中节点状态下的配置
|
// 节点中icon配置
|
||||||
select: {
|
icon: {
|
||||||
lineWidth: 5
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: false,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shapeType: 'circle',
|
shapeType: 'circle',
|
||||||
// 文本位置
|
// 文本位置
|
||||||
labelPosition: 'bottom',
|
labelPosition: 'bottom',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, icon: defaultIcon } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon } = customOptions;
|
||||||
const { icon, linkPoints, ...circleStyle } = style;
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const r = size[0];
|
||||||
const keyShape = group.addShape('circle', {
|
const keyShape = group.addShape('circle', {
|
||||||
attrs: circleStyle
|
attrs: {
|
||||||
|
...style,
|
||||||
|
r
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { r } = circleStyle;
|
|
||||||
const { width, height, show } = icon;
|
const { width, height, show } = icon;
|
||||||
if (show) {
|
if (show) {
|
||||||
const image = group.addShape('image', {
|
const image = group.addShape('image', {
|
||||||
@ -77,20 +83,35 @@ Shape.registerNode('circle', {
|
|||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top, left, right, bottom, size,
|
this.drawLinkPoints(cfg, group);
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
|
||||||
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const { top, left, right, bottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const r = size[0];
|
||||||
if (left) {
|
if (left) {
|
||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: -r,
|
x: -r,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'circle-anchor-left'
|
className: 'circle-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,14 +119,12 @@ Shape.registerNode('circle', {
|
|||||||
// right circle
|
// right circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: r,
|
x: r,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'circle-anchor-right'
|
className: 'circle-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,14 +132,12 @@ Shape.registerNode('circle', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -r,
|
y: -r,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'circle-anchor-top'
|
className: 'circle-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,46 +145,33 @@ Shape.registerNode('circle', {
|
|||||||
// bottom circle
|
// bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: r,
|
y: r,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'circle-anchor-bottom'
|
className: 'circle-mark-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyShape;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取节点宽高
|
|
||||||
* @internal 返回节点的大小,以 [width, height] 的方式维护
|
|
||||||
* @param {Object} cfg 节点的配置项
|
|
||||||
* @return {Array} 宽高
|
|
||||||
*/
|
|
||||||
getSize(cfg) {
|
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { r } = style;
|
|
||||||
|
|
||||||
return [ 2 * r, 2 * r ];
|
|
||||||
},
|
},
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default || {};
|
const { style: defaultStyle, icon: defaultIcon, labelCfg: defaultLabelCfg } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon, labelCfg: customLabelCfg } = customOptions;
|
||||||
const { icon, linkPoints, labelCfg: defaultLabelCfg, ...circleStyle } = style;
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
const { r } = circleStyle;
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const r = size[0];
|
||||||
|
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
|
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
keyShape.attr(circleStyle);
|
keyShape.attr({
|
||||||
|
...style,
|
||||||
|
r
|
||||||
|
});
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
|
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
@ -187,52 +191,68 @@ Shape.registerNode('circle', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('circle-anchor-left');
|
const { size: markSize, fill: markFill, stroke: markStroke, lineWidth: borderWidth } = linkPoints;
|
||||||
if (anchorLeft) {
|
|
||||||
anchorLeft.attr({
|
const size = this.getSize(cfg);
|
||||||
|
const r = size[0];
|
||||||
|
|
||||||
|
const markLeft = group.findByClassName('circle-mark-left');
|
||||||
|
if (markLeft) {
|
||||||
|
markLeft.attr({
|
||||||
x: -r,
|
x: -r,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('circle-anchor-right');
|
const markRight = group.findByClassName('circle-mark-right');
|
||||||
if (anchorRight) {
|
if (markRight) {
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
x: r,
|
x: r,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('circle-anchor-top');
|
const markTop = group.findByClassName('circle-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -r,
|
y: -r,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorBottom = group.findByClassName('circle-anchor-bottom');
|
const markBottom = group.findByClassName('circle-mark-bottom');
|
||||||
if (anchorBottom) {
|
if (markBottom) {
|
||||||
anchorBottom.attr({
|
markBottom.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: r,
|
y: r,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,63 +5,65 @@ const deepMix = require('@antv/util/lib/deep-mix');
|
|||||||
Shape.registerNode('diamond', {
|
Shape.registerNode('diamond', {
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: [ 100, 100 ],
|
||||||
default: {
|
style: {
|
||||||
width: 100,
|
stroke: '#87e8de',
|
||||||
height: 100,
|
fill: '#36cfc9',
|
||||||
stroke: '#69c0ff',
|
lineWidth: 1
|
||||||
fill: '#e6f7ff',
|
},
|
||||||
lineWidth: 1,
|
// 文本样式配置
|
||||||
// 文本样式配置
|
labelCfg: {
|
||||||
labelCfg: {
|
style: {
|
||||||
style: {
|
fill: '#595959'
|
||||||
fill: '#595959'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
|
||||||
linkPoints: {
|
|
||||||
top: false,
|
|
||||||
right: false,
|
|
||||||
bottom: false,
|
|
||||||
left: false,
|
|
||||||
// circle的大小
|
|
||||||
size: 3,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#72CC4A',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
|
||||||
// 节点中icon配置
|
|
||||||
icon: {
|
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
|
||||||
show: false,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
|
||||||
width: 16,
|
|
||||||
height: 16
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 鼠标hover状态下的配置
|
stateStyles: {
|
||||||
hover: {
|
// 鼠标hover状态下的配置
|
||||||
lineWidth: 3
|
hover: {
|
||||||
|
lineWidth: 3
|
||||||
|
},
|
||||||
|
// 选中节点状态下的配置
|
||||||
|
select: {
|
||||||
|
lineWidth: 5
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 选中节点状态下的配置
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
select: {
|
linkPoints: {
|
||||||
lineWidth: 5
|
top: false,
|
||||||
|
right: false,
|
||||||
|
bottom: false,
|
||||||
|
left: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 3,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#72CC4A',
|
||||||
|
stroke: '#72CC4A'
|
||||||
|
},
|
||||||
|
// 节点中icon配置
|
||||||
|
icon: {
|
||||||
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: false,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shapeType: 'circle',
|
shapeType: 'circle',
|
||||||
// 文本位置
|
// 文本位置
|
||||||
labelPosition: 'center',
|
labelPosition: 'center',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, icon: defaultIcon } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon } = customOptions;
|
||||||
const { icon, linkPoints, width, height, ...diamondStyle } = style;
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
|
||||||
const path = this.getPath(cfg);
|
const path = this.getPath(cfg);
|
||||||
const keyShape = group.addShape('path', {
|
const keyShape = group.addShape('path', {
|
||||||
attrs: {
|
attrs: {
|
||||||
path,
|
path,
|
||||||
...diamondStyle
|
...style
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,20 +81,36 @@ Shape.registerNode('diamond', {
|
|||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top, left, right, bottom, size,
|
this.drawLinkPoints(cfg, group);
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
|
||||||
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const { top, left, right, bottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
if (left) {
|
if (left) {
|
||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'diamond-anchor-left'
|
className: 'diamond-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,14 +118,12 @@ Shape.registerNode('diamond', {
|
|||||||
// right circle
|
// right circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: width / 2,
|
x: width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'diamond-anchor-right'
|
className: 'diamond-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,14 +131,12 @@ Shape.registerNode('diamond', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'diamond-anchor-top'
|
className: 'diamond-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,27 +144,19 @@ Shape.registerNode('diamond', {
|
|||||||
// bottom circle
|
// bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'diamond-anchor-bottom'
|
className: 'diamond-mark-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyShape;
|
|
||||||
},
|
|
||||||
shouldUpdate() {
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
getPath(cfg) {
|
getPath(cfg) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const size = this.getSize(cfg);
|
||||||
const defaultConfig = customStyle.default;
|
const width = size[0];
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const height = size[1];
|
||||||
const { width, height } = style;
|
|
||||||
const path = [
|
const path = [
|
||||||
[ 'M', 0, -height / 2 ], // 上部顶点
|
[ 'M', 0, -height / 2 ], // 上部顶点
|
||||||
[ 'L', width / 2, 0 ], // 右侧点
|
[ 'L', width / 2, 0 ], // 右侧点
|
||||||
@ -160,35 +166,22 @@ Shape.registerNode('diamond', {
|
|||||||
];
|
];
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 获取节点宽高
|
|
||||||
* @internal 返回节点的大小,以 [width, height] 的方式维护
|
|
||||||
* @param {Object} cfg 节点的配置项
|
|
||||||
* @return {Array} 宽高
|
|
||||||
*/
|
|
||||||
getSize(cfg) {
|
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { width, height } = style;
|
|
||||||
|
|
||||||
return [ width, height ];
|
|
||||||
},
|
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default || {};
|
const { style: defaultStyle, icon: defaultIcon, labelCfg: defaultLabelCfg } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon, labelCfg: customLabelCfg } = customOptions;
|
||||||
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
|
||||||
const { width, height, icon, linkPoints, labelCfg: defaultLabelCfg, ...diamondStyle } = style;
|
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
const path = this.getPath(cfg);
|
const path = this.getPath(cfg);
|
||||||
keyShape.attr({
|
keyShape.attr({
|
||||||
path,
|
path,
|
||||||
...diamondStyle
|
...style
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
|
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
@ -208,52 +201,69 @@ Shape.registerNode('diamond', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('diamond-anchor-left');
|
const { size: markSize, fill: markFill, stroke: markStroke, lineWidth: borderWidth } = linkPoints;
|
||||||
if (anchorLeft) {
|
|
||||||
anchorLeft.attr({
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
|
const markLeft = group.findByClassName('diamond-mark-left');
|
||||||
|
if (markLeft) {
|
||||||
|
markLeft.attr({
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('diamond-anchor-right');
|
const markRight = group.findByClassName('diamond-mark-right');
|
||||||
if (anchorRight) {
|
if (markRight) {
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
x: width / 2,
|
x: width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('diamond-anchor-top');
|
const markTop = group.findByClassName('diamond-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorBottom = group.findByClassName('diamond-anchor-bottom');
|
const markBottom = group.findByClassName('diamond-mark-bottom');
|
||||||
if (anchorBottom) {
|
if (markBottom) {
|
||||||
anchorBottom.attr({
|
markBottom.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,68 +7,78 @@ const deepMix = require('@antv/util/lib/deep-mix');
|
|||||||
Shape.registerNode('ellipse', {
|
Shape.registerNode('ellipse', {
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: [ 60, 30 ],
|
||||||
default: {
|
style: {
|
||||||
rx: 60,
|
|
||||||
ry: 30,
|
|
||||||
stroke: '#69c0ff',
|
|
||||||
fill: '#e6f7ff',
|
|
||||||
lineWidth: 1,
|
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
// 文本样式配置
|
stroke: '#87e8de',
|
||||||
labelCfg: {
|
fill: '#36cfc9',
|
||||||
style: {
|
lineWidth: 1
|
||||||
fill: '#595959'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 节点中icon配置
|
|
||||||
icon: {
|
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
|
||||||
show: false,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
|
||||||
width: 36,
|
|
||||||
height: 36
|
|
||||||
},
|
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
|
||||||
linkPoints: {
|
|
||||||
top: false,
|
|
||||||
right: false,
|
|
||||||
bottom: false,
|
|
||||||
left: false,
|
|
||||||
// circle的大小
|
|
||||||
size: 3,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#72CC4A',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
|
||||||
// 连接点,默认为左右
|
|
||||||
anchorPoints: [[ 0, 0.5 ], [ 1, 0.5 ]]
|
|
||||||
},
|
},
|
||||||
// 鼠标hover状态下的配置
|
// 文本样式配置
|
||||||
hover: {
|
labelCfg: {
|
||||||
lineWidth: 3
|
style: {
|
||||||
|
fill: '#595959'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 选中节点状态下的配置
|
stateStyles: {
|
||||||
select: {
|
// 鼠标hover状态下的配置
|
||||||
lineWidth: 5
|
hover: {
|
||||||
}
|
lineWidth: 3
|
||||||
|
},
|
||||||
|
// 选中节点状态下的配置
|
||||||
|
select: {
|
||||||
|
lineWidth: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
|
linkPoints: {
|
||||||
|
top: false,
|
||||||
|
right: false,
|
||||||
|
bottom: false,
|
||||||
|
left: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 3,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#72CC4A',
|
||||||
|
stroke: '#72CC4A'
|
||||||
|
},
|
||||||
|
// 节点中icon配置
|
||||||
|
icon: {
|
||||||
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: false,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||||
|
width: 36,
|
||||||
|
height: 36
|
||||||
|
},
|
||||||
|
// 连接点,默认为左右
|
||||||
|
anchorPoints: [[ 0, 0.5 ], [ 1, 0.5 ]]
|
||||||
},
|
},
|
||||||
shapeType: 'ellipse',
|
shapeType: 'ellipse',
|
||||||
// 文本位置
|
// 文本位置
|
||||||
labelPosition: 'center',
|
labelPosition: 'center',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
// const customStyle = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
// const defaultConfig = customStyle.default;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
// const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
||||||
|
|
||||||
// 使用用户配置的size大小
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const { linkPoints, icon, ...ellipseStyle } = style;
|
const { style: defaultStyle, icon: defaultIcon } = this.options;
|
||||||
|
const { style: customStyle, icon: customIcon } = customOptions;
|
||||||
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
|
||||||
|
const rx = size[0];
|
||||||
|
const ry = size[1];
|
||||||
|
|
||||||
const { rx, ry } = ellipseStyle;
|
|
||||||
const keyShape = group.addShape('ellipse', {
|
const keyShape = group.addShape('ellipse', {
|
||||||
attrs: ellipseStyle
|
attrs: {
|
||||||
|
...style,
|
||||||
|
rx,
|
||||||
|
ry
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { width, height, show } = icon;
|
const { width, height, show } = icon;
|
||||||
@ -85,20 +95,37 @@ Shape.registerNode('ellipse', {
|
|||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top, left, right, bottom, size,
|
this.drawLinkPoints(cfg, group);
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
|
||||||
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const { top, left, right, bottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const rx = size[0];
|
||||||
|
const ry = size[1];
|
||||||
|
|
||||||
if (left) {
|
if (left) {
|
||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: -rx,
|
x: -rx,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'ellipse-anchor-left'
|
className: 'ellipse-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,14 +133,12 @@ Shape.registerNode('ellipse', {
|
|||||||
// right circle
|
// right circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: rx,
|
x: rx,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'ellipse-anchor-right'
|
className: 'ellipse-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,14 +146,12 @@ Shape.registerNode('ellipse', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -ry,
|
y: -ry,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'ellipse-anchor-top'
|
className: 'ellipse-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,47 +159,44 @@ Shape.registerNode('ellipse', {
|
|||||||
// bottom circle
|
// bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: ry,
|
y: ry,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'ellipse-anchor-bottom'
|
className: 'ellipse-mark-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyShape;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取节点宽高
|
|
||||||
* @internal 返回节点的大小,以 [width, height] 的方式维护
|
|
||||||
* @param {Object} cfg 节点的配置项
|
|
||||||
* @return {Array} 宽高
|
|
||||||
*/
|
|
||||||
getSize(cfg) {
|
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { rx, ry } = style;
|
|
||||||
|
|
||||||
return [ 2 * rx, 2 * ry ];
|
|
||||||
},
|
},
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
// const customStyle = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default || {};
|
// const defaultConfig = customStyle.default || {};
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
// const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
||||||
const { icon, linkPoints, labelCfg: defaultLabelCfg, ...ellipseStyle } = style;
|
// const { icon, linkPoints, labelCfg: defaultLabelCfg, ...ellipseStyle } = style;
|
||||||
|
|
||||||
|
// const { rx, ry } = ellipseStyle;
|
||||||
|
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { style: defaultStyle, icon: defaultIcon, labelCfg: defaultLabelCfg } = this.options;
|
||||||
|
const { style: customStyle, icon: customIcon, labelCfg: customLabelCfg } = customOptions;
|
||||||
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
|
||||||
|
const rx = size[0];
|
||||||
|
const ry = size[1];
|
||||||
|
|
||||||
const { rx, ry } = ellipseStyle;
|
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
|
|
||||||
keyShape.attr(ellipseStyle);
|
keyShape.attr({
|
||||||
|
...style,
|
||||||
|
rx,
|
||||||
|
ry
|
||||||
|
});
|
||||||
|
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
if (text) {
|
if (text) {
|
||||||
@ -195,53 +215,62 @@ Shape.registerNode('ellipse', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('ellipse-anchor-left');
|
const { size: markSize, ...markStyles } = linkPoints;
|
||||||
if (anchorLeft) {
|
|
||||||
anchorLeft.attr({
|
const size = this.getSize(cfg);
|
||||||
|
const rx = size[0];
|
||||||
|
const ry = size[1];
|
||||||
|
|
||||||
|
const markLeft = group.findByClassName('ellipse-mark-left');
|
||||||
|
if (markLeft) {
|
||||||
|
markLeft.attr({
|
||||||
|
...markStyles,
|
||||||
x: -rx,
|
x: -rx,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('ellipse-anchor-right');
|
const markRight = group.findByClassName('ellipse-mark-right');
|
||||||
if (anchorRight) {
|
if (markRight) {
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
|
...markStyles,
|
||||||
x: rx,
|
x: rx,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('ellipse-anchor-top');
|
const markTop = group.findByClassName('ellipse-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
|
...markStyles,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -ry,
|
y: -ry,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorBottom = group.findByClassName('ellipse-anchor-bottom');
|
const markBottom = group.findByClassName('ellipse-mark-bottom');
|
||||||
if (anchorBottom) {
|
if (markBottom) {
|
||||||
anchorBottom.attr({
|
markBottom.attr({
|
||||||
|
...markStyles,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: ry,
|
y: ry,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,95 +6,98 @@ Shape.registerNode('modelRect', {
|
|||||||
// labelPosition: 'center',
|
// labelPosition: 'center',
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: [ 185, 70 ],
|
||||||
default: {
|
style: {
|
||||||
width: 185,
|
|
||||||
height: 70,
|
|
||||||
radius: 5,
|
radius: 5,
|
||||||
stroke: '#69c0ff',
|
stroke: '#69c0ff',
|
||||||
fill: '#ffffff',
|
fill: '#ffffff',
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
fillOpacity: 1,
|
fillOpacity: 1
|
||||||
// 文本样式配置
|
|
||||||
labelCfg: {
|
|
||||||
style: {
|
|
||||||
fill: '#595959',
|
|
||||||
fontSize: 14
|
|
||||||
},
|
|
||||||
offset: 30
|
|
||||||
},
|
|
||||||
preRect: {
|
|
||||||
show: true,
|
|
||||||
width: 4,
|
|
||||||
fill: '#40a9ff',
|
|
||||||
radius: 2
|
|
||||||
},
|
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
|
||||||
linkPoints: {
|
|
||||||
top: false,
|
|
||||||
right: false,
|
|
||||||
bottom: false,
|
|
||||||
left: false,
|
|
||||||
// circle的大小
|
|
||||||
size: 3,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#72CC4A',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 节点中icon配置
|
|
||||||
logoIcon: {
|
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
|
||||||
show: true,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg',
|
|
||||||
width: 16,
|
|
||||||
height: 16
|
|
||||||
},
|
|
||||||
// 节点中表示状态的icon配置
|
|
||||||
stateIcon: {
|
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
|
||||||
show: true,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg',
|
|
||||||
width: 16,
|
|
||||||
height: 16
|
|
||||||
},
|
|
||||||
// 连接点,默认为左右
|
|
||||||
anchorPoints: [[ 0, 0.5 ], [ 1, 0.5 ]]
|
|
||||||
},
|
},
|
||||||
// hover状态下的配置
|
// 文本样式配置
|
||||||
hover: {
|
labelCfg: {
|
||||||
lineWidth: 2,
|
style: {
|
||||||
stroke: '#1890ff',
|
fill: '#595959',
|
||||||
fill: '#e6f7ff'
|
fontSize: 14
|
||||||
|
},
|
||||||
|
offset: 30
|
||||||
},
|
},
|
||||||
// 节点选中状态下的配置
|
stateStyles: {
|
||||||
select: {
|
// hover状态下的配置
|
||||||
lineWidth: 3,
|
hover: {
|
||||||
stroke: '#1890ff',
|
lineWidth: 2,
|
||||||
fill: '#e6f7ff'
|
stroke: '#1890ff',
|
||||||
}
|
fill: '#e6f7ff'
|
||||||
|
},
|
||||||
|
// 节点选中状态下的配置
|
||||||
|
select: {
|
||||||
|
lineWidth: 3,
|
||||||
|
stroke: '#1890ff',
|
||||||
|
fill: '#e6f7ff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preRect: {
|
||||||
|
show: true,
|
||||||
|
width: 4,
|
||||||
|
fill: '#40a9ff',
|
||||||
|
radius: 2
|
||||||
|
},
|
||||||
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
|
linkPoints: {
|
||||||
|
top: false,
|
||||||
|
right: false,
|
||||||
|
bottom: false,
|
||||||
|
left: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 3,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#72CC4A',
|
||||||
|
stroke: '#72CC4A'
|
||||||
|
},
|
||||||
|
// 节点中icon配置
|
||||||
|
logoIcon: {
|
||||||
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg',
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
|
},
|
||||||
|
// 节点中表示状态的icon配置
|
||||||
|
stateIcon: {
|
||||||
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: true,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg',
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
|
},
|
||||||
|
// 连接点,默认为左右
|
||||||
|
anchorPoints: [[ 0, 0.5 ], [ 1, 0.5 ]]
|
||||||
},
|
},
|
||||||
|
shapeType: 'modelRect',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, preRect: defaultPreRect } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, preRect: customPreRect } = customOptions;
|
||||||
const { width, height, linkPoints, preRect,
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
logoIcon, stateIcon, ...rectStyle } = style;
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
const keyShape = group.addShape('rect', {
|
const keyShape = group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...style,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height
|
||||||
...rectStyle
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const preRect = deepMix({}, defaultPreRect, customPreRect, cfg.preRect);
|
||||||
const { show: preRectShow, ...preRectStyle } = preRect;
|
const { show: preRectShow, ...preRectStyle } = preRect;
|
||||||
if (preRectShow) {
|
if (preRectShow) {
|
||||||
group.addShape('rect', {
|
group.addShape('rect', {
|
||||||
@ -108,6 +111,26 @@ Shape.registerNode('modelRect', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.drawLogoIcon(cfg, group);
|
||||||
|
|
||||||
|
this.drawStateIcon(cfg, group);
|
||||||
|
|
||||||
|
this.drawLinkPoints(cfg, group);
|
||||||
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制模型矩形左边的logo图标
|
||||||
|
* @param {Object} cfg 数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLogoIcon(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { logoIcon: defaultLogoIcon } = this.options;
|
||||||
|
const { logoIcon: customLogoIcon } = customOptions;
|
||||||
|
const logoIcon = deepMix({}, defaultLogoIcon, customLogoIcon, cfg.logoIcon);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
|
||||||
if (logoIcon.show) {
|
if (logoIcon.show) {
|
||||||
const { width: w, height: h, x, y, ...logoIconStyle } = logoIcon;
|
const { width: w, height: h, x, y, ...logoIconStyle } = logoIcon;
|
||||||
const image = group.addShape('image', {
|
const image = group.addShape('image', {
|
||||||
@ -115,14 +138,25 @@ Shape.registerNode('modelRect', {
|
|||||||
...logoIconStyle,
|
...logoIconStyle,
|
||||||
x: x || -width / 2 + w,
|
x: x || -width / 2 + w,
|
||||||
y: y || -h / 2
|
y: y || -h / 2
|
||||||
// width: w,
|
|
||||||
// height: h
|
|
||||||
},
|
},
|
||||||
className: 'rect-logo-icon'
|
className: 'rect-logo-icon'
|
||||||
});
|
});
|
||||||
|
|
||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制模型矩形右边的状态图标
|
||||||
|
* @param {Object} cfg 数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawStateIcon(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { stateIcon: defaultStateIcon } = this.options;
|
||||||
|
const { stateIcon: customStateIcon } = customOptions;
|
||||||
|
const stateIcon = deepMix({}, defaultStateIcon, customStateIcon, cfg.stateIcon);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
|
||||||
if (stateIcon.show) {
|
if (stateIcon.show) {
|
||||||
const { width: w, height: h, x, y } = stateIcon;
|
const { width: w, height: h, x, y } = stateIcon;
|
||||||
@ -137,21 +171,34 @@ Shape.registerNode('modelRect', {
|
|||||||
|
|
||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const { top, left, right, bottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
const { top, left, right, bottom, size,
|
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
|
||||||
if (left) {
|
if (left) {
|
||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-left'
|
className: 'rect-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,14 +206,12 @@ Shape.registerNode('modelRect', {
|
|||||||
// right circle
|
// right circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: width / 2,
|
x: width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-right'
|
className: 'rect-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,14 +219,12 @@ Shape.registerNode('modelRect', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-top'
|
className: 'rect-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,26 +232,29 @@ Shape.registerNode('modelRect', {
|
|||||||
// bottom circle
|
// bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-bottom'
|
className: 'rect-mark-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return keyShape;
|
|
||||||
},
|
},
|
||||||
drawLabel(cfg, group) {
|
drawLabel(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default || {};
|
const { labelCfg: defaultLabelCfg, logoIcon: defaultLogoIcon } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { labelCfg: customLabelCfg, logoIcon: customLogoIcon } = customOptions;
|
||||||
const labelCfg = deepMix({}, style.labelCfg, cfg.labelCfg);
|
|
||||||
|
const logoIcon = deepMix({}, defaultLogoIcon, customLogoIcon, cfg.logoIcon);
|
||||||
|
|
||||||
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
|
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
|
||||||
let label = null;
|
let label = null;
|
||||||
|
|
||||||
const { logoIcon, width } = style;
|
|
||||||
const { show, width: w } = logoIcon;
|
const { show, width: w } = logoIcon;
|
||||||
let offsetX = -width / 2 + labelCfg.offset;
|
let offsetX = -width / 2 + labelCfg.offset;
|
||||||
|
|
||||||
@ -223,13 +269,13 @@ Shape.registerNode('modelRect', {
|
|||||||
...fontStyle,
|
...fontStyle,
|
||||||
y: -5,
|
y: -5,
|
||||||
x: offsetX,
|
x: offsetX,
|
||||||
text: Util.fittingString(cfg.label, 70, 14)
|
text: Util.fittingString(cfg.label, 100, 14)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
group.addShape('text', {
|
group.addShape('text', {
|
||||||
attrs: {
|
attrs: {
|
||||||
text: Util.fittingString(cfg.description, 80, 12),
|
text: Util.fittingString(cfg.description, 75, 12),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
x: offsetX,
|
x: offsetX,
|
||||||
y: 17,
|
y: 17,
|
||||||
@ -249,40 +295,39 @@ Shape.registerNode('modelRect', {
|
|||||||
}
|
}
|
||||||
return label;
|
return label;
|
||||||
},
|
},
|
||||||
getAnchorPoints() {
|
getAnchorPoints(cfg) {
|
||||||
const defaultOptions = this.options;
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
return defaultOptions.anchorPoints;
|
const { anchorPoints: defaultAnchorPoints } = this.options;
|
||||||
},
|
const { anchorPoints: customAnchorPoints } = customOptions;
|
||||||
getSize(cfg) {
|
const anchorPoints = deepMix({}, defaultAnchorPoints, customAnchorPoints);
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
return anchorPoints;
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { width, height } = style;
|
|
||||||
|
|
||||||
return [ width, height ];
|
|
||||||
},
|
},
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, labelCfg: defaultLabelCfg, preRect: defaultPreRect,
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
logoIcon: defaultLogoIcon, stateIcon: defaultStateIcon } = this.options;
|
||||||
const { width, height, linkPoints, logoIcon, stateIcon,
|
const { style: customStyle, labelCfg: customLabelCfg, preRect: customPreRect,
|
||||||
labelCfg: defaultLabelCfg, ...rectStyle } = style;
|
logoIcon: customLogoIcon, stateIcon: customStateIcon } = customOptions;
|
||||||
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
keyShape.attr({
|
keyShape.attr({
|
||||||
|
...style,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height
|
||||||
...rectStyle
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
// const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
|
|
||||||
|
const logoIcon = deepMix({}, defaultLogoIcon, customLogoIcon, cfg.logoIcon);
|
||||||
|
|
||||||
const { show, width: w } = logoIcon;
|
const { show, width: w } = logoIcon;
|
||||||
|
|
||||||
const { offset, style: fontStyle } = labelCfg;
|
const { offset, style: fontStyle } = labelCfg;
|
||||||
@ -318,7 +363,9 @@ Shape.registerNode('modelRect', {
|
|||||||
|
|
||||||
const preRectShape = group.findByClassName('pre-rect');
|
const preRectShape = group.findByClassName('pre-rect');
|
||||||
if (preRectShape) {
|
if (preRectShape) {
|
||||||
|
const preRect = deepMix({}, defaultPreRect, customPreRect, cfg.preRect);
|
||||||
preRectShape.attr({
|
preRectShape.attr({
|
||||||
|
...preRect,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
height
|
height
|
||||||
@ -327,8 +374,9 @@ Shape.registerNode('modelRect', {
|
|||||||
|
|
||||||
const logoIconShape = group.findByClassName('rect-logo-icon');
|
const logoIconShape = group.findByClassName('rect-logo-icon');
|
||||||
if (logoIconShape) {
|
if (logoIconShape) {
|
||||||
const { width: w, height: h, x, y } = logoIcon;
|
const { width: w, height: h, x, y, ...logoIconStyle } = logoIcon;
|
||||||
logoIconShape.attr({
|
logoIconShape.attr({
|
||||||
|
...logoIconStyle,
|
||||||
x: x || -width / 2 + w,
|
x: x || -width / 2 + w,
|
||||||
y: y || -h / 2,
|
y: y || -h / 2,
|
||||||
width: w,
|
width: w,
|
||||||
@ -338,8 +386,10 @@ Shape.registerNode('modelRect', {
|
|||||||
|
|
||||||
const stateIconShape = group.findByClassName('rect-state-icon');
|
const stateIconShape = group.findByClassName('rect-state-icon');
|
||||||
if (stateIconShape) {
|
if (stateIconShape) {
|
||||||
const { width: w, height: h, x, y } = stateIcon;
|
const stateIcon = deepMix({}, defaultStateIcon, customStateIcon, cfg.stateIcon);
|
||||||
|
const { width: w, height: h, x, y, ...stateIconStyle } = stateIcon;
|
||||||
stateIconShape.attr({
|
stateIconShape.attr({
|
||||||
|
...stateIconStyle,
|
||||||
x: x || width / 2 - w * 2 + 8,
|
x: x || width / 2 - w * 2 + 8,
|
||||||
y: y || -h / 2,
|
y: y || -h / 2,
|
||||||
width: w,
|
width: w,
|
||||||
@ -347,52 +397,69 @@ Shape.registerNode('modelRect', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('rect-anchor-left');
|
const { size: markSize, fill: markFill, stroke: markStroke, lineWidth: borderWidth } = linkPoints;
|
||||||
if (anchorLeft) {
|
|
||||||
anchorLeft.attr({
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
|
const markLeft = group.findByClassName('rect-mark-left');
|
||||||
|
if (markLeft) {
|
||||||
|
markLeft.attr({
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('rect-anchor-right');
|
const markRight = group.findByClassName('rect-mark-right');
|
||||||
if (anchorRight) {
|
if (markRight) {
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
x: width / 2,
|
x: width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('rect-anchor-top');
|
const markTop = group.findByClassName('rect-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorBottom = group.findByClassName('rect-anchor-bottom');
|
const markBottom = group.findByClassName('rect-mark-bottom');
|
||||||
if (anchorBottom) {
|
if (markBottom) {
|
||||||
anchorBottom.attr({
|
markBottom.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,81 +4,100 @@ const deepMix = require('@antv/util/lib/deep-mix');
|
|||||||
Shape.registerNode('rect', {
|
Shape.registerNode('rect', {
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: [ 100, 30 ],
|
||||||
default: {
|
style: {
|
||||||
width: 100,
|
|
||||||
height: 30,
|
|
||||||
radius: 0,
|
radius: 0,
|
||||||
stroke: '#69c0ff',
|
stroke: '#87e8de',
|
||||||
fill: '#e6f7ff',
|
fill: '#36cfc9',
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
fillOpacity: 1,
|
fillOpacity: 1
|
||||||
// 文本样式配置
|
|
||||||
labelCfg: {
|
|
||||||
style: {
|
|
||||||
fill: '#595959',
|
|
||||||
fontSize: 12
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
|
||||||
linkPoints: {
|
|
||||||
top: false,
|
|
||||||
right: false,
|
|
||||||
bottom: false,
|
|
||||||
left: false,
|
|
||||||
// circle的大小
|
|
||||||
size: 3,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#72CC4A',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
|
||||||
// 连接点,默认为左右
|
|
||||||
anchorPoints: [[ 0, 0.5 ], [ 1, 0.5 ]]
|
|
||||||
},
|
},
|
||||||
// hover状态下的配置
|
// 文本样式配置
|
||||||
hover: {
|
labelCfg: {
|
||||||
lineWidth: 2,
|
style: {
|
||||||
stroke: '#1890ff'
|
fill: '#595959',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 节点选中状态下的配置
|
stateStyles: {
|
||||||
select: {
|
// hover状态下的配置
|
||||||
lineWidth: 3,
|
hover: {
|
||||||
stroke: '#1890ff',
|
lineWidth: 2,
|
||||||
fill: '#91d5ff'
|
stroke: '#1890ff'
|
||||||
}
|
},
|
||||||
|
// 节点选中状态下的配置
|
||||||
|
select: {
|
||||||
|
lineWidth: 3,
|
||||||
|
stroke: '#1890ff',
|
||||||
|
fill: '#91d5ff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
|
linkPoints: {
|
||||||
|
top: false,
|
||||||
|
right: false,
|
||||||
|
bottom: false,
|
||||||
|
left: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 3,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#72CC4A',
|
||||||
|
stroke: '#72CC4A'
|
||||||
|
},
|
||||||
|
// 连接点,默认为左右
|
||||||
|
markPoints: [[ 0, 0.5 ], [ 1, 0.5 ]]
|
||||||
},
|
},
|
||||||
shapeType: 'rect',
|
shapeType: 'rect',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle } = customOptions;
|
||||||
const { width, height, linkPoints, ...rectStyle } = style;
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
const keyShape = group.addShape('rect', {
|
const keyShape = group.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...style,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height
|
||||||
...rectStyle
|
|
||||||
},
|
},
|
||||||
className: 'rect-keyShape'
|
className: 'rect-keyShape'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { top, left, right, bottom, size,
|
this.drawLinkPoints(cfg, group);
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const { top, left, right, bottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
if (left) {
|
if (left) {
|
||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-left'
|
className: 'rect-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,14 +105,12 @@ Shape.registerNode('rect', {
|
|||||||
// right circle
|
// right circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: width / 2,
|
x: width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-right'
|
className: 'rect-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,14 +118,12 @@ Shape.registerNode('rect', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-top'
|
className: 'rect-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,35 +131,30 @@ Shape.registerNode('rect', {
|
|||||||
// bottom circle
|
// bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'rect-anchor-bottom'
|
className: 'rect-mark-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return keyShape;
|
|
||||||
},
|
},
|
||||||
getAnchorPoints() {
|
getAnchorPoints(cfg) {
|
||||||
const defaultOptions = this.options;
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
return defaultOptions.anchorPoints;
|
const { anchorPoints: defaultAnchorPoints } = this.options;
|
||||||
},
|
const { anchorPoints: customAnchorPoints } = customOptions;
|
||||||
getSize(cfg) {
|
const anchorPoints = deepMix({}, defaultAnchorPoints, customAnchorPoints);
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
return anchorPoints;
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { width, height } = style;
|
|
||||||
|
|
||||||
return [ width, height ];
|
|
||||||
},
|
},
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, labelCfg: defaultLabelCfg } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, labelCfg: customLabelCfg } = customOptions;
|
||||||
const { width, height, linkPoints, labelCfg: defaultLabelCfg, ...rectStyle } = style;
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const width = size[0];
|
||||||
|
const height = size[1];
|
||||||
|
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
keyShape.attr({
|
keyShape.attr({
|
||||||
@ -152,12 +162,12 @@ Shape.registerNode('rect', {
|
|||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
...rectStyle
|
...style
|
||||||
});
|
});
|
||||||
|
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
if (text) {
|
if (text) {
|
||||||
@ -165,53 +175,69 @@ Shape.registerNode('rect', {
|
|||||||
...labelStyle
|
...labelStyle
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
const { size: markSize, fill: markFill, stroke: markStroke, lineWidth: borderWidth } = linkPoints;
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('rect-anchor-left');
|
const size = this.getSize(cfg);
|
||||||
if (anchorLeft) {
|
const width = size[0];
|
||||||
anchorLeft.attr({
|
const height = size[1];
|
||||||
|
|
||||||
|
const markLeft = group.findByClassName('rect-mark-left');
|
||||||
|
if (markLeft) {
|
||||||
|
markLeft.attr({
|
||||||
x: -width / 2,
|
x: -width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('rect-anchor-right');
|
const markRight = group.findByClassName('rect-mark-right');
|
||||||
if (anchorRight) {
|
if (markRight) {
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
x: width / 2,
|
x: width / 2,
|
||||||
y: 0,
|
y: 0,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('rect-anchor-top');
|
const markTop = group.findByClassName('rect-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: -height / 2,
|
y: -height / 2,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorBottom = group.findByClassName('rect-anchor-bottom');
|
const markBottom = group.findByClassName('rect-mark-bottom');
|
||||||
if (anchorBottom) {
|
if (markBottom) {
|
||||||
anchorBottom.attr({
|
markBottom.attr({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: height / 2,
|
y: height / 2,
|
||||||
r: size,
|
r: markSize,
|
||||||
fill: anchorFill,
|
fill: markFill,
|
||||||
stroke: anchorStroke,
|
stroke: markStroke,
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,69 +1,71 @@
|
|||||||
const Shape = require('../shape');
|
const Shape = require('../shape');
|
||||||
const deepMix = require('@antv/util/lib/deep-mix');
|
const deepMix = require('@antv/util/lib/deep-mix');
|
||||||
|
|
||||||
// 菱形shape
|
// 五角星shape
|
||||||
Shape.registerNode('star', {
|
Shape.registerNode('star', {
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: [ 20, 60 ],
|
||||||
default: {
|
style: {
|
||||||
outerR: 60,
|
stroke: '#87e8de',
|
||||||
innerR: 20,
|
fill: '#36cfc9',
|
||||||
stroke: '#69c0ff',
|
lineWidth: 1
|
||||||
fill: '#e6f7ff',
|
},
|
||||||
lineWidth: 1,
|
// 文本样式配置
|
||||||
// 文本样式配置
|
labelCfg: {
|
||||||
labelCfg: {
|
style: {
|
||||||
style: {
|
fill: '#595959'
|
||||||
fill: '#595959'
|
|
||||||
},
|
|
||||||
offset: 0
|
|
||||||
},
|
},
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
offset: 0
|
||||||
linkPoints: {
|
},
|
||||||
top: false,
|
stateStyles: {
|
||||||
right: false,
|
// 鼠标hover状态下的配置
|
||||||
left: false,
|
hover: {
|
||||||
leftBottom: false,
|
lineWidth: 3
|
||||||
rightBottom: false,
|
|
||||||
// circle的大小
|
|
||||||
size: 3,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#fff',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
},
|
||||||
// 节点中icon配置
|
// 选中节点状态下的配置
|
||||||
icon: {
|
select: {
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
lineWidth: 5
|
||||||
show: false,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
|
||||||
width: 16,
|
|
||||||
height: 16
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 鼠标hover状态下的配置
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
hover: {
|
linkPoints: {
|
||||||
lineWidth: 3
|
top: false,
|
||||||
|
right: false,
|
||||||
|
left: false,
|
||||||
|
leftBottom: false,
|
||||||
|
rightBottom: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 3,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#72CC4A'
|
||||||
},
|
},
|
||||||
// 选中节点状态下的配置
|
// 节点中icon配置
|
||||||
select: {
|
icon: {
|
||||||
lineWidth: 5
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: false,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shapeType: 'star',
|
shapeType: 'star',
|
||||||
// 文本位置
|
// 文本位置
|
||||||
labelPosition: 'center',
|
labelPosition: 'center',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, icon: defaultIcon } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon } = customOptions;
|
||||||
const { icon, linkPoints, outerR, ...starStyle } = style;
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
|
||||||
const path = this.getPath(cfg);
|
const path = this.getPath(cfg);
|
||||||
const keyShape = group.addShape('path', {
|
const keyShape = group.addShape('path', {
|
||||||
attrs: {
|
attrs: {
|
||||||
path,
|
path,
|
||||||
...starStyle
|
...style
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,8 +83,25 @@ Shape.registerNode('star', {
|
|||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top, left, right, leftBottom, rightBottom, size,
|
this.drawLinkPoints(cfg, group);
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
|
||||||
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const { top, left, right, leftBottom, rightBottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const outerR = size[1];
|
||||||
|
|
||||||
if (right) {
|
if (right) {
|
||||||
// right circle
|
// right circle
|
||||||
@ -92,14 +111,12 @@ Shape.registerNode('star', {
|
|||||||
|
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: x1,
|
x: x1,
|
||||||
y: -y1,
|
y: -y1,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'star-anchor-right'
|
className: 'star-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,14 +128,12 @@ Shape.registerNode('star', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: x1,
|
x: x1,
|
||||||
y: -y1,
|
y: -y1,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'star-anchor-top'
|
className: 'star-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,14 +145,12 @@ Shape.registerNode('star', {
|
|||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: x1,
|
x: x1,
|
||||||
y: -y1,
|
y: -y1,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'star-anchor-left'
|
className: 'star-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,14 +162,12 @@ Shape.registerNode('star', {
|
|||||||
// left bottom circle
|
// left bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: x1,
|
x: x1,
|
||||||
y: -y1,
|
y: -y1,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'star-anchor-left-bottom'
|
className: 'star-mark-left-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,27 +179,19 @@ Shape.registerNode('star', {
|
|||||||
// left bottom circle
|
// left bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: x1,
|
x: x1,
|
||||||
y: -y1,
|
y: -y1,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'star-anchor-right-bottom'
|
className: 'star-mark-right-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyShape;
|
|
||||||
},
|
|
||||||
shouldUpdate() {
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
getPath(cfg) {
|
getPath(cfg) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const size = this.getSize(cfg);
|
||||||
const defaultConfig = customStyle.default;
|
const innerR = size[0];
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const outerR = size[1];
|
||||||
const { outerR, innerR } = style;
|
|
||||||
const path = [];
|
const path = [];
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
const x1 = Math.cos((18 + 72 * i) / 180 * Math.PI) * outerR;
|
const x1 = Math.cos((18 + 72 * i) / 180 * Math.PI) * outerR;
|
||||||
@ -216,36 +219,22 @@ Shape.registerNode('star', {
|
|||||||
|
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 获取节点宽高
|
|
||||||
* @internal 返回节点的大小,以 [width, height] 的方式维护
|
|
||||||
* @param {Object} cfg 节点的配置项
|
|
||||||
* @return {Array} 宽高
|
|
||||||
*/
|
|
||||||
getSize(cfg) {
|
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { outerR } = style;
|
|
||||||
|
|
||||||
return [ outerR, outerR ];
|
|
||||||
},
|
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default || {};
|
const { style: defaultStyle, icon: defaultIcon, labelCfg: defaultLabelCfg } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon, labelCfg: customLabelCfg } = customOptions;
|
||||||
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
|
||||||
const { icon, linkPoints, outerR,
|
|
||||||
labelCfg: defaultLabelCfg, ...starStyle } = style;
|
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
const path = this.getPath(cfg);
|
const path = this.getPath(cfg);
|
||||||
keyShape.attr({
|
keyShape.attr({
|
||||||
path,
|
path,
|
||||||
...starStyle
|
...style
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
|
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
@ -265,84 +254,90 @@ Shape.registerNode('star', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('star-anchor-right');
|
const { size: markSize, ...markStyle } = linkPoints;
|
||||||
if (anchorRight) {
|
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const outerR = size[1];
|
||||||
|
|
||||||
|
const markRight = group.findByClassName('star-mark-right');
|
||||||
|
if (markRight) {
|
||||||
const x = Math.cos((18 + 72 * 0) / 180 * Math.PI) * outerR;
|
const x = Math.cos((18 + 72 * 0) / 180 * Math.PI) * outerR;
|
||||||
const y = Math.sin((18 + 72 * 0) / 180 * Math.PI) * outerR;
|
const y = Math.sin((18 + 72 * 0) / 180 * Math.PI) * outerR;
|
||||||
|
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
|
...markStyle,
|
||||||
x,
|
x,
|
||||||
y: -y,
|
y: -y,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('star-anchor-top');
|
const markTop = group.findByClassName('star-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
const x = Math.cos((18 + 72 * 1) / 180 * Math.PI) * outerR;
|
const x = Math.cos((18 + 72 * 1) / 180 * Math.PI) * outerR;
|
||||||
const y = Math.sin((18 + 72 * 1) / 180 * Math.PI) * outerR;
|
const y = Math.sin((18 + 72 * 1) / 180 * Math.PI) * outerR;
|
||||||
|
|
||||||
// top circle
|
// top circle
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
|
...markStyle,
|
||||||
x,
|
x,
|
||||||
y: -y,
|
y: -y,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('star-anchor-left');
|
const markLeft = group.findByClassName('star-mark-left');
|
||||||
if (anchorLeft) {
|
if (markLeft) {
|
||||||
const x = Math.cos((18 + 72 * 2) / 180 * Math.PI) * outerR;
|
const x = Math.cos((18 + 72 * 2) / 180 * Math.PI) * outerR;
|
||||||
const y = Math.sin((18 + 72 * 2) / 180 * Math.PI) * outerR;
|
const y = Math.sin((18 + 72 * 2) / 180 * Math.PI) * outerR;
|
||||||
|
|
||||||
// left circle
|
// left circle
|
||||||
anchorLeft.attr({
|
markLeft.attr({
|
||||||
|
...markStyle,
|
||||||
x,
|
x,
|
||||||
y: -y,
|
y: -y,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorLeftBottom = group.findByClassName('star-anchor-left-bottom');
|
const markLeftBottom = group.findByClassName('star-mark-left-bottom');
|
||||||
if (anchorLeftBottom) {
|
if (markLeftBottom) {
|
||||||
const x = Math.cos((18 + 72 * 3) / 180 * Math.PI) * outerR;
|
const x = Math.cos((18 + 72 * 3) / 180 * Math.PI) * outerR;
|
||||||
const y = Math.sin((18 + 72 * 3) / 180 * Math.PI) * outerR;
|
const y = Math.sin((18 + 72 * 3) / 180 * Math.PI) * outerR;
|
||||||
|
|
||||||
// bottom circle
|
// bottom circle
|
||||||
anchorLeftBottom.attr({
|
markLeftBottom.attr({
|
||||||
|
...markStyle,
|
||||||
x,
|
x,
|
||||||
y: -y,
|
y: -y,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRightBottom = group.findByClassName('star-anchor-right-bottom');
|
const markRightBottom = group.findByClassName('star-mark-right-bottom');
|
||||||
if (anchorRightBottom) {
|
if (markRightBottom) {
|
||||||
const x = Math.cos((18 + 72 * 4) / 180 * Math.PI) * outerR;
|
const x = Math.cos((18 + 72 * 4) / 180 * Math.PI) * outerR;
|
||||||
const y = Math.sin((18 + 72 * 4) / 180 * Math.PI) * outerR;
|
const y = Math.sin((18 + 72 * 4) / 180 * Math.PI) * outerR;
|
||||||
|
|
||||||
// bottom circle
|
// bottom circle
|
||||||
anchorRightBottom.attr({
|
markRightBottom.attr({
|
||||||
|
...markStyle,
|
||||||
x,
|
x,
|
||||||
y: -y,
|
y: -y,
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,66 +5,69 @@ const deepMix = require('@antv/util/lib/deep-mix');
|
|||||||
Shape.registerNode('triangle', {
|
Shape.registerNode('triangle', {
|
||||||
// 自定义节点时的配置
|
// 自定义节点时的配置
|
||||||
options: {
|
options: {
|
||||||
// 默认配置
|
size: 40,
|
||||||
default: {
|
direction: 'up',
|
||||||
len: 40,
|
style: {
|
||||||
direction: 'up',
|
stroke: '#87e8de',
|
||||||
stroke: '#69c0ff',
|
fill: '#36cfc9',
|
||||||
fill: '#e6f7ff',
|
lineWidth: 1
|
||||||
lineWidth: 1,
|
},
|
||||||
// 文本样式配置
|
// 文本样式配置
|
||||||
labelCfg: {
|
labelCfg: {
|
||||||
style: {
|
style: {
|
||||||
fill: '#595959'
|
fill: '#595959'
|
||||||
},
|
|
||||||
offset: 15
|
|
||||||
},
|
},
|
||||||
// 节点上左右上下四个方向上的链接circle配置
|
offset: 15
|
||||||
linkPoints: {
|
},
|
||||||
top: false,
|
stateStyles: {
|
||||||
right: false,
|
// 鼠标hover状态下的配置
|
||||||
bottom: false,
|
hover: {
|
||||||
left: false,
|
lineWidth: 3
|
||||||
// circle的大小
|
|
||||||
size: 5,
|
|
||||||
lineWidth: 1,
|
|
||||||
fill: '#fff',
|
|
||||||
stroke: '#72CC4A'
|
|
||||||
},
|
},
|
||||||
// 节点中icon配置
|
// 选中节点状态下的配置
|
||||||
icon: {
|
select: {
|
||||||
// 是否显示icon,值为 false 则不渲染icon
|
lineWidth: 5
|
||||||
show: false,
|
|
||||||
// icon的地址,字符串类型
|
|
||||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
offset: 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 鼠标hover状态下的配置
|
// 节点上左右上下四个方向上的链接circle配置
|
||||||
hover: {
|
linkPoints: {
|
||||||
lineWidth: 3
|
top: false,
|
||||||
|
right: false,
|
||||||
|
bottom: false,
|
||||||
|
left: false,
|
||||||
|
// circle的大小
|
||||||
|
size: 5,
|
||||||
|
lineWidth: 1,
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#72CC4A'
|
||||||
},
|
},
|
||||||
// 选中节点状态下的配置
|
// 节点中icon配置
|
||||||
select: {
|
icon: {
|
||||||
lineWidth: 5
|
// 是否显示icon,值为 false 则不渲染icon
|
||||||
|
show: false,
|
||||||
|
// icon的地址,字符串类型
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
offset: 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shapeType: 'circle',
|
shapeType: 'triangle',
|
||||||
// 文本位置
|
// 文本位置
|
||||||
labelPosition: 'bottom',
|
labelPosition: 'bottom',
|
||||||
drawShape(cfg, group) {
|
drawShape(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { style: defaultStyle, icon: defaultIcon, direction: defaultDirection } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon, direction: customDirection } = customOptions;
|
||||||
const { icon, linkPoints,
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
direction, len, ...triangleStyle } = style;
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
|
|
||||||
|
const direction = cfg.direction || customDirection || defaultDirection;
|
||||||
const path = this.getPath(cfg);
|
const path = this.getPath(cfg);
|
||||||
const keyShape = group.addShape('path', {
|
const keyShape = group.addShape('path', {
|
||||||
attrs: {
|
attrs: {
|
||||||
path,
|
path,
|
||||||
...triangleStyle
|
...style
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,8 +93,28 @@ Shape.registerNode('triangle', {
|
|||||||
image.set('capture', false);
|
image.set('capture', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top, left, right, bottom, size,
|
this.drawLinkPoints(cfg, group);
|
||||||
fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
|
||||||
|
return keyShape;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制节点上的LinkPoints
|
||||||
|
* @param {Object} cfg data数据配置项
|
||||||
|
* @param {Group} group Group实例
|
||||||
|
*/
|
||||||
|
drawLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints, direction: defaultDirection } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints, direction: customDirection } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
|
const direction = cfg.direction || customDirection || defaultDirection;
|
||||||
|
|
||||||
|
const { top, left, right, bottom, size: markSize,
|
||||||
|
...markStyle } = linkPoints;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const len = size[0];
|
||||||
|
|
||||||
if (left) {
|
if (left) {
|
||||||
// up down left right 四个方向的坐标均不相同
|
// up down left right 四个方向的坐标均不相同
|
||||||
let leftPos = null;
|
let leftPos = null;
|
||||||
@ -109,14 +132,12 @@ Shape.registerNode('triangle', {
|
|||||||
// left circle
|
// left circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: leftPos[0],
|
x: leftPos[0],
|
||||||
y: leftPos[1],
|
y: leftPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'triangle-anchor-left'
|
className: 'triangle-mark-left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,14 +159,12 @@ Shape.registerNode('triangle', {
|
|||||||
if (rightPos) {
|
if (rightPos) {
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: rightPos[0],
|
x: rightPos[0],
|
||||||
y: rightPos[1],
|
y: rightPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'triangle-anchor-right'
|
className: 'triangle-mark-right'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,14 +186,12 @@ Shape.registerNode('triangle', {
|
|||||||
// top circle
|
// top circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: topPos[0],
|
x: topPos[0],
|
||||||
y: topPos[1],
|
y: topPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'triangle-anchor-top'
|
className: 'triangle-mark-top'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,28 +213,25 @@ Shape.registerNode('triangle', {
|
|||||||
// bottom circle
|
// bottom circle
|
||||||
group.addShape('circle', {
|
group.addShape('circle', {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
...markStyle,
|
||||||
x: bottomPos[0],
|
x: bottomPos[0],
|
||||||
y: bottomPos[1],
|
y: bottomPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
},
|
},
|
||||||
className: 'triangle-anchor-bottom'
|
className: 'triangle-mark-bottom'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyShape;
|
|
||||||
},
|
|
||||||
shouldUpdate() {
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
getPath(cfg) {
|
getPath(cfg) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { direction: defaultDirection } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { direction: customDirection } = customOptions;
|
||||||
const { len, direction } = style;
|
|
||||||
|
const direction = cfg.direction || customDirection || defaultDirection;
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const len = size[0];
|
||||||
|
|
||||||
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
||||||
const r = len * Math.sin((1 / 3) * Math.PI);
|
const r = len * Math.sin((1 / 3) * Math.PI);
|
||||||
let path = [
|
let path = [
|
||||||
@ -251,36 +265,21 @@ Shape.registerNode('triangle', {
|
|||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 获取节点宽高
|
|
||||||
* @internal 返回节点的大小,以 [width, height] 的方式维护
|
|
||||||
* @param {Object} cfg 节点的配置项
|
|
||||||
* @return {Array} 宽高
|
|
||||||
*/
|
|
||||||
getSize(cfg) {
|
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
|
||||||
const defaultConfig = customStyle.default;
|
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
|
||||||
const { len } = style;
|
|
||||||
|
|
||||||
return [ len, len ];
|
|
||||||
},
|
|
||||||
update(cfg, item) {
|
update(cfg, item) {
|
||||||
const group = item.getContainer();
|
const group = item.getContainer();
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default || {};
|
const { style: defaultStyle, icon: defaultIcon, labelCfg: defaultLabelCfg } = this.options;
|
||||||
const style = deepMix({}, this.options.default, defaultConfig, cfg.style);
|
const { style: customStyle, icon: customIcon, labelCfg: customLabelCfg } = customOptions;
|
||||||
|
const style = deepMix({}, defaultStyle, customStyle, cfg.style);
|
||||||
const { icon, linkPoints, direction, len,
|
const icon = deepMix({}, defaultIcon, customIcon, cfg.icon);
|
||||||
labelCfg: defaultLabelCfg, ...triangleStyle } = style;
|
|
||||||
const keyShape = item.get('keyShape');
|
const keyShape = item.get('keyShape');
|
||||||
const path = this.getPath(cfg);
|
const path = this.getPath(cfg);
|
||||||
keyShape.attr({
|
keyShape.attr({
|
||||||
path,
|
path,
|
||||||
...triangleStyle
|
...style
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelCfg = deepMix({}, defaultLabelCfg, cfg.labelCfg);
|
const labelCfg = deepMix({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
|
|
||||||
const text = group.findByClassName('node-label');
|
const text = group.findByClassName('node-label');
|
||||||
@ -300,10 +299,29 @@ Shape.registerNode('triangle', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { size, fill: anchorFill, stroke: anchorStroke, lineWidth: borderWidth } = linkPoints;
|
this.updateLinkPoints(cfg, group);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新linkPoints
|
||||||
|
* @param {Object} cfg 节点数据配置项
|
||||||
|
* @param {Group} group Item所在的group
|
||||||
|
*/
|
||||||
|
updateLinkPoints(cfg, group) {
|
||||||
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
|
const { linkPoints: defaultLinkPoints, direction: defaultDirection } = this.options;
|
||||||
|
const { linkPoints: customLinkPoints, direction: customDirection } = customOptions;
|
||||||
|
const linkPoints = deepMix({}, defaultLinkPoints, customLinkPoints, cfg.linkPoints);
|
||||||
|
|
||||||
const anchorLeft = group.findByClassName('triangle-anchor-left');
|
const direction = cfg.direction || customDirection || defaultDirection;
|
||||||
if (anchorLeft) {
|
|
||||||
|
|
||||||
|
const { size: markSize, ...markStyle } = linkPoints;
|
||||||
|
|
||||||
|
const size = this.getSize(cfg);
|
||||||
|
const len = size[0];
|
||||||
|
|
||||||
|
const markLeft = group.findByClassName('triangle-mark-left');
|
||||||
|
if (markLeft) {
|
||||||
let leftPos = null;
|
let leftPos = null;
|
||||||
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
||||||
const r = len * Math.sin((1 / 3) * Math.PI);
|
const r = len * Math.sin((1 / 3) * Math.PI);
|
||||||
@ -317,19 +335,17 @@ Shape.registerNode('triangle', {
|
|||||||
|
|
||||||
if (leftPos) {
|
if (leftPos) {
|
||||||
// left circle
|
// left circle
|
||||||
anchorLeft.attr({
|
markLeft.attr({
|
||||||
|
...markStyle,
|
||||||
x: leftPos[0],
|
x: leftPos[0],
|
||||||
y: leftPos[1],
|
y: leftPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorRight = group.findByClassName('triangle-anchor-right');
|
const markRight = group.findByClassName('triangle-mark-right');
|
||||||
if (anchorRight) {
|
if (markRight) {
|
||||||
let rightPos = null;
|
let rightPos = null;
|
||||||
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
||||||
const r = len * Math.sin((1 / 3) * Math.PI);
|
const r = len * Math.sin((1 / 3) * Math.PI);
|
||||||
@ -342,19 +358,17 @@ Shape.registerNode('triangle', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rightPos) {
|
if (rightPos) {
|
||||||
anchorRight.attr({
|
markRight.attr({
|
||||||
|
...markStyle,
|
||||||
x: rightPos[0],
|
x: rightPos[0],
|
||||||
y: rightPos[1],
|
y: rightPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorTop = group.findByClassName('triangle-anchor-top');
|
const markTop = group.findByClassName('triangle-mark-top');
|
||||||
if (anchorTop) {
|
if (markTop) {
|
||||||
let topPos = null;
|
let topPos = null;
|
||||||
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
||||||
const r = len * Math.sin((1 / 3) * Math.PI);
|
const r = len * Math.sin((1 / 3) * Math.PI);
|
||||||
@ -368,19 +382,17 @@ Shape.registerNode('triangle', {
|
|||||||
|
|
||||||
if (topPos) {
|
if (topPos) {
|
||||||
// top circle
|
// top circle
|
||||||
anchorTop.attr({
|
markTop.attr({
|
||||||
|
...markStyle,
|
||||||
x: topPos[0],
|
x: topPos[0],
|
||||||
y: topPos[1],
|
y: topPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorBottom = group.findByClassName('triangle-anchor-bottom');
|
const markBottom = group.findByClassName('triangle-mark-bottom');
|
||||||
if (anchorBottom) {
|
if (markBottom) {
|
||||||
let bottomPos = null;
|
let bottomPos = null;
|
||||||
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
const diffY = len * Math.sin((1 / 3) * Math.PI);
|
||||||
const r = len * Math.sin((1 / 3) * Math.PI);
|
const r = len * Math.sin((1 / 3) * Math.PI);
|
||||||
@ -395,13 +407,11 @@ Shape.registerNode('triangle', {
|
|||||||
|
|
||||||
if (bottomPos) {
|
if (bottomPos) {
|
||||||
// bottom circle
|
// bottom circle
|
||||||
anchorBottom.attr({
|
markBottom.attr({
|
||||||
|
...markStyle,
|
||||||
x: bottomPos[0],
|
x: bottomPos[0],
|
||||||
y: bottomPos[1],
|
y: bottomPos[1],
|
||||||
r: size,
|
r: markSize
|
||||||
fill: anchorFill,
|
|
||||||
stroke: anchorStroke,
|
|
||||||
lineWidth: borderWidth
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,11 @@ const SingleShape = {
|
|||||||
|
|
||||||
},
|
},
|
||||||
drawLabel(cfg, group) {
|
drawLabel(cfg, group) {
|
||||||
const customStyle = this.getCustomConfig(cfg) || {};
|
const customOptions = this.getCustomConfig(cfg) || {};
|
||||||
const defaultConfig = customStyle.default;
|
const { labelCfg: defaultLabelCfg } = this.options;
|
||||||
const style = merge({}, this.options.default, defaultConfig);
|
const { labelCfg: customLabelCfg } = customOptions;
|
||||||
const labelCfg = merge({}, style.labelCfg, cfg.labelCfg);
|
|
||||||
|
const labelCfg = merge({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
|
||||||
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
|
||||||
const label = group.addShape('text', {
|
const label = group.addShape('text', {
|
||||||
attrs: labelStyle
|
attrs: labelStyle
|
||||||
@ -154,15 +155,23 @@ const SingleShape = {
|
|||||||
* @return {object} 样式
|
* @return {object} 样式
|
||||||
*/
|
*/
|
||||||
getStateStyle(name, value, item) {
|
getStateStyle(name, value, item) {
|
||||||
const defaultStyle = this.options;
|
|
||||||
const model = item.getModel();
|
const model = item.getModel();
|
||||||
const customStyle = this.getCustomConfig(model) || {};
|
const customOptions = this.getCustomConfig(model) || {};
|
||||||
|
const { style: defaultStyle, stateStyles: defaultStateStyle } = this.options;
|
||||||
|
const { style: customStyle, stateStyles: customStateStyle } = customOptions;
|
||||||
|
|
||||||
|
const stateStyles = merge({}, defaultStateStyle, customStateStyle);
|
||||||
|
let currentStateStyle = defaultStyle;
|
||||||
|
|
||||||
|
if (stateStyles[name]) {
|
||||||
|
currentStateStyle = stateStyles[name];
|
||||||
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
return merge({}, get(defaultStyle, name, {}), get(customStyle, name, {}));
|
return currentStateStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
const states = item.getStates();
|
const states = item.getStates();
|
||||||
const resultStyle = merge({}, get(defaultStyle, 'default', {}), get(customStyle, 'default', {}));
|
const resultStyle = merge({}, defaultStyle, customStyle);
|
||||||
const style = cloneDeep(resultStyle);
|
const style = cloneDeep(resultStyle);
|
||||||
states.forEach(state => {
|
states.forEach(state => {
|
||||||
merge(style, get(defaultStyle, state, {}), get(customStyle, state, {}));
|
merge(style, get(defaultStyle, state, {}), get(customStyle, state, {}));
|
||||||
|
Loading…
Reference in New Issue
Block a user