Search code examples
reactjskonvajsreact-konvakonva

Increase Arrow from both sides in React-Konva


I'm drawing a Line with React-Konva. Once the Line is drawn, I would like to display an Arrow (with pointers on both sides) underneath that Line. That Arrow should be longer (both sides) than the Line but should point the same direction.

What I've tried so far is to create an Arrow with the same points as the Line, and then using a scale on that Arrow to make it longer than the Line. This works, but the Arrow is longer than the Line on just one side.

Screenshot

<Arrow
 pointerAtBeginning={true}
 scaleX={1.4}
 scaleY={1.4}
 stroke="rgb(0, 61, 55)"
 x={startPoint[0]}
 y={startPoint[1]}
 points={[0, 0, ...endPoint]}
/>

Is there a method I can use to accomplish this?


Solution

  • In principle your issue is because the arrow shape is drawn from the x & y position - A in your sketch. What you need to do is compute the positions for the drawing point moved by 0.1 times the line length, then apply a 1.2 scale X & Y.

    You can do this by

    1. Get the width and height of the line via the line points where the starting pt is {x: points[0], y: points[1]} and the ending pt is the same from points[2] and [3].
    2. Subtract the x's to get length and y's to get the height.
    3. Multiply each by .1 to find the delta X & Y.
    4. Subtract deltaX & Y from the line X & Y to find the new drawing point
    5. Draw you arrow from there using the same points[] array as the line, and scale of 1.2.

    Job done. I would not have thought of your cunning solution of manipulating the scale - I would likely have computed the end point in the same manner as the start point and drawn the arrow between them at a scale of 1.

    I include a working snippet below - this is vanilla JS rather than react but hopefully it will show you the way.

    The black line is the original Konva line, and the arrow position is computed as per my explanation above.

      // Set up a stage
    let     
        // Konva housekeeping
        stage = new Konva.Stage({
          container: 'container',
          width: window.innerWidth,
          height: window.innerHeight
        }), 
    
        // add a layer to draw on
        layer = new Konva.Layer(),
    
        points = [0, 0, 250, -50],
        drawX = 50,
        drawY = 100
        line = new Konva.Line({
          x: drawX,
          y: drawY,
          points: points,
          stroke: 'black',
          strokeWidth: 3
        });
        
    // Add the layer to the stage and shape to layer
    stage.add(layer);
    
    
    // Make a couple of points to give start and end of line for ease
    let ptStart = {x: points[0], y: points[1]},
        ptEnd =  {x: points[2], y: points[3]};
    
    // Compute the width and height of the line
    let sz = {
      width: Math.abs(ptEnd.x - ptStart.x),
      height: Math.abs(ptEnd.y - ptStart.y),
    }
    
    // Compute x & y size of 10% increase
    let adj = {
      width: sz.width * 0.1,
      height: sz.height * 0.1
    }
    
    // Compute new position of arrow.
    drawX = drawX - adj.width;
    drawY = drawY + adj.height
    
    // add the arrow at the new position and scaled x 1.2
    let arrow = new Konva.Arrow({
          x: drawX,
          y: drawY,
          points: points,
          stroke: 'magenta',
          strokeWidth: 6,
          opacity: 0.2,
          pointerAtBeginning: true,
          scaleX: 1.2,
          scaleY: 1.2
        });
    
    // Job done
    layer.add(line, arrow)
    stage.draw();
    * {
      box-sizing: border-box;
    }
    
    body {
      margin: 10;
      padding: 10;
      overflow: hidden;
      background-color: #f0f0f0;
    }
    
    #container {
      border: 1px solid silver;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://unpkg.com/konva@^3/konva.min.js"></script>
      <div id="container"></div>