Draw line between rectangle from border to border

Viewed 124

I would like to draw a line to connect two rectangle from border to border and pass through both rectangle center. currently, I can draw the line from center to center. I know if I fill the rectangle with white, then the line connection will looks like from border to border. but I do wish to keep rectangle transparent!

What's proper way to implement it?

var svg = d3.select('body').append('svg')
.attr('width',800)
.attr('height',600)
.style('border','1px solid red')
.style('background-color','#e0e0e0')

 svg.append("defs")
    .append("marker")
    .attr("id","pointer")
    .attr("markerWidth", 10)
    .attr("markerHeight", 10)
    .attr("orient","auto")
    .attr("refY", 5)
    .append("path")
    .attr("d", "M 0 0 L 10 5 L 0 10 z")
    

var data = [
  {
    id:0,
    x:100,
    y:100,
    width:100,
    height:50,
  },{
    id:1,
    x:200,
    y:200,
    width:100,
    height:50,
  }
]

var links = [
  {
    src:0,
    dest:1,
  }
]

svg.selectAll(null)
.data(data)
.enter()
.append('rect')
.attr('x', d => d.x - d.width/2)
.attr('y', d => d.y - d.height/2)
.attr('width',d => d.width)
.attr('height', d => d.height)
.attr('fill','none')
.attr('stroke','black')
.attr('stroke-width',2)

svg.selectAll(null)
.data(links)
.enter()
.append('line')
.attr('x1',d => data.find(e => e.id === d.src).x)
.attr('y1',d => data.find(e => e.id === d.src).y)
.attr('x2',d => data.find(e => e.id === d.dest).x)
.attr('y2',d => data.find(e => e.id === d.dest).y)
.attr('stroke','black')
.attr("marker-end","url(#pointer)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

1 Answers

Use linkPath function:

const first = {x: 50, y: 50, width: 40, height: 30};
const second = {x: 200, y: 110, width: 70, height: 40};
const third = {x: 80, y: 120, width: 50, height: 20};

const linkPath = (from, to) => {
  const dx = to.x - from.x;
  const dy = to.y - from.y;
  let fX, fY, tX, tY;
  const factor = dx > 0 ^ dy > 0;
  
  if (from.width / from.height > Math.abs(dx / dy)) {
    fX = from.x + from.height * dx / dy / 2 * (factor ? -1 : 1);
    fY = from.y + from.height / 2 * (factor ? -1: 1);
  }
  else {
    fX = from.x + from.width / 2 * (factor ? -1 : 1);
    fY = from.y + from.width * dy / dx / 2 * (factor ? -1 : 1);
  }

  if (to.width / to.height > Math.abs(dx / dy)) {
    tX = to.x + to.height * dx / dy / 2 * (factor ? 1 : -1);
    tY = to.y + to.height / 2 * (factor ? 1 : -1);
  }
  else {
    tX = to.x + to.width / 2 * (factor ? 1 : -1);
    tY = to.y + to.width * dy / dx / 2 * (factor ? 1 : -1);
  }

  return {from: {x: fX, y: fY}, to: {x: tX, y: tY}};  
};


const svg = d3.select('svg');

const drawRectangle = rect => 
  svg.append('rect')
    .attr('x', rect.x - rect.width / 2) 
    .attr('y', rect.y - rect.height / 2) 
    .attr('width', rect.width) 
    .attr('height', rect.height);
    
const drawLink = (from, to) => {
    const path = linkPath(from, to);
  svg.append('line')
    .attr('x1', path.from.x)
    .attr('y1', path.from.y)
    .attr('x2', path.to.x)
    .attr('y2', path.to.y)
    .attr('marker-end', 'url(#arrowhead)')
} 
    
    
drawRectangle(first);
drawRectangle(second);
drawRectangle(third);
drawLink(first, second);
drawLink(first, third);
drawLink(second, third);
rect {
  fill: none;
  stroke: black;
}

line {
  stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="300" height="200">
  <defs>
    <marker id="arrowhead" markerWidth="10" markerHeight="7" 
    refX="10" refY="3.5" orient="auto">
      <polygon fill="black" points="0 0, 10 3.5, 0 7" />
    </marker>
  </defs>
</svg>

Related