1

I'am trying to draw a line to connect two shape based on mouse move. And I konw how to relize it by using native canvas. But have no idea how to realize it by using KonvaJS. Please help me on this.

This image shows what result i what : enter image description here

And this is the code that i try to realize what I want. But it doesn't work.

            stage.on('mousedown', function(e) {
                const a = e.target instanceof Konva.Rect;
                if (!a) {
                    return;
                } else {
                    
                    group.draggable(false);
                    group2.draggable(false);
                    
                    clickdot1 = e.target;
                    drawingLine = true;
                    }
            });

            stage.on('mousemove', function(e) {
                if (!drawingLine) {
                    return;
                }else{
                    if(clickdot1!=null&&drawingLine){
                        let lastLine = new Konva.Line({
                            stroke: '#df4b26',
                            strokeWidth: 5,
                            lineCap: 'round',
                            lineJoin: 'round',
                            points: [e.target.x(), e.target.y()],
                        });
                        connections.push(lastLine);
                        drawthings();
                    }
                }   
            });
            
            function drawthings(){
                for(let i = 0;i<connections.length;i++){
                    animLayer.add(connections[i]);
                    animLayer.batchDraw();
                }
            }
yanyusanqian
  • 110
  • 1
  • 9

2 Answers2

2

There are many ways to do that. The basic idea:

  1. create a line on mousedown on your source object
  2. update line position on mousemove event
  3. check target shape on mouseup. If that is something "connectable" keep a line, otherwise destroy it.
const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight
});

const layer = new Konva.Layer();
stage.add(layer);

layer.add(new Konva.Text({ text: 'try to drag a green source into any red target', padding: 10}))

const source = new Konva.Circle({
  x: 20,
  y: 50,
  radius: 10,
  fill: 'green'
});
layer.add(source);

const target1 = new Konva.Circle({
  x: 20,
  y: 220,
  radius: 10,
  fill: 'red',
  name: 'target'
});
layer.add(target1);


const target2 = new Konva.Circle({
  x: 120,
  y: 220,
  radius: 10,
  fill: 'red',
  name: 'target'
});
layer.add(target2);


let drawingLine = false;
let line;
source.on('mousedown', () => {
  drawingLine = true;
  const pos = stage.getPointerPosition();
  line = new Konva.Line({
    stroke: 'black',
    // remove line from hit graph, so we can check intersections
    listening: false,
    points: [source.x(), source.y(), pos.x, pos.y]
  });
  layer.add(line);
});

stage.on('mouseover', (e) => {
  if (e.target.hasName('target')) {
    e.target.stroke('black');
    layer.draw();
  }
});

stage.on('mouseout', (e) => {
  if (e.target.hasName('target')) {
    e.target.stroke(null);
    layer.draw();
  }
});

stage.on('mousemove', (e) => {
  if (!line) {
    return;
  }
  const pos = stage.getPointerPosition();
  const points = line.points().slice();
  points[2] = pos.x;
  points[3] = pos.y;
  line.points(points);
  layer.batchDraw();
});

stage.on('mouseup', (e) => {
  if (!line) {
    return;
  }
  if (!e.target.hasName('target')) {
    line.destroy();
    layer.draw();
    line = null;
  } else {
    line = null;
  }
  
});


layer.draw();

https://jsbin.com/rumizocise/1/edit?html,js,output

lavrton
  • 18,973
  • 4
  • 30
  • 63
  • 1
    Thanks! It helped me lot. But I want to draw a line from one shape's center to the other shape's center. So i need the shape position when `mouseup`. Then I find the `e.target` is always `Konva.line` when the `mouseup` event is triggered. Is there any way can help me get the position under the line? – yanyusanqian Jul 21 '20 at 07:37
  • See edit in my answer that adds to @lavrton's answer for centering on the target shape. – Vanquished Wombat Jul 24 '20 at 11:52
  • 2
    @yanyusanqian as in my answer, you have to set `listening: false` to the line. So it doesn't effect `e.target` on `mouseup`. – lavrton Jul 24 '20 at 13:17
  • 1
    @Iavrton I didn't notice that before, which made my problems more complicated. Thanks a lot for your help! – yanyusanqian Jul 24 '20 at 15:17
0

It seems that your real question is how to check if there is a shape under the mouse during mouse move or mouse up operations.

Konva has a hit detection approach that I will let @lavarton explain. If you are dealing with pure rectangles - as opposed to for example circles - you can do your own hit testing using shape position and running some simple math checks. See my solution to this question about 'Selecting by drawing a box around objects in konva' which covers the same ground for hit testing and should show you a simple way forward.

The point about 'pure rectangles' concerns the fact that this approach works easily for non-rotated, rectangular shapes. However, rotated rectangles or non-rectangular shapes would require more work and if that were your use-case then Konva's built-in hit testing would offer a lower time-cost for learning and future support of your code.

Regarding @lavrton's answer missing the requirement to place the line into the centre position on the connected shape, change the stage.on('mouseup') listener in his code as below to achieve that.

stage.on('mouseup', (e) => {
  if (!line) {
    return;
  }
  
  if (!e.target.hasName('target')) {
    line.destroy();
    
    layer.draw();
    line = null;
  } else {
    let pos = e.target.getClientRect();
    const points = line.points().slice();
    points[2] = pos.x + (e.target.width()/2);
    points[3] = pos.y + (e.target.height()/2);;
    line.points(points);
    layer.batchDraw();
   
    line = null;
  }
  
});

This works by getting the topleft of the target shape (the getClientRect value), then adding half the shape width to the x and half the shape height to the y value to give the centre point. We than get the current line points array, set the values in slot 2 & 3 which are the end.x and end.y, give that back to the line and redraw the layer.

@lavrton should modify his example as above and be awarded the correct answer.

Vanquished Wombat
  • 9,075
  • 5
  • 28
  • 67
  • In native canvas, I used this way that check if there is a shape under the mouse to solve my problem. But I did't find some way in konvaJS, so I found a more simpler but not very rigorous way that to let the line draw a certain distance from the mouse. In this way, the line will not covering the rectangle below. But thanks for your help! Thanks a lot! – yanyusanqian Jul 24 '20 at 10:05