Search code examples
javascripthtml5-canvas

How to drag points with it's connecting line in html5 canvas?


Following is my code. I have face issue when I drag a point. The problem is that whenever I drag point all the line connect to one point. I need to have draggable points in a html5 Canvas. But I have a supplementary constraint: the 2 points must be linked by a line. When I drag a point, the line must be dynamically drawn, and still linked to the 2 points.

let points= [];
let drag_point= -1;
let pointSize= 6;
let canvas = document.querySelector("#myCanvas");
let w = canvas.width;
let h = canvas.height;

var ctx = canvas.getContext("2d");

            $("#myCanvas").mousedown(function (e) {
                var pos = getPosition(e);
                drag_point = getPointAt(pos.x, pos.y);
                console.log("pos", drag_point);
                if (drag_point == -1) {
                    // no point at that position, add new point
                    drawlines(pos.x, pos.y);
                    points.push(pos);
                }
            });
            $("#myCanvas").mousemove(function (e) {
                if (drag_point != -1) {
                    // if currently dragging a point...
                    var pos = getPosition(e);
                    //...update that.points position...
                    points[drag_point].x = pos.x;
                    points[drag_point].y = pos.y;
                    redraw(); // ... and redraw myCanvas
                }
            });
            $("#myCanvas").mouseup(function (e) {
                drag_point = -1;
            });
    function getPosition(event) {

                var rect = canvas.getBoundingClientRect();
                var x = event.clientX - rect.left;
                var y = event.clientY - rect.top;
                console.log(x, y);
                return { x: x, y: y };

            }
    function getPointAt(x, y) {
                for (var i = 0; i < points.length; i++) {
                    if (
                        Math.abs(points[i].x - x) < pointSize &&
                        Math.abs(points[i].y - y) < pointSize
                    )
                        // check if x,y is inside points bounding box. replace with pythagoras theorem if you like.
                        return i;
                }
                return -1; // no point at x,y
            }

        function    redraw() {

                ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas

                for (var i = 0; i < points.length; i++) {
                    // draw all points again
                    drawlines(points[i].x, points[i].y);
                }
            }

function drawlines(x, y) {
        
                drawImages(x, y);

                if (points.length > 0) {
                    var last = points[points.length - 1];
                    ctx.beginPath();
                    ctx.moveTo(last.x, last.y);
                    ctx.lineTo(x, y);
                    ctx.strokeStyle = "blue";
                    ctx.stroke();
                }

            }
            function drawImages(x, y) {
                var ctx = document.getElementById("myCanvas").getContext("2d");
                ctx.beginPath();
                ctx.arc(x, y, pointSize, 0, Math.PI * 2, true);
                ctx.strokeStyle = "red";
                ctx.stroke();
            }
      
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas
            id="myCanvas"
            width="1000"
            height="1000"
            style="border: 1px solid #d3d3d3"
        ></canvas>

.


Solution

  • See code below...

    I refactored your drawlines and drawImages to be called independently not inside a common loop, in my code we draw all lines, then we draw all circles, that way we don't have to change colors back and forth all the time and prevents any overlaps of the lines over the circles, also another change is in the mousedown I call the redraw instead of drawlines.

    Looking at your code educated guess the problem is in your:
    var last = points[points.length - 1];
    that seems very off, technically that is making the last always be the same

    let points = [{x:10,y:10},{x:55,y:50},{x:100,y:10}];
    let drag_point = -1;
    let pointSize = 6;
    let canvas = document.getElementById("myCanvas");
    var ctx = canvas.getContext("2d");
    
    canvas.onmousedown = function(e) {
      var pos = getPosition(e);
      drag_point = getPointAt(pos.x, pos.y);
      if (drag_point == -1) {
        points.push(pos);
        redraw();
      }
    };
    canvas.onmousemove = function(e) {
      if (drag_point != -1) {
        var pos = getPosition(e);
        points[drag_point].x = pos.x;
        points[drag_point].y = pos.y;
        redraw(); 
      }
    };
    canvas.onmouseup = function(e) {
      drag_point = -1;
    };
    
    function getPosition(event) {
      var rect = canvas.getBoundingClientRect();
      var x = event.clientX - rect.left;
      var y = event.clientY - rect.top;
      return {x, y};
    }
    
    function getPointAt(x, y) {
      for (var i = 0; i < points.length; i++) {
        if (
          Math.abs(points[i].x - x) < pointSize &&
          Math.abs(points[i].y - y) < pointSize
        )
          return i;
      }
      return -1; 
    }
    
    function redraw() {
      if (points.length > 0) {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawLines()
        drawCircles()
      }
    }
    
    function drawLines() {
      ctx.beginPath();
      ctx.moveTo(points[0].x, points[0].y);
      ctx.strokeStyle = "blue";
      ctx.lineWidth = 2;
      points.forEach((p) => {
        ctx.lineTo(p.x, p.y);
      })
      ctx.stroke();
    }
    
    function drawCircles() {
      ctx.strokeStyle = "red";
      ctx.lineWidth = 4;
      points.forEach((p) => {
        ctx.beginPath();
        ctx.arc(p.x, p.y, pointSize, 0, Math.PI * 2, true);
        ctx.stroke();
      })
    }
    
    redraw()
    <canvas id="myCanvas" width=160 height=160 style="border: 1px solid"></canvas>