从边界到边界绘制箭头线

  • 本文关键字:边界 绘制 svg d3.js
  • 更新时间 :
  • 英文 :


我看到一些关于边界到边界连接的主题讨论,但我的情况很特殊,它与四次曲线连接,所以不容易计算出确切的位置!

console.clear()
var w = 15
var svg = d3.select('body').append('svg')
.attr('width','500')
.attr('height','500')
.attr('viewBox',`0 0 ${w} ${w}`)
//.style('border','1px solid red')
add_marker(svg)
add_grid(svg)
draw(svg)
function add_marker(svg) {  
var color = 'black'  
svg.append("defs")
.append("marker")
.attr("id","arrowhead")
.attr("markerWidth", 10)
.attr("markerHeight", 7)
.attr("orient","auto")
.attr('refX',5)
.attr("refY", 2)
.append("path")
.attr("d", "M 0 0 L 5 2 L 0 4 z")
.style("fill", color);
}
function px(r,a) {
return r*Math.cos(a*Math.PI/180)
}
function py(r,a) {
return r*Math.sin(a*Math.PI/180)
}
function draw() {
var r = 5
var dots = [0,60,120,180,240,300,360]
var g = svg.append('g')
.attr('transform', (d,i) => `translate(${r+2},${r+2})`)
var g1 = g.selectAll(null)
.data(dots).enter()

g1.append('circle')
.attr('cx',d => px(r,d))
.attr('cy',d => py(r,d))
.attr('r',1)
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)

var prev = null
var mydraw = function(d,i) {
if (prev == null) {
prev = d
return ''
}
var x1 = px(r,d)
var y1 = py(r,d)
var x2 = px(r,prev)
var y2 = py(r,prev)
var x3 = px(r*1.8,(prev+d)/2)
var y3 = py(r*1.8,(prev+d)/2)
var path = `M ${x1},${y1} Q ${x3},${y3} ${x2},${y2}`
prev = d
return path
}
g1.append('path')
.attr('d',d => mydraw(d))
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)
.attr("marker-end","url(#arrowhead)")
}
function add_grid(svg) {
var mygrid = function(d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`
}
var grid = []
for(var i = 0; i < w; i++) {
grid.push(i);
}
svg.append('g')
.selectAll(null)
.data(grid).enter()
.append('path')
.attr('d',d => mygrid(d))
.attr('fill','none')
.attr('stroke','green')
.attr('stroke-width',.05)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

如果d3可以收缩,那就太酷了\r那么在这种情况下它就完美了!

使用qbPath函数绘制从边界到边界的路径:

var w = 15
var svg = d3.select('body').append('svg')
.attr('width','500')
.attr('height','500')
.attr('viewBox',`0 0 ${w} ${w}`)
//.style('border','1px solid red')
add_marker(svg)
add_grid(svg)
draw(svg)
function add_marker(svg) {  
var color = 'black'  
svg.append("defs")
.append("marker")
.attr("id","arrowhead")
.attr("markerWidth", 10)
.attr("markerHeight", 7)
.attr("orient","auto")
.attr('refX',5)
.attr("refY", 2)
.append("path")
.attr("d", "M 0 0 L 5 2 L 0 4 z")
.style("fill", color);
}
function px(r,a) {
return r*Math.cos(a*Math.PI/180)
}
function py(r,a) {
return r*Math.sin(a*Math.PI/180)
}
function draw() {
var r = 5
var dots = [0,60,120,180,240,300,360]
var g = svg.append('g')
.attr('transform', (d,i) => `translate(${r+2},${r+2})`)

const qbPath = index => {
const from  = dots[(index + 1) % dots.length];
const to = dots[index];
const x1 = px(r,from);
const y1 = py(r,from);
const x2 = px(r,to);
const y2 = py(r,to);
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2;
const dx = (x2 - mx);
const dy = (y2 - my);
const x3 = mx - dy * 1.5;
const y3 = my + dx * 1.5;

const dist = Math.hypot(x3-x1, y3-y1);
const cx1 = x1 + (x3 - x1) / dist;
const cy1 = y1 + (y3 - y1) / dist;
const cx2 = x2 + (x3 - x2) / dist;
const cy2 = y2 + (y3 - y2) / dist;

return `M ${cx1},${cy1} Q ${x3},${y3} ${cx2},${cy2}`;
};

var g1 = g.selectAll(null)
.data(dots).enter()

g1.append('circle')
.attr('cx',d => px(r,d))
.attr('cy',d => py(r,d))
.attr('r',1)
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)

var prev = null
var mydraw = function(d,i) {
if (prev == null) {
prev = d
return ''
}
var x1 = px(r,d)
var y1 = py(r,d)
var x2 = px(r,prev)
var y2 = py(r,prev)
var x3 = px(r*1.8,(prev+d)/2)
var y3 = py(r*1.8,(prev+d)/2)
var path = `M ${x1},${y1} Q ${x3},${y3} ${x2},${y2}`
prev = d
return path
}
/*
g1.append('path')
.attr('d',d => mydraw(d))
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)
.attr("marker-end","url(#arrowhead)")
*/
g1.filter((_, i) => i > 0)
.append('path')
.attr('d', (_, i) => qbPath(i))
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)
.attr("marker-end","url(#arrowhead)")
}
function add_grid(svg) {
var mygrid = function(d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`
}
var grid = []
for(var i = 0; i < w; i++) {
grid.push(i);
}
svg.append('g')
.selectAll(null)
.data(grid).enter()
.append('path')
.attr('d',d => mygrid(d))
.attr('fill','none')
.attr('stroke','green')
.attr('stroke-width',.05)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

另外两个修复:

  1. 在箭头标记.attr('refX',4)而不是5中
  2. qbPath中,增加端点的半径(可以将1.1更改为大于1的任何值(:
const cx2 = x2 + (x3 - x2) / dist * 1.1;
const cy2 = y2 + (y3 - y2) / dist * 1.1; 

var w = 15
var svg = d3.select('body').append('svg')
.attr('width','500')
.attr('height','500')
.attr('viewBox',`0 0 ${w} ${w}`)
//.style('border','1px solid red')
add_marker(svg)
add_grid(svg)
draw(svg)
function add_marker(svg) {  
var color = 'red'  
svg.append("defs")
.append("marker")
.attr("id","arrowhead")
.attr("markerWidth", 10)
.attr("markerHeight", 7)
.attr("orient","auto")
.attr('refX',4)
.attr("refY", 2)
.append("path")
.attr("d", "M 0 0 L 5 2 L 0 4 z")
.style("fill", color);
}
function px(r,a) {
return r*Math.cos(a*Math.PI/180)
}
function py(r,a) {
return r*Math.sin(a*Math.PI/180)
}
function draw() {
var r = 5
var dots = [0,60,120,180,240,300,360]
var g = svg.append('g')
.attr('transform', (d,i) => `translate(${r+2},${r+2})`)

const qbPath = index => {
const from  = dots[(index + 1) % dots.length];
const to = dots[index];
const x1 = px(r,from);
const y1 = py(r,from);
const x2 = px(r,to);
const y2 = py(r,to);
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2;
const dx = (x2 - mx);
const dy = (y2 - my);
const x3 = mx - dy * 1.5;
const y3 = my + dx * 1.5;

const dist = Math.hypot(x3-x1, y3-y1);
const cx1 = x1 + (x3 - x1) / dist;
const cy1 = y1 + (y3 - y1) / dist;
const cx2 = x2 + (x3 - x2) / dist * 1.1;
const cy2 = y2 + (y3 - y2) / dist * 1.1;

return `M ${cx1},${cy1} Q ${x3},${y3} ${cx2},${cy2}`;
};

var g1 = g.selectAll(null)
.data(dots).enter()

g1.append('circle')
.attr('cx',d => px(r,d))
.attr('cy',d => py(r,d))
.attr('r',1)
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)

var prev = null
var mydraw = function(d,i) {
if (prev == null) {
prev = d
return ''
}
var x1 = px(r,d)
var y1 = py(r,d)
var x2 = px(r,prev)
var y2 = py(r,prev)
var x3 = px(r*1.8,(prev+d)/2)
var y3 = py(r*1.8,(prev+d)/2)
var path = `M ${x1},${y1} Q ${x3},${y3} ${x2},${y2}`
prev = d
return path
}
/*
g1.append('path')
.attr('d',d => mydraw(d))
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)
.attr("marker-end","url(#arrowhead)")
*/
g1.filter((_, i) => i > 0)
.append('path')
.attr('d', (_, i) => qbPath(i))
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',.1)
.attr("marker-end","url(#arrowhead)")
}
function add_grid(svg) {
var mygrid = function(d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`
}
var grid = []
for(var i = 0; i < w; i++) {
grid.push(i);
}
svg.append('g')
.selectAll(null)
.data(grid).enter()
.append('path')
.attr('d',d => mygrid(d))
.attr('fill','none')
.attr('stroke','green')
.attr('stroke-width',.05)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

最新更新