javascriptalgorithmkonvajs

Algorithm for drawing lines with 90-degree angles


To get straight to the point, what I hope to achieve is to be able to create a connecting line between two elements with this shape:

Demo Image

DBDIAGRAM.IO

When the elements move, the line resets but always maintains a 90 degree angle, instead of being a straight or diagonal line between [x,y] to [x,y].

Is there some kind of algorithm for this? Maybe a grid with some kind of A* implementation?


Solution

  • I don't know how to make rounded corners easy, but the easiest example will be this:

    const canvas = document.querySelector('canvas')
    const ctx = canvas.getContext('2d')
    
    // define the points
    const p1 = {
      x: 30,
      y: 50
    }
    
    const p2 = {
      x: 150,
      y: 130
    }
    
    ctx.strokeStyle = 'red'
    
    // draw the points
    ctx.beginPath()
    ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
    ctx.stroke()
    
    ctx.beginPath()
    ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
    ctx.stroke()
    
    // get distance between
    const horizontalDistance = p2.x - p1.x
    
    ctx.strokeStyle = 'black'
    
    
    // draw left part
    ctx.beginPath()
    ctx.moveTo(p1.x, p1.y)
    ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
    ctx.stroke()
    
    // draw vertical part
    ctx.beginPath()
    ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
    ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
    ctx.stroke()
    
    // draw right part
    ctx.beginPath()
    ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
    ctx.lineTo(p2.x, p2.y)
    ctx.stroke()
    canvas {
      border: 1px solid black;
    }
    <canvas></canvas>

    Real-time version:

    const canvas = document.querySelector('canvas')
    const ctx = canvas.getContext('2d')
    
    const p1 = {
      x: canvas.width / 2,
      y: canvas.height / 2
    }
    
    const p2 = {
      x: 150,
      y: 130
    }
    
    canvas.addEventListener('mousemove', e => {
      const mousePos = getMousePos(canvas, e)
      p2.x = mousePos.x
      p2.y = mousePos.y
    })
    
    loop()
    
    function loop() {
      draw()
      requestAnimationFrame(loop)
    }
    
    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      ctx.strokeStyle = 'red'
    
      ctx.beginPath()
      ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
      ctx.stroke()
    
      const horizontalDistance = p2.x - p1.x
    
      ctx.strokeStyle = 'black'
    
    
      ctx.beginPath()
      ctx.moveTo(p1.x, p1.y)
      ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
      ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
      ctx.lineTo(p2.x, p2.y)
      ctx.stroke()
    
    }
    
    function getMousePos(canvas, evt) {
      const rect = canvas.getBoundingClientRect();
      return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
      };
    }
    canvas {
      border: 1px solid black;
    }
    <canvas></canvas>

    const canvas = document.querySelector('canvas')
    const ctx = canvas.getContext('2d')
    
    // define the points
    const p1 = {
      x: 30,
      y: 50
    }
    
    const p2 = {
      x: 150,
      y: 130
    }
    
    ctx.strokeStyle = 'red'
    
    // draw the points
    ctx.beginPath()
    ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
    ctx.stroke()
    
    ctx.beginPath()
    ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
    ctx.stroke()
    
    // get distance between
    const horizontalDistance = p2.x - p1.x
    const verticalDistance = p2.y - p1.y
    
    ctx.strokeStyle = 'black'
    
    
    // draw left part
    ctx.beginPath()
    ctx.moveTo(p1.x, p1.y)
    ctx.quadraticCurveTo(
      p1.x + horizontalDistance / 2, p1.y,
      p1.x + horizontalDistance / 2, p1.y + verticalDistance / 2
    )
    ctx.stroke()
    
    // draw right part
    ctx.beginPath()
    ctx.moveTo(p1.x + horizontalDistance / 2, p1.y + verticalDistance / 2)
    ctx.quadraticCurveTo(
      p1.x + horizontalDistance / 2, p2.y,
      p2.x, p2.y
    )
    ctx.lineTo(p2.x, p2.y)
    ctx.stroke()
    canvas {
      border: 1px solid black;
    }
    <canvas></canvas>

    It's possible to use quadratic curves to make the lines more curvy

    const canvas = document.querySelector('canvas')
    const ctx = canvas.getContext('2d')
    
    const p1 = {
      x: canvas.width / 2,
      y: canvas.height / 2
    }
    
    const p2 = {
      x: 150,
      y: 130
    }
    
    canvas.addEventListener('mousemove', e => {
      const mousePos = getMousePos(canvas, e)
      p2.x = mousePos.x
      p2.y = mousePos.y
    })
    
    loop()
    
    function loop() {
      draw()
      requestAnimationFrame(loop)
    }
    
    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      ctx.strokeStyle = 'red'
    
      ctx.beginPath()
      ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
      ctx.stroke()
    
      const horizontalDistance = p2.x - p1.x
    
      ctx.strokeStyle = 'black'
    
    
      ctx.beginPath()
      ctx.moveTo(p1.x, p1.y)
      ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
      ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
      ctx.lineTo(p2.x, p2.y)
      ctx.stroke()
    
    }
    
    function getMousePos(canvas, evt) {
      const rect = canvas.getBoundingClientRect();
      return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
      };
    }
    canvas {
      border: 1px solid black;
    }
    <canvas></canvas>