在繪製diagram
圖表時,會用到的垂直水平連線,並且在特定位置折角,雖然d3
提供了很多繪製Bezier
的方法,可是實際上除了數據分析或特別的圖表外,我很少遇到需要Bezier
的圖表。
其實流程很簡單,幾個步驟而已。
範例:
rect2 = rootLayer
.append("rect")
.attr("width", 100)
.attr("height", 100)
.attr("x", 500)
.attr("y", 50)
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
以上範例即是放置一個rect
,並設定屬性後,使用d3.drag()
綁定拖拉事件。
Callback的部分:
function dragstarted(event) {}
function dragged(event) {
let target = d3.select(this);
let offsetX = event.x - target.attr("x");
let offsetY = event.y - target.attr("y");
target
.attr("x", event.x - offsetX + event.dx)
.attr("y", event.y - offsetY + event.dy);
}
function dragended(event) {}
我們透過d3.select(this)
取得拖拉事件並產生Selection
。
讀取目前點擊
的位置與目標被點擊元件
的位置算出offset
後,更新目標被點擊元件
的位置並加上位移值,為何要算出offset
僅是因為不想讓拖拉事件一定都會以左上角定位。
const genLine = (source, target) => {
let ctx = d3.path();
ctx.moveTo(source.x, source.y);
ctx.lineTo(target.x, target.y);
return ctx.toString();
};
其實就是直接將目前位置跟目標位置直接連線。
const genLine = (source, target) => {
let ctx = d3.path();
let dx = Math.abs(source.x - target.x);
let dy = Math.abs(source.y - target.y);
// 兩點距離
let dl = Math.sqrt( dx * dx + dy * dy );
// 兩點XY距離加總
let tl = Math.abs(dx + dy);
// 中點座標
let mp = {
x: ((source.x + target.x) / 2),
y: ((source.y + target.y) / 2)
}
// 如果小於150,沒必要特別繞中點
if (tl - dl <= 150) {
// 連線垂直即可
ctx.moveTo(source.x, source.y);
ctx.lineTo(source.x, target.y);
ctx.lineTo(target.x, target.y);
} else {
// 先連線至中點
ctx.moveTo(source.x, source.y);
ctx.lineTo(mp.x, source.y);
// 再連線至終點
ctx.lineTo(mp.x, target.y);
ctx.lineTo(target.x, target.y);
}
return ctx.toString();
};
其實以最後一部分來看,也只是先到達目標位置的Y
,再到達目標位置的X
,至於為何會有分先連線至中點呢?
產生結過為:
如果兩點直線連線以及折線長度相差未超過150,就會只採用普通折現,不會到中點。
結果為下:
其實很多線圖,應該有更複雜更多的判斷,像是繞開碰撞元件,線段起始方向,等等更多常見diagram
會實現的功能。
d3
已經讓我們非常方便操作線段了,只要自己寫些數學公式,可能配合三角函數,可以玩出更多花樣。