1、普通任务节点是流程里面的最常用的节点,这种节点是rect形状
需求:
- 普通节点可以进行拖拽
- 普通节点可以进行大小变化
- 普通节点可以拖拽出线条,跟其他节点连接
第一次这么深入的玩svg和d3 还是走了蛮多冤枉路,废话不多说了,上效果图吧。
这里面涉及几个技巧
1、text节点和边框拖拽节点和节点rect要实现在一个g里头,这样拖拽事件给g即可,不需要每个元素去监听拖拽。
2、拖拽的时候,要对window.d3.event.x进行特殊的计算处理,因为rect节点的宽高都比较大,导致每次获取都x,y都是不统一的,如果简单的进行translate({window.d3.event.y})的话,会出现跳动的问题。
再就是采用了timer来使移动更加平滑。
function dragstarted(d) {
if (d != null) {
if(desObj.drageTimer!=null){
clearTimeout(desObj.drageTimer);
}
desObj.drageTimer=setTimeout(()=>{
desObj.startedDrage=true;
},100);
}
}
function dragged(d) {
if (d != null&&desObj.startedDrage) {
desObj.vueMethods.hideNodeMenu&&desObj.vueMethods.hideNodeMenu();
if(d.mvX1==null){
d.mvX1=window.d3.event.x;
d.mvY1=window.d3.event.y;
return void(0);
}
d.mvX2=window.d3.event.x;
d.mvY2=window.d3.event.y;
let moveX=parseFloat(d.mvX2)-parseFloat(d.mvX1);
let moveY=parseFloat(d.mvY2)-parseFloat(d.mvY1);
d.moveX=moveX;
d.moveY=moveY;
window.d3.select(this).attr("transform",`translate(${parseFloat(d.fx)+parseFloat(moveX)},${parseFloat(d.fy)+parseFloat(moveY)})`);
for(let drageType in d.linkDrage){
let links=d.linkDrage[drageType];
//如果启始存在
if(links.startX!==null){
window.d3.select("#link_"+drageType+"_"+d.id) .attr("d", (d)=>{
return `M${ d.linkDrage[drageType]["cx"]+ d.fx} ${ d.linkDrage[drageType]["cy"]+ d.fy}L${d.linkDrage[drageType].endX} ${d.linkDrage[drageType].endY}`;
});
}
}
}
}
function dragended(d) {
if(d!=null){
if(d.moveX!=null&&!isNaN(d.moveX)){
d.fx=parseFloat(d.fx)+parseFloat(d.moveX);
d.fy=parseFloat(d.fy)+parseFloat(d.moveY);
d.moveX=null;
d.moveY=null;
}
d.mvX1=null;
d.mvY1=null;
d.mvX2=null;
d.mvY2=null;
desObj.startedDrage=false;
//如果存在timer 则需要清空
if(desObj.drageTimer!=null){
clearTimeout(desObj.drageTimer);
}
}
}
3、计算text的宽度,再进行位置的计算
FlowDesCtl.prototype.addNodeText=function({node,nodeWidth,nodeText,colorText="#000",nodeHeight}){
node.append("text")
.attr("x", (d)=>{
$(`<span id="labelTemp">${nodeText}</span>`).appendTo($('#' + this.containerId));
let labelWid=$("#labelTemp").width();$("#labelTemp").remove();
d.labelWid=labelWid+20;
return parseFloat(nodeWidth)/2-labelWid/2;
})
.attr("y", parseFloat(nodeHeight/2)+5)
.text(nodeText)
.attr("stroke-width", "0.8px")
.style('stroke',colorText)
.attr("font-family", 'PingFangSC, "Microsoft YaHei", "Lantinghei SC", serif')
.attr("font-size", 16)
.style("text-anchor"," start")
.on("click", (d) => {
//左键盘点击 则隐藏右键菜单
this.vueMethods.hideNodeMenu&&this.vueMethods.hideNodeMenu();
//隐藏其他节点的边框拖拽
this._hideDragSizeBorder();
//显示当前节点的拖拽边框
this._showCurDragSizeBorder(d);
window.d3.event.stopPropagation();
})
.on("contextmenu", d => {
this._showNodeMenu(d);
});
};
4、绘制拖拽节点
FlowDesCtl.prototype._addDragSizeBorder=function({nodeCtl,nodeData,nodeWidth,nodeHeight,nodeType}){
const borderNode = nodeCtl.append("g")
// .selectAll("g")
.data([nodeData])
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.attr("class","drage_size_border")
.attr("id", (d)=>{
return "drage_size_border_"+d.id+"";
})
.style("cursor",`move)`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);
let rectWidth=nodeWidth;
let rectHeight=nodeHeight;
if(nodeType==='startNode'||nodeType==='endNode'){
rectWidth=nodeWidth*2;
rectHeight=rectHeight*2;
}
let padding=14;
borderNode.append("rect")
.attr("rx", 0)
.attr("ry", 0)
.attr("id", (d)=>{
return "rect_drage_"+d.id+"";
})
.attr("fill", "#fff")
.attr("width", rectWidth+padding)
.attr("height", rectHeight+padding)
.attr("stroke", "#dfdfdf")
.attr("stroke-width", 1)
.attr("transform",`translate(${-padding/2},${-padding/2})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);
//绘制top 第一个 top-left拖拽节点
this._drawDragNode({borderNode,x:-padding/2-this.options.dragNodeWidth/2,y:-padding+this.options.dragNodeWidth/2,cursor:"nw-resize",dragType:"nw"});
//绘制top 第二个 top-center拖拽节点
this._drawDragNode({borderNode,x:this.options.taskNodeWidth/2,y:-padding+this.options.dragNodeWidth/2,cursor:"n-resize",dragType:"n"});
//绘制top 第三个 top-right拖拽节点
this._drawDragNode({borderNode,x:this.options.taskNodeWidth+this.options.dragNodeWidth/2,y:-padding+this.options.dragNodeWidth/2,cursor:"ne-resize",dragType:"ne"});
//绘制center 第一个拖拽节点 center-left
this._drawDragNode({borderNode,x:-padding/2-this.options.dragNodeWidth/2,y:this.options.taskNodeHeight/2-this.options.dragNodeWidth/2,cursor:"w-resize",dragType:"w"});
//绘制center 第二个拖拽节点 center-right
this._drawDragNode({borderNode,x:this.options.taskNodeWidth+this.options.dragNodeWidth/2,y:this.options.taskNodeHeight/2-this.options.dragNodeWidth/2,cursor:"e-resize",dragType:"e"});
//绘制bottom 第一个 bottom-left拖拽节点
this._drawDragNode({borderNode,x:-padding/2-this.options.dragNodeWidth/2,y:this.options.taskNodeHeight+this.options.dragNodeWidth/2,cursor:"sw-resize",dragType:"sw"});
//绘制bottom 第二个 bottom-center拖拽节点
this._drawDragNode({borderNode,x:this.options.taskNodeWidth/2,y:this.options.taskNodeHeight+this.options.dragNodeWidth/2,cursor:"s-resize",dragType:"s"});
//绘制bottom 第三个 bottom-right拖拽节点
this._drawDragNode({borderNode,x:this.options.taskNodeWidth+this.options.dragNodeWidth/2,y:this.options.taskNodeHeight+this.options.dragNodeWidth/2,cursor:"se-resize",dragType:"se"});
};