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"});
};