Javascript 2D Library Konva.js

Code Snippets JS 2D Library Konva.js
Share:

About

In this post, we’ll take a look at the Konva.js.

Konva is a pretty nice 2D javascript graphics library for the browser. I discovered it when I needed a 2D library for a project at work. It supports both Vue and React frameworks. The reason a library is useful compared to just drawing objects on a canvas is that it provides you with an easy way to draw, style, find, manipulate and attach events to objects. 

In the first example, we’ll learn how to draw a grid, add objects and snap those objects to the grid. In the second example, we’ll learn how to draw/move/delete lines, how to calculate the drawing angle and restrict it to 90 degrees.

Full code can be found hereAlso, check out the official documentation as it has a lot of great code examples.

Note: The application architecture demonstrated here is not the best. If you want to make an app that utilizes konva consider organizing the project files and functions, classes, … differently than I did and define some data structure to store your state. These are just examples to demonstrate how to implement the features.

Item Snapping On A Grid:

  • Add Item
First I will include the konva.js library and the js file containing my code. Then there is just some basic styling, the layout for the toolbar and a div with a “container” id(this is where the canvas will appear).
index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Konva.js Object Grid Snapping Demo</title>
    </head>
    <script src="code.js"></script>
    <script src="konva.min.js"></script>
    <style>
        body{
            font-family: Arial, Helvetica, sans-serif;
        }

        .toolBarItem{
            width: 75px;
            height: auto;
            border: 1px solid black;
            list-style: none;
            text-align: center;
            float: left;
            padding: 2px;
        }

        #toolbar{
            padding: 0px;
            margin: 0px;
        }

        .row {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            width: 100%;
        }

        .column {
            display: flex;
            flex-direction: column;
            flex-basis: 100%;
            flex: 1;
        }
    </style>
    <body>
        <div>
            <h1>Konva.js Object Grid Snapping Demo</h1>
              
            <div>
                <div class="row">
                    <ul id="toolbar">
                        <li class="toolBarItem">
                            <select id="backgroundSelection">
                                <option value="lines">Lines</option>
                                <option value="dottedLines">Dots</option>
                                <option value="none">None</option>
                              </select>
                        </li>
                        <li id="add" class="toolBarItem">Add Item</li>
                    </ul>
                </div>
                <div id="container" class="row"></div>
            </div>
        </div>
    </body>
</html>
See code comments for further explanation.
code.js
//Global variables///////////////////////////////

let app;

/////////////////////////////////////////////////


//Main///////////////////////////////////////////

//Run this when the HTML gets loaded.
window.onload = () => {
  let settings = {
    containerSizeX: 1000, 
    containerSizeY: 700, 
    blockSnapSize: 20,
    draggable: false //enable this if you want to be able to drag the canvas around.
  };

  //Make an instance of the app.
  app = new KonvaDemoApp(settings);
}

/////////////////////////////////////////////////


//Code///////////////////////////////////////////

class KonvaDemoApp{

  //Initialize///////////////////////////////////////

  constructor(settings){
    //Add settings.
    this.settings = settings;

    //No currently selected item.
    this.selectedItem = null;

    //Initialize container.
    this.stage = this.makeContainer();

    //Draw background grid. (stage ref., dots or lines, block snap size)
    this.drawGrid("lines");

    //Add the layer for the items.
    let itemLayer = new Konva.Layer({id:"itemLayer"});
    //Add the new layer.
    this.stage.add(itemLayer);

    //Register toolbar controls events.
    this.registerControls();
  }

  //////////////////////////////////////////////////


  //////////////////////////////////////////////////

  makeContainer(){
    return new Konva.Stage({
      container: "container",
      width: this.settings.containerSizeX, 
      height: this.settings.containerSizeY,
      x:0,
      y:0,
      scaleX:1,
      scaleY:1,
      draggable: this.settings.draggable
    });
  }

  drawGrid(gridType){

    //Make a new layer for the grid.
    let gridLayer = new Konva.Layer({id:"grid"});

    const padding = this.settings.blockSnapSize;

    //Grid size.
    const height = this.stage.attrs.height*15;
    const width = this.stage.attrs.width*15;

    if(gridType == "dottedLines"){
      //Draw dotted lines for background to improve performance vs drawing dots as individual objects.
      for(var i = /*-(width / padding)*/0; i < width / padding; i++){
        gridLayer.add(new Konva.Line({
          points: [Math.round(i * padding) + 0.5, 0, Math.round(i * padding) + 0.5, height],
          stroke: "#000",
          strokeWidth: 3,
          name: "backgroundLine",
          lineCap: "round",
          lineJoin: "round",
          dash: [ 0, this.settings.blockSnapSize, 0, this.settings.blockSnapSize ]
        }));
      }

      gridLayer.add(new Konva.Line({points: [0,0,10,10]}));
    }

    /*
    //Draw dots as individual objects.
    if(gridType == "dots"){
        //Draw dots for backgrouond.
        for(var i = 0; i < width1 / padding; i++){
            for(var j = 0; j < height1 / padding; j++){
                gridLayer.add(new Konva.Circle({
                    x: Math.round(i * padding),
                    y: Math.round(j * padding),
                    stroke: "#000",
                    strokeWidth: 0,
                    fill: "#000",
                    radius: 1,
                    name: "backgroundDot"
                }));
            }
        }
    }
    */
    
    if(gridType == "lines"){
      //Draw lines for backgrouond.
      for(var i = 0; i < width / padding; i++){
        gridLayer.add(new Konva.Line({
          points: [Math.round(i * padding) + 0.5, 0, Math.round(i * padding) + 0.5, height],
          stroke: "#000",
          strokeWidth: 0.5,
          name: "backgroundLine"
        }));
      }

      gridLayer.add(new Konva.Line({points: [0,0,10,10]}));

      for(var j = 0; j < height / padding; j++){
        gridLayer.add(new Konva.Line({
          points: [0, Math.round(j * padding), width, Math.round(j * padding)],
          stroke: "#000",
          strokeWidth: 0.5,
          name: "backgroundLine"
        }));
      }
    }

    //Add layer to stage.
    this.stage.add(gridLayer);

    if(this.stage.find("Layer").length > 1) //Another layer must be present in stage to be able to use setZIndex().
      gridLayer.setZIndex(0); //Will be above any lower numbered layers.

  }

  /////////////////////////////////////////////////


  //Feature specific functions/////////////////////

  addItem(x, y, width, height){
    //Make the width/height fit(match) the grid.
    width = this.settings.blockSnapSize * width;
    height = this.settings.blockSnapSize * height

    //Add a new rectangle.
    let rectangle = this.newRectangle(0, 0,  width, height);
    //Add a snap location indicator rectangle for the new rectangle.
    let snapLocationRectangle = this.newSnapLocationRectangle(x, y, width, height);

    //Make item group.
    let item = new Konva.Group({name: "itemGroup"}); 
    item.add(rectangle);
    item.add(snapLocationRectangle);

    //Add snapLocationRectangle to a new layer.
    this.stage.find("#itemLayer")[0].add(item);
  }

  newSnapLocationRectangle(x, y, width, height){
    let snapRect = new Konva.Rect({
      name: "snapLocationRectangle",
      x: x,
      y: y,
      width: width,
      height: height,
      fill: "#26dd02",
      opacity: 0.7,
      stroke: "#168201",
      strokeWidth: 4
    });
    
    //Keep the snap location rectangle hidden by default.
    snapRect.hide();

    return snapRect;
  }
  
  newRectangle(x, y, width, height) {
    //Make new object.
    let rectangle = new Konva.Rect({
      x: x,
      y: y,
      width: width,
      height: height,
      fill: "#fff",
      stroke: "#ddd",
      strokeWidth: 1,
      shadowColor: "black",
      shadowBlur: 2,
      shadowOffset: { x : 1, y : 1 },
      shadowOpacity: 0.4,
      draggable: true
    });

    //Events////////////////////////////////////////////
    
    //When the object dragging starts show snap location, then move the object on top.
    rectangle.on("dragstart", (event) => {
      event.currentTarget.parent.find(".snapLocationRectangle").forEach((shape) => shape.show());
      event.currentTarget.moveToTop();

      this.stage.batchDraw();
    });
    
    //When moving stops snap item to grid and hide snap location item. 
    rectangle.on("dragend", (event) => {

      event.currentTarget.position({
        x: Math.round(rectangle.x() / this.settings.blockSnapSize) * this.settings.blockSnapSize,
        y: Math.round(rectangle.y() / this.settings.blockSnapSize) * this.settings.blockSnapSize
      });

      this.stage.batchDraw();
      event.currentTarget.parent.find(".snapLocationRectangle").forEach((shape) => shape.hide());
    });
    
    //On move snap location indication rectangle.
    rectangle.on("dragmove", (event) => {
      event.currentTarget.parent.find(".snapLocationRectangle").forEach((shape) => shape.position({
        x: Math.round(rectangle.x() / this.settings.blockSnapSize) * this.settings.blockSnapSize,
        y: Math.round(rectangle.y() / this.settings.blockSnapSize) * this.settings.blockSnapSize
      }));

      this.stage.batchDraw();
    });

    //On item click set it as the selected item.
    rectangle.on("click", (event) => {
      this.selectedItem = rectangle;
    });

    ///////////////////////////////////////////////////////

    return rectangle;
  }

  /////////////////////////////////////////////////


  //Events/////////////////////////////////////////

  registerControls(){
    //Grid selection.
    document.getElementById("backgroundSelection").addEventListener("change", (event) => {
      //Clear current grid.
      this.stage.find("#grid")[0].destroy();
      //Draw grid with lines.
      this.drawGrid(event.target.value);
      //Redraw stage.
      this.stage.draw();
    });

    //Delete selected item.
    document.addEventListener('keydown', (event) => {
      if(event.keyCode == 46){
        this.selectedItem.destroy();
        this.selectedItem = null;
        this.stage.batchDraw();
      }
    });

    //Add item.
    document.getElementById("add").addEventListener("click", (event) => {
      //Add item.
      this.addItem(0, 0, 6, 3);
      //Redraw stage.
      this.stage.draw();
    });
  }

  /////////////////////////////////////////////////
}

Drawing Lines:

First I will include the konva.js library and the js file containing my code. Then there is just some basic styling, the layout for the toolbar and a div with a “container” id(this is where the canvas will appear).
index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Konva.js Drawing Lines Demo</title>
    </head>
    <script src="code.js"></script>
    <script src="konva.min.js"></script>
    <style>
        body{
            font-family: Arial, Helvetica, sans-serif;
        }

        .toolBarItem{
            width: 120px;
            height: auto;
            border: 1px solid black;
            list-style: none;
            text-align: center;
            float: left;
            padding: 2px;
        }

        #toolbar{
            padding: 0px;
            margin: 0px;
        }

        .row {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            width: 100%;
        }

        .column {
            display: flex;
            flex-direction: column;
            flex-basis: 100%;
            flex: 1;
        }
    </style>
    <body>
        <div>
            <h1>Konva.js Drawing Lines Demo</h1>
              
            <div>
                <div class="row">
                    <ul id="toolbar">
                        <li class="toolBarItem">
                            <select id="backgroundSelection">
                                <option value="lines">Lines</option>
                                <option value="dottedLines">Dots</option>
                                <option value="none">None</option>
                              </select>
                        </li>
                        <li class="toolBarItem"><label>Angle Snap</label><input type="checkbox" onchange="angleSnapping(event)" /></li>
                        <li class="toolBarItem"><label>Draw</label><input id="drawLine" checked="checked" name="tool" type="radio" onchange="setTool(event)" /></li>
                        <li class="toolBarItem" style="width: 150px;"><label>Delete</label><input id="delete" name="tool"name="tool" type="radio" onchange="setTool(event)" /></li>
                        <li class="toolBarItem" style="width: 150px;"><label>Move</label><input id="move" name="tool" type="radio" onchange="setTool(event)" /></li>
                    </ul>
                </div>
                <div id="container" class="row"></div>
            </div>
        </div>
    </body>
</html>
See code comments for further explanation.
code.js
//Global variables///////////////////////////////

let app;

/////////////////////////////////////////////////


//Functions//////////////////////////////////////

function registerControls(){
  //Grid selection.
  document.getElementById("backgroundSelection").addEventListener("change", (event) => {
    //Clear current grid.
    app.stage.find("#grid")[0].destroy();
    //Draw grid with lines.
    app.drawGrid(event.target.value);
    //Redraw stage.
    app.stage.draw();
  });
}

function setTool(eventArgs){
  app.action = eventArgs.target.id;
}

function angleSnapping(eventArgs){
  app.angleSnapping = eventArgs.target.checked;
}

/////////////////////////////////////////////////


//Main///////////////////////////////////////////

//Run this when the HTML gets loaded.
window.onload = () => {
  let settings = {
    containerSizeX: 1000, 
    containerSizeY: 700, 
    blockSnapSize: 20,
    draggable: false //enable this if you want to be able to drag the canvas around.
  };

  //Make an instance of the app.
  app = new KonvaDemoApp(settings);
}

/////////////////////////////////////////////////


//Code///////////////////////////////////////////

class KonvaDemoApp{

  //Initialize/////////////////////////////////////

  constructor(settings){
    //Add settings.
    this.settings = settings;

    //No currently selected item.
    this.selectedItem = null;

    //Initialize container.
    this.stage = this.makeContainer();

    this.action = "drawLine";
    this.lineStatus = "start";
    this.angleSnapping = false;
    this.lineStartX = 0;
    this.lineStartY = 0;

    this.registerStageEvents(this.stage);

    //Draw background grid. (stage ref., dots or lines, block snap size)
    this.drawGrid("lines");

    //Add the layer for the items.
    let itemLayer = new Konva.Layer({id:"itemLayer"});
    //Add the new layer.
    this.stage.add(itemLayer);

    //Register toolbar controls events.
    registerControls();
  }

  /////////////////////////////////////////////////

  
  /////////////////////////////////////////////////

  makeContainer(){
    return new Konva.Stage({
      container: "container",
      width: this.settings.containerSizeX, 
      height: this.settings.containerSizeY,
      x:0,
      y:0,
      scaleX:1,
      scaleY:1,
      draggable: this.settings.draggable
    });
  }

  drawGrid(gridType){

    //Make a new layer for the grid.
    let gridLayer = new Konva.Layer({id:"grid"});

    const padding = this.settings.blockSnapSize;

    //Grid size.
    const height = this.stage.attrs.height*15;
    const width = this.stage.attrs.width*15;

    if(gridType == "dottedLines"){
      //Draw dotted lines for background to improve performance vs drawing dots as individual objects.
      for(var i = /*-(width / padding)*/0; i < width / padding; i++){
        gridLayer.add(new Konva.Line({
          points: [Math.round(i * padding) + 0.5, 0, Math.round(i * padding) + 0.5, height],
          stroke: "#000",
          strokeWidth: 3,
          name: "backgroundLine",
          lineCap: "round",
          lineJoin: "round",
          dash: [ 0, this.settings.blockSnapSize, 0, this.settings.blockSnapSize ]
        }));
      }

      gridLayer.add(new Konva.Line({points: [0,0,10,10]}));
    }

    /*
    //Draw dots as individual objects.
    if(gridType == "dots"){
        //Draw dots for backgrouond.
        for(var i = 0; i < width1 / padding; i++){
            for(var j = 0; j < height1 / padding; j++){
                gridLayer.add(new Konva.Circle({
                    x: Math.round(i * padding),
                    y: Math.round(j * padding),
                    stroke: "#000",
                    strokeWidth: 0,
                    fill: "#000",
                    radius: 1,
                    name: "backgroundDot"
                }));
            }
        }
    }
    */
    
    if(gridType == "lines"){
      //Draw lines for backgrouond.
      for(var i = 0; i < width / padding; i++){
        gridLayer.add(new Konva.Line({
          points: [Math.round(i * padding) + 0.5, 0, Math.round(i * padding) + 0.5, height],
          stroke: "#000",
          strokeWidth: 0.5,
          name: "backgroundLine"
        }));
      }

      gridLayer.add(new Konva.Line({points: [0,0,10,10]}));

      for(var j = 0; j < height / padding; j++){
        gridLayer.add(new Konva.Line({
          points: [0, Math.round(j * padding), width, Math.round(j * padding)],
          stroke: "#000",
          strokeWidth: 0.5,
          name: "backgroundLine"
        }));
      }
    }

    //Add layer to stage.
    this.stage.add(gridLayer);

    if(this.stage.find("Layer").length > 1) //Another layer must be present in stage to be able to use setZIndex().
      gridLayer.setZIndex(0); //Will be above any lower numbered layers.

  }

  /////////////////////////////////////////////////


  //Helper functions///////////////////////////////

  getScaledPointerPosition(){
    const pointerPosition = this.stage.getPointerPosition();
    const stageAttrs = this.stage.attrs;
    const x = (pointerPosition.x - stageAttrs.x) / stageAttrs.scaleX;
    const y = (pointerPosition.y - stageAttrs.y) / stageAttrs.scaleY;
    return {x: x, y: y};
  }

  snapToGrid(axis){
    return Math.round(axis / this.settings.blockSnapSize) * this.settings.blockSnapSize;
  }

  angleBetweenPoints(p1, p2){
    //Angle in radians.
    //var angleRadians = Math.atan2(p2.y - p1.y, p2.x - p1.x);

    //Angle in degrees.
    let angleDeg = Math.atan2(p1.y - p2.y, p1.x - p2.x) * 180 / Math.PI;
    //Make 360 degree from 180/-180 degree.
    angleDeg = angleDeg + 180
    //Mirror.
    angleDeg = 360 - angleDeg;

    return angleDeg;
  }

  snapToGridAngle(p1, p2){
    let angle = this.angleBetweenPoints(p1, p2);

    let angleSnappedX = p2.x;
    let angleSnappedY = p2.y;

    if(angle > 0 && angle < 90){
        //1. quadrant
        if(angle < 45){
            angleSnappedX = p2.x;
            angleSnappedY = p1.y;
        }else{
            angleSnappedX = p1.x;
            angleSnappedY = p2.y;
        }
    }else if(angle > 90 && angle < 180){
        //2. quadrant
        if(angle < 135){
            angleSnappedX = p1.x;
            angleSnappedY = p2.y;
        }else{
            angleSnappedX = p2.x;
            angleSnappedY = p1.y;
        }
    }else if(angle > 180 && angle < 270){
        //3. quadrant
        if(angle < 225){
            angleSnappedX = p2.x;
            angleSnappedY = p1.y;
        }else{
            angleSnappedX = p1.x;
            angleSnappedY = p2.y;
        }
    }else if(angle > 270 && angle < 360){
        //4. quadrant
        if(angle < 315){
            angleSnappedX = p1.x;
            angleSnappedY = p2.y;
        }else{
            angleSnappedX = p2.x;
            angleSnappedY = p1.y;
        }
    }

    return { x: this.snapToGrid(angleSnappedX), y: this.snapToGrid(angleSnappedY)};
  }

  getLength(x1, y1, x2, y2){
    const distance = Math.sqrt(Math.abs(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)));
    return distance;
    //return Math.round((distance/this.settings.blockSnapSize)*100);
  }

  MakeID(prefix){
    let i = 0;    
    while(this.stage.find("#"+prefix+i)[0] != undefined && i < 1000){
        if(i > 980)
          console.log("Over " +i+ "of type " +prefix+ "in existance! This might start causing problems soon!");

        i++;
    }
    
    return prefix+i;
  }

  /////////////////////////////////////////////////


  //Event callback functions///////////////////////

  adjustDisplayGroup(){
    //Get current mouse position.
    //pos = stage.getPointerPosition();
    let pos = this.getScaledPointerPosition();

    let pos1 = { x: this.snapToGrid(this.lineStartX), y: this.snapToGrid(this.lineStartY) };
    let pos2 = { x: this.snapToGrid(pos.x), y: this.snapToGrid(pos.y) };

    //Snap to angle.
    if(app.angleSnapping)
      pos2 = this.snapToGridAngle(pos1, pos2);

    //Get array of points for line.
    let p = [ this.snapToGrid(this.lineStartX), this.snapToGrid(this.lineStartY), pos2.x, pos2.y];

    //Get length.
    let length = this.getLength(this.snapToGrid(this.lineStartX), this.snapToGrid(this.lineStartY), pos.x, pos.y);
    //Get angle.
    const angle = this.angleBetweenPoints(pos1, pos2);

    //Get length label.
    let lengthLabel = this.stage.find("#displayLabel")[0];
    //Update label text. BlockSnapSize is 20, lets say each square is 10mm, so divide by 2. 
    lengthLabel.text(Math.trunc(length/2) + "mm " +Math.trunc(angle) + " Deg.");
    //Update label position.
    lengthLabel.position({ x: pos2.x, y: pos2.y });

    //Get display line label.
    let displayLine = this.stage.find("#displayLine")[0];
    //Update display line points.
    displayLine.setPoints(p);       
    
    //Get item layer.
    let layer = this.stage.find("#itemLayer")[0];
    //Redraw layer.
    layer.batchDraw();
  }

  drawLineDisplayGroup(){
    //Get mouse position.
    //pos = stage.getPointerPosition();
    let pos = this.getScaledPointerPosition();

    //Make temp display group.
    let displayGroup = new Konva.Group({
        /*draggable: true*/
        id: "displayGroup"
    });

    //Make display line.
    let displayLine = new Konva.Line({
        points: [this.snapToGrid(pos.x), this.snapToGrid(pos.y)],
        stroke: 'black',
        tension: 1,
        strokeWidth: 6,
        draggable: false,
        id: "displayLine"
    });

    displayGroup.add(displayLine);

    //Make line start point displayCircle.
    let displayCircleStart = new Konva.Circle({
        x: this.snapToGrid(pos.x),
        y: this.snapToGrid(pos.y),
        radius: 7,
        fill: '#34eb5b',
        stroke: 'black',
        strokeWidth: 2,
        draggable: false,
        name: "circle"
    });

    displayGroup.add(displayCircleStart);  
    
    let lengthLabel = new Konva.Text({
      x: 0,
      y: 0,
      text: "",
      fontSize: 20,
      fontFamily: "Calibri",
      fill: "green",
      id: "displayLabel"
    });

    displayGroup.add(lengthLabel);   

    //Save line starting postions.
    this.lineStartX = pos.x;
    this.lineStartY = pos.y;

    //Get main item layer.
    let layer = this.stage.find("#itemLayer")[0];
    //Add current display group to the layer.
    layer.add(displayGroup);
    //Redraw layer.
    layer.draw();

    //Change line status to being drawn.
    this.lineStatus = "drawing";
  }

  addNewLine(){
    //Get mouse position, adjusted for current scalling/zoom.
    let pos = this.getScaledPointerPosition(); //stage.getPointerPosition(); //non adjusted mouse position.
    let layer = this.stage.find("#itemLayer")[0];

    //Snap starting position coordinates to grid.
    let pos1 = { x: this.snapToGrid(this.lineStartX), y: this.snapToGrid(this.lineStartY) };
    //Snap end position coordinates to grid.
    let pos2 = { x: this.snapToGrid(pos.x), y: this.snapToGrid(pos.y) };

    //Snap to angle.
    if(app.angleSnapping)
      pos2 = this.snapToGridAngle(pos1, pos2);

    //Grid snap.
    const p = [this.snapToGrid(this.lineStartX), this.snapToGrid(this.lineStartY), pos2.x, pos2.y];

    //Get length.
    let length = this.getLength(pos1.x, pos1.y, pos2.x, pos2.y);

    //Make sure the line length spans at least one block.
    if(length < this.settings.blockSnapSize){
      this.stage.find("#displayGroup")[0].destroy();
      layer.draw();
      return;
    }

    //Line Group/////////////////////////////////////////////////////////////

    //Make new group for the line and it's endpoint circles.
    let group = new Konva.Group({
        name: "lineGroup"
    });

    //Make line start point circle.
    let circleStart = new Konva.Circle({
        x: pos1.x,
        y: pos1.y,
        radius: 7,
        fill: '#34eb5b',
        stroke: 'black',
        strokeWidth: 2,
        draggable: true,
        name: "circleStart"
    });

    //Make line end point circle.
    let circleEnd = new Konva.Circle({
        x: pos2.x,
        y: pos2.y,
        radius: 7,
        fill: 'red',
        stroke: 'black',
        strokeWidth: 2,
        draggable: true,
        name: "circleEnd"
    });

    //Make drawn line.
    let drawnLine = new Konva.Line({
        points: p,
        tension: 0,
        fill: 'red',
        stroke: 'black',
        strokeWidth: 6,
        draggable: false,
        name: "drawLine",
        id: this.MakeID("drawLine"),
        length: length 
    });

    /////////////////////////////////////////////////////////////////////////

    //Add events.
    circleStart.on('dragmove', this.adjustLineCirclePoint);
    circleEnd.on('dragmove', this.adjustLineCirclePoint);

    //Add to group.
    group.add(drawnLine);
    group.add(circleEnd);
    group.add(circleStart);

    //Event used to delete a line.
    group.on("click", this.groupClicked);

    //Add to layer.
    layer.add(group);

    //Put group on the bottom so line doesn't overlap existing line endpoint circle.
    group.moveToBottom();
  }

  adjustLineCirclePoint(event){  
    if(app.action != "move")
      return;

    //Get mouse position, adjusted for current scalling/zoom.
    let pos = app.getScaledPointerPosition(); //stage.getPointerPosition(); //non adjusted mouse position.

    //Get Line from group.
    let currentLine = event.target.parent.find('.drawLine')[0];
    
    //Snap starting position coordinates to grid.
    let pos1 = { x: currentLine.attrs.points[0], y: currentLine.attrs.points[1] };
    if(event.target.attrs.name == "circleStart")
      pos1 = { x: currentLine.attrs.points[2], y: currentLine.attrs.points[3] };
    
    //Snap end position coordinates to grid.
    let pos2 = { x: app.snapToGrid(pos.x), y: app.snapToGrid(pos.y) };

    //Snap to angle.
    if(app.angleSnapping)
      pos2 = app.snapToGridAngle(pos1, pos2);

    //Get length.
    let length = app.getLength(pos1.x, pos1.y, pos2.x, pos2.y);
    //Do nothing if set length is too short.
    if(length < app.settings.blockSnapSize)
      return;

    //Set the targets x,y to new grid snaped mouse pointer position.  
    event.target.attrs.x = pos2.x;
    event.target.attrs.y = pos2.y;

    let points = [pos1.x, pos1.y, pos2.x, pos2.y];  //Make an array from circle start/end postiotns and assign the new position values array to the line(so that the line will be drawn between the two circles).
    if(event.target.attrs.name == "circleStart")
      points = [pos2.x, pos2.y, pos1.x, pos1.y];  //Make an array from circle start/end postiotns and assign the new position values array to the line(so that the line will be drawn between the two circles). 


    //Set the curently selected line points.    
    currentLine.setPoints(points);
    
    //Get items layer.
    let layer = app.stage.find("#itemLayer")[0];
    //Redraw layer.
    layer.batchDraw();
  }

  groupClicked(event){
    if(app.action != "delete")
      return

    //Get parent layer clicked of item(so we can redraw it after).
    let parentLayer = event.currentTarget.parent;
    //Delete line group and all of its children.
    event.currentTarget.destroy();
    //Redraw layer.
    parentLayer.draw();
  }

  /////////////////////////////////////////////////


  //Events/////////////////////////////////////////

  registerStageEvents(stage){
    //Get container.
    let container = stage.container();

    container.addEventListener('mousedown', function(e) {
        e.preventDefault();

        if(app.action == "drawLine" && app.lineStatus == "start")
          app.drawLineDisplayGroup(); //Draw temporary display group.
    });
    
    container.addEventListener('mouseup', function(e) {
      e.preventDefault();

      if(app.action == "drawLine" && app.lineStatus == "drawing"){
        //Make final line.
        app.addNewLine();
  
        //Delete temp. display elements.
        app.stage.find("#displayGroup")[0].destroy();
        app.stage.find("#itemLayer")[0].draw();

        //Reset line status back to default to enable drawing a new line.
        app.lineStatus = "start";
      }
    });

    container.addEventListener('mousemove', function(e) {
      if(app.lineStatus == "drawing")
        app.adjustDisplayGroup();
    });
  }

  /////////////////////////////////////////////////
}
Share:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.

Advertisment ad adsense adlogger