前面一篇写了使用jointjs实现自动布局和拖拽缩放,这篇记录一下自定义图形。
首先jointjs内置的图形有很多,文档已经列出来了:
但是有时候这些图形满足不了我们的需求,就需要我们自己去绘制自己想要的图形。例如完成下面图的效果:
拆解一下每个基本图案,实际上是 一个圆形图片 + 一个圆角矩形 + 文字。
- 代码基本结构跟之前没有太大区别,原始节点数据新加了几个属性。
/** 原始数据:节点 */
nodes: [
{ id: 1, label: '阿巴', img: require('../../assets/img/pic-1.jpg') },
{ id: 2, label: '暴富', img: require('../../assets/img/pic-2.jpg') },
{ id: 3, label: '小熊', img: require('../../assets/img/pic-3.jpg') }
],
/** 原始数据:连线 */
links: [
{ from: 1, to: 2 },
{ from: 1, to: 3 }
]
- 在自己的某个目录创建一个js文件,在这个文件内写自定义图形的代码。我这里主要使用joint.shapes.basic.Generic.extend方法,这个方法返回一个创建好的图形的对象。
文档内也有其他方法定义自定义图形的方法,例如:joint.dia.Element.define(),从头开始创建图元子类型,也可以继承某种标准类型,创建某标准类型的子类型,例如:joint.shapes.standard.Rectangle.define()。
文档:https://resources.jointjs.com/tutorial/custom-elements
-
首先,使用joint.shapes.basic.Generic.extend自定义图案的基本代码是下面的这些:
let option = { /** 一些自定义配置:比如大小,样式等 */ }; joint.shapes.basic.Generic.extend({ /** markup配置项:要渲染的svg结构的模板 */ markup: '', /** 这是设置默认属性,其中的defaultsDeep我理解为类似于Object.assign的东西,后面我贴下专业解释 */ defaults: joint.util.defaultsDeep(option, joint.shapes.basic.Generic.prototype.defaults), /** 初始化函数,on里面监听某些属性的变化,比如监听label的变化就是change:label, 监听attrs的变化就是change:attrs,可以多个,空格分隔即可,监听变化后调用update函数更新内容 */ initialize: function() { this.on( 'change:label', function() { this.updateRectangles(); }, this ); this.updateRectangles(); joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); }, updateRectangles() { /** 这写更新图形的代码 */ } });
-
第一个markup配置,我们要渲染 一个圆形图片 + 一个圆角矩形 + 文字;
/** * 需要有一个这种外壳,看名字感觉是基于这个主体元素进行缩放和旋转(猜的) * <g class="rotatable"> * <g class="scalable"> * 这里放主体的一个承载元素,我放了一个rect作为最外层 * </g> * 要渲染的内容放在这里 * </g> * 如下: * 由于我选的图片是正方形的,所以做了一个圆边 */ markup: `<g class="rotatable"> <g class="scalable"> <rect class="body" /> </g> <g> <rect id="image-box" /> <clipPath id="clip"> <use xlink:href="#image-box" /> </clipPath> <image class="avatar" clip-path="url(#clip)" /> </g> <g> <rect class="name-bg" /> <text class="name"></text> </g> </g>`,
-
其次搞一下option给他一个好看的样式。
let option = { /** 这里必须有type,相当于你要在哪一个类型下创建一个叫什么的子类型 */ type: 'basic.customShape', /** 整体的尺寸大小 */ size: { width: 80, height: 114 }, /** attrs里面通过选择器给相应元素配置样式 */ attrs: { '.body': { width: 80, height: 114, fill: 'none' }, '#image-box': { width: 80, height: 80, rx: '50%', ry: '50%', }, '.avatar': { width: 80, height: 80, }, '.name-bg': { width: 50, height: 24, rx: 12, ry: 12, fill: '#ffcdcd', /** refX和refY是相对于父元素在x和y方向的偏移量 */ refX: 15, refY: 90 }, '.name': { fill: '#000', /** ref可以指定继承于哪个元素,这决定了refX和refY基于谁进行偏移 */ ref: '.name-bg', /** 文字居中的配置 */ textVerticalAnchor: 'middle', textAnchor: 'middle', refX: '50%', refY: '50%' } } };
-
initialize里面监听内容不变,接着写updateRectangles函数
updateRectangles() { /** 通过this.get(属性名)获取对应属性值 */ const attrs = this.get('attrs'); const rect = { label: this.get('label'), image: this.get('image') }; /** 给attrs里面的属性赋值 */ attrs['.name'].text = rect.label; attrs['.avatar']['xlink:href'] = rect.image; }
在接收joint.shapes.basic.Generic.extend这个方法的返回时,可以直接在joint.shapes里面创建一个新名称:
joint.shapes.basic.customShape = joint.shapes.basic.Generic.extend({});
也可以随便赋值给一个变量myShape,然后export出去;
在vue文件引入这个js后,在创建节点时,把图形函数名换成customShape就可以了,例如:
let node = new joint.shapes.basic.customShape({...});
或者
let node = new myShape({...});
- 最后完整的js代码:
import * as joint from 'jointjs';
let option = {
type: 'basic.customShape',
size: {
width: 80,
height: 114
},
attrs: {
'.body': {
width: 80,
height: 114,
fill: 'none'
},
'#image-box': {
width: 80,
height: 80,
rx: '50%',
ry: '50%',
},
'.avatar': {
width: 80,
height: 80,
},
'.name-bg': {
width: 50,
height: 24,
rx: 12,
ry: 12,
fill: '#ffcdcd',
refX: 15,
refY: 90
},
'.name': {
fill: '#000',
ref: '.name-bg',
textVerticalAnchor: 'middle',
textAnchor: 'middle',
refX: '50%',
refY: '50%'
}
}
};
let customShape = joint.shapes.basic.Generic.extend({
markup: `<g class="rotatable">
<g class="scalable">
<rect class="body" />
</g>
<g>
<rect id="image-box" />
<clipPath id="clip">
<use xlink:href="#image-box" />
</clipPath>
<image class="avatar" clip-path="url(#clip)" />
</g>
<g>
<rect class="name-bg" />
<text class="name"></text>
</g>
</g>`,
defaults: joint.util.defaultsDeep(option, joint.shapes.basic.Generic.prototype.defaults),
initialize: function() {
this.on(
'change:label',
function() {
this.updateRectangles();
},
this
);
this.updateRectangles();
joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments);
},
updateRectangles() {
const attrs = this.get('attrs');
const rect = { label: this.get('label'), image: this.get('image') };
attrs['.name'].text = rect.label;
attrs['.avatar']['xlink:href'] = rect.image;
}
});
export default customShape;
对于配置中defaults: joint.util.defaultsDeep(option, joint.shapes.basic.Generic.prototype.defaults)的defaultsDeep方法