Fabric.js: How to draw a polygon with the mouse

I want to draw a fabric.Polygon

with mouse interaction in Fabric.js. I made a little jsfiddle to show my actual state: http://jsfiddle.net/Kienz/ujefxh7w/

Pressing the ESC key cancels the "interactive drawing mode" and the polygon ends. But now the position of the polygon is wrong (controls are right).

Anyone have an idea?

+3


source to share


3 answers


Defining the problem

When a polygon is added as a static object (in the sense that the points will not change), it left, right, minX, minY, width, height

and the center point can be calculated based on the provided points.

But when you need to create a polygon dynamically, the problem arises. Once called, setCoords()

it calculates the locations of the move fields, but based on the incorrect width and height information.

Remember , when you create a 1-point polygon, it has a width and height of 0, and the top-left is that one point.

Decision

Correct the size first

_calcDimensions()

calculates width

, height

, minX

and minY

a polygon. minX

and minY

are minima at all points.

This tells us how far to the left and above the old center some points were placed. We need to use this information to move the old left to the correct location. The new top left point is the old center point translated with information minX

and minY

.

var oldC = polygon.getCenterPoint();
polygon._calcDimensions();
polygon.set({
  left: oldC.x + polygon.get('minX'),
  top: oldC.y + polygon.get('minY')
});

      



Fix points

After moving the center by some vector v

(the result of changing the properties left

, right

and width

, height

). We need to update all the points with the opposite value v

.

var newC = polygon.getCenterPoint();
var moveX = -(newC.x-oldC.x);
var moveY = -(newC.y-oldC.y)
var adjPoints = polygon.get("points").map(function(p) {
  return {
    x: p.x + moveX,
    y: p.y + moveY
  };
});

      

Complete the Fiddle for a complete example: http://jsfiddle.net/orian/dyxjkmes/5/

/**
 * fabric.js template for bug reports
 *
 * Please update the name of the jsfiddle (see Fiddle Options).
 * This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/all.js).
 */

// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');

// Do some initializing stuff
fabric.Object.prototype.set({
  transparentCorners: false,
  cornerColor: 'rgba(102,153,255,0.5)',
  cornerSize: 12,
  padding: 7
});

// ADD YOUR CODE HERE
var mode = "add",
  currentShape;

canvas.observe("mouse:move", function(event) {
  var pos = canvas.getPointer(event.e);
  if (mode === "edit" && currentShape) {
    var points = currentShape.get("points");
    points[points.length - 1].x = pos.x - currentShape.get("left");
    points[points.length - 1].y = pos.y - currentShape.get("top");
    currentShape.set({
      points: points
    });
    canvas.renderAll();
  }
});

canvas.observe("mouse:down", function(event) {
  var pos = canvas.getPointer(event.e);

  if (mode === "add") {
    var polygon = new fabric.Polygon([{
      x: pos.x,
      y: pos.y
    }, {
      x: pos.x + 0.5,
      y: pos.y + 0.5
    }], {
      fill: 'blue',
      opacity: 0.5,
      selectable: false
    });
    currentShape = polygon;
    canvas.add(currentShape);
    mode = "edit";
  } else if (mode === "edit" && currentShape && currentShape.type === "polygon") {
    var points = currentShape.get("points");
    points.push({
      x: pos.x - currentShape.get("left"),
      y: pos.y - currentShape.get("top")
    });
    currentShape.set({
      points: points
    });
    canvas.renderAll();
  }
});

    fabric.util.addListener(window, 'keyup', function(e) {
      if (e.keyCode === 27) {
        if (mode === 'edit' || mode === 'add') {
          mode = 'normal';

          var points = currentShape.get("points");
          points.pop();
          currentShape.set({
            points: points
          });

          var oldC = currentShape.getCenterPoint();
          currentShape._calcDimensions();

          var xx = currentShape.get("minX");
          var yy = currentShape.get("minY");
          currentShape.set({
            left: currentShape.get('left') + xx,
            top: currentShape.get('top') + yy
          });

          var pCenter = currentShape.getCenterPoint();
          var adjPoints = currentShape.get("points").map(function(p) {
            return {
              x: p.x - pCenter.x + oldC.x,
              y: p.y - pCenter.y + oldC.y
            };
          });
          currentShape.set({
            points: adjPoints,
            selectable: true
          });

          canvas.setActiveObject(currentShape);
          currentShape.setCoords();
          canvas.renderAll();
        } else {
          mode = 'add';
        }
        currentShape = null;
      }
    })
      

canvas {
  border: 1px solid #999;
}

img {
  display: none;
}
      

<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
      

Run codeHide result


Copy & paste

function fixPoly(poly) {
  var oldC = poly.getCenterPoint();
  poly._calcDimensions();
  poly.set({
    left: poly.get('left') + poly.get("minX"),
    top: poly.get('top') + poly.get("minY")
  });

  var pCenter = poly.getCenterPoint();
  poly.get("points").forEach((p) => {
    p.x -= pCenter.x - oldC.x;
    p.y -= pCenter.y - oldC.y
  });
  poly.setCoords();
};

      

+1


source


There are two solutions to this problem, which are very simple:

Solution # 1

Just remove the polygon from the canvas and re-create it (using the new fabric.Polygon (...)) for each point added!

Pros and Cons: Yes, you will get a slightly worse performance because the canvas will be republished twice, but you will save time recalculating the coordinates each time.

You can check out this solution in the snippet below or in this forked fiddle .

/**
 * fabric.js template for bug reports
 *
 * Please update the name of the jsfiddle (see Fiddle Options).
 * This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/all.js).
 */

// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');

// Do some initializing stuff
fabric.Object.prototype.set({
  transparentCorners: false,
  cornerColor: 'rgba(102,153,255,0.5)',
  cornerSize: 12,
  padding: 7
});

// ADD YOUR CODE HERE
var mode = "add",
  currentShape;

canvas.observe("mouse:move", function(event) {
  var pos = canvas.getPointer(event.e);
  if (mode === "edit" && currentShape) {
    var points = currentShape.get("points");
    points[points.length - 1].x = pos.x;
    points[points.length - 1].y = pos.y;
    currentShape.set({
      points: points
    });
    canvas.renderAll();
  }
});

canvas.observe("mouse:down", function(event) {
  var pos = canvas.getPointer(event.e);

  if (mode === "add") {
    var polygon = new fabric.Polygon([{
      x: pos.x,
      y: pos.y
    }, {
      x: pos.x + 0.5,
      y: pos.y + 0.5
    }], {
      fill: 'blue',
      opacity: 0.5,
      selectable: false
    });
    currentShape = polygon;
    canvas.add(currentShape);
    mode = "edit";
  } else if (mode === "edit" && currentShape && currentShape.type === "polygon") {
    var points = currentShape.get("points");
    points.push({
      x: pos.x,
      y: pos.y
    });
    canvas.remove(currentShape);
    currentShape = new fabric.Polygon(points, {
      fill: 'blue',
      opacity: 0.5,
      selectable: false
    });
    canvas.add(currentShape);
  }
});

fabric.util.addListener(window, 'keyup', function(e) {
  if (e.keyCode === 27) {
    if (mode === 'edit' || mode === 'add') {
      mode = 'normal';
      var points = currentShape.get('points');
      canvas.remove(currentShape);
      currentShape = new fabric.Polygon(points, {
        fill: 'blue',
        opacity: 0.5,
        selectable: true
      });
      canvas.add(currentShape);
    } else {
      mode = 'add';
    }
    currentShape = null;
  }
})
      

canvas {
  border: 1px solid #999;
}
img {
  display: none;
}
      

<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
      

Run codeHide result




Decision # 2

recalculate the dimensions of the polygon each time you do it in the constructor of the Polygon class. Excerpt from the code:

currentShape.set({
    points: points
});
currentShape._calcDimensions();
currentShape.set({
    left: currentShape.minX,
    top: currentShape.minY,
    pathOffset: {
        x: currentShape.minX + currentShape.width / 2,
        y: currentShape.minY + currentShape.height / 2
    }
});
currentShape.setCoords();
canvas.renderAll();

      

Pros and Cons: Better performance (probably noticeable on heavily loaded canvases), but you'll have more code since you have to add it to both handlers.

You can check it out in the snippet below or in this forked fiddle .

/**
 * fabric.js template for bug reports
 *
 * Please update the name of the jsfiddle (see Fiddle Options).
 * This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/all.js).
 */

// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');

// Do some initializing stuff
fabric.Object.prototype.set({
  transparentCorners: false,
  cornerColor: 'rgba(102,153,255,0.5)',
  cornerSize: 12,
  padding: 7
});

// ADD YOUR CODE HERE
var mode = "add",
  currentShape;

canvas.observe("mouse:move", function(event) {
  var pos = canvas.getPointer(event.e);
  if (mode === "edit" && currentShape) {
    var points = currentShape.get("points");
    points[points.length - 1].x = pos.x;
    points[points.length - 1].y = pos.y;
    currentShape.set({
      points: points
    });
    canvas.renderAll();
  }
});

canvas.observe("mouse:down", function(event) {
  var pos = canvas.getPointer(event.e);

  if (mode === "add") {
    var polygon = new fabric.Polygon([{
      x: pos.x,
      y: pos.y
    }, {
      x: pos.x + 0.5,
      y: pos.y + 0.5
    }], {
      fill: 'blue',
      opacity: 0.5,
      selectable: false
    });
    currentShape = polygon;
    canvas.add(currentShape);
    mode = "edit";
  } else if (mode === "edit" && currentShape && currentShape.type === "polygon") {
    var points = currentShape.get("points");
    points.push({
      x: pos.x,
      y: pos.y
    });
    currentShape.set({
      points: points
    });
    currentShape._calcDimensions();
    currentShape.set({
      left: currentShape.minX,
      top: currentShape.minY,
      pathOffset: {
        x: currentShape.minX + currentShape.width / 2,
        y: currentShape.minY + currentShape.height / 2
      }
    });
    currentShape.setCoords();
    canvas.renderAll();
  }
});

fabric.util.addListener(window, 'keyup', function(e) {
  if (e.keyCode === 27) {
    if (mode === 'edit' || mode === 'add') {
      mode = 'normal';
      currentShape.set({
        selectable: true
      });
      currentShape._calcDimensions();
      currentShape.set({
        left: currentShape.minX,
        top: currentShape.minY,
        pathOffset: {
          x: currentShape.minX + currentShape.width / 2,
          y: currentShape.minY + currentShape.height / 2
        }
      });
      currentShape.setCoords();
      canvas.renderAll();
    } else {
      mode = 'add';
    }
    currentShape = null;
  }
})
      

canvas {
  border: 1px solid #999;
}
img {
  display: none;
}
      

<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
      

Run codeHide result


+1


source


you can fix the bouncing polygon by setting initialization points X and originY on initialization.

        var polygon = new fabric.Polygon([{
        x: pos.x,
        y: pos.y
    }, {
        x: pos.x + 0.5,
        y: pos.y + 0.5
    }], {
        fill: 'blue',
        opacity: 0.5,
        selectable: false,
        originX: pos.x,
        originY: pos.y
    });

      

See it here: http://jsfiddle.net/ujefxh7w/48/

I don't know why the selection box is shifted.

EDIT: Found that reloading the canvas object fixes the select box

serializing and loading serialized objects does the trick:

var json = JSON.stringify(canvas);
canvas.loadFromJSON(json, function() {
    canvas.renderAll();
});

      

http://jsfiddle.net/ujefxh7w/50/

-1


source







All Articles