Struggling to remove multiple objects from the fabric canvas. Everything seems to be in working order but when the code runs it does not remove the multiple selected objects from the canvas.
service.deleteSelectedObject = function () {
var selectedObject = service.canvas.getActiveObject();
var selectedMultipleObjects = service.canvas.getActiveGroup();
var data = {};
// get object id
// get selected objects from canvas
if (selectedObject) {
data = {
type: 'whiteboard::delete',
id: selectedObject.id
};
delete service.objectMap[selectedObject.id];
service.canvas.remove(selectedObject);
} else {
data = {
type: 'whiteboard::delete',
object: selectedMultipleObjects
};
console.log(selectedMultipleObjects);
selectedMultipleObjects._objects.forEach(function (object, key) {
console.log(object);
service.canvas.remove(object);
});
}
signalService.sendMessage(service.recipient, data);
};
I should point out that all of these console logs do pass what they should. As well as the problem is occuring in the else statement the first part of this code works how it should
please let me know if you need any further information.
Object of active group are indeed on the canvas but removing them from canvas will not remove them from the rendering pipeline if they are in active group.
When fabricjs draws everything, it checks for object on canvas and presence of activeGroup. If activeGroup is present, its objects get rendered later, regardless they are on canvas or not.
So you are effectively removing the objects from the canvas, but untill you clear the selection of the active group, the objects are still there.
Correct procedure is removing the object from the canvas and from the activeGroup.
check the snippet, select all objects with mouse and the press remove all button.
var canvas = new fabric.Canvas('canvas');
var r1 = new fabric.Rect({width:50,height:50});
var r2 = new fabric.Rect({width:50,height:50,top:110, left:110});
var r3 = new fabric.Rect({width:50,height:50,top:60, left:60});
canvas.add(r1,r2,r3);
function removeAll() {
var o = canvas.getActiveGroup();
o._objects.forEach(function (object, key) {
canvas.remove(object);
o.removeWithUpdate(object);
});
canvas.discardActiveGroup();
canvas.renderAll();
}
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js" ></script>
<input type="button" onClick="removeAll()" value="remove"/>
<canvas id="canvas" width=400 height=400 style="height:500px;width:500px;"></canvas>
After version 2 of fabric.js, there is no getActiveGroup. To delete multiple objects, you need to use forEachObject. This example builds on the previous one and can delete single objects or grouped objects.
var canvas = new fabric.Canvas('canvas');
var r1 = new fabric.Rect({width:50,height:50});
var r2 = new fabric.Rect({width:50,height:50,top:110, left:110});
var r3 = new fabric.Rect({width:50,height:50,top:60, left:60});
canvas.add(r1,r2,r3);
function deleteObj(){
var doomedObj = canvas.getActiveObject();
if (doomedObj.type === 'activeSelection') {
// active selection needs a reference to the canvas.
doomedObj.canvas = canvas;
doomedObj.forEachObject(function(obj) {
canvas.remove(obj);
});
}//endif multiple objects
else{
//If single object, then delete it
var activeObject = canvas.getActiveObject();
//How to delete multiple objects?
//if(activeObject !== null && activeObject.type === 'rectangle') {
if(activeObject !== null ) {
canvas.remove(activeObject);
}
}//end else there's a single object
}//end deleteObj
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.min.js" ></script>
<input type="button" onClick="deleteObj()" value="remove"/>
<canvas id="canvas" width=400 height=400 style="height:500px;width:500px;"></canvas>
Related
I have two functions deleteNode, which accepts a node id and deleteEdge ,which takes a edge object as input.deleteEdge is triggered when a button is pressed and it deletes a particular node and also deletes all its connected edges. However my code only seems to delete the first edge that was added in the array.Not sure what is causing this problem.
Here $scope.nodes is an array of objects with each object looks like this:
var node = {
id:$scope.node_id,
Yedges_id:[],
Nedges_id:[],
}
And $scope.edges is an array of objects where each edge looks like :
var edge = {
id: $scope.edge_id,
start_id: start,
end_id: end,
}
$scope.deleteEdge = function(edge){
var index = $scope.edges.indexOf(edge)
var start_idx = $scope.nodes.findIndex(node=>node!==undefined && node.id==edge.start_id);
var end_idx = $scope.nodes.findIndex(node=>node!==undefined && node.id==edge.end_id);
var Ystart_edge_idx = $scope.nodes[start_idx].Yedges_id.indexOf(edge.id);
var Nstart_edge_idx = $scope.nodes[start_idx].Nedges_id.indexOf(edge.id);
var end_edge_idx = $scope.nodes[end_idx].Yedges_id.indexOf(edge.id);
if(Ystart_edge_idx!==-1){
$scope.nodes[start_idx].Yedges_id.splice(Ystart_edge_idx,1);
}
if(Nstart_edge_idx!==-1){
$scope.nodes[start_idx].Nedges_id.splice(Nstart_edge_idx,1);
}
$scope.nodes[end_idx].Yedges_id.splice(end_edge_idx,1);
$scope.edges.splice(index, 1);
}
$scope.deleteNode = function(id){
var index = $scope.nodes.findIndex(x=>x!==undefined && x.id===id);
$scope.nodes[index].Yedges_id.forEach(edge_id => {
var edge = $scope.edges.filter(edge=>edge.id==edge_id)
$scope.deleteEdge(edge[0]);
});
$scope.nodes[index].Nedges_id.forEach(edge_id => {
var edge = $scope.edges.filter(edge=>edge.id==edge_id)
$scope.deleteEdge(edge[0]);
});
delete $scope.nodes[index]
}
I'm currently creating a game using Phaser IO and SignalR (+jQuery),
I get a list of players from server (for now containing an ID and Name), for each player I create a text field, which I later want to manipulate (with the amount of votes the specific player has), however I have no clue on how to reference the dynamically text object.
I'm open to new ideas as well
var game = new Phaser.Game($window.innerWidth, $window.innerHeight, Phaser.Auto, 'gameCanvas');
var dayState = {
preload: function () {
// Preloaded stuff
},
create: function () {
var world = game.world;
// Players alive in game
var players = // Call to Server, retrieves list of players
// Add player
for (var i = 0; i < players.length; i++) {
var currentPlayer = players[i];
// Player name
game.add.text(world.width - 225, y, currentPlayer.Name);
// I WANT TO UPDATE THIS UPON CALLBACK
game.add.text(world.width - 175, y, 0)
// Vote button
game.add.button(world.width - 50, y + 2, //preloaded texture for button, voteFunction, currentPlayer.Id , 2, 1, 0);
}
}
};
game.state.add('DayState', dayState);
game.state.start('DayState');
function voteFunction() {
// Posts vote to server
};
function voteReturnedFromServer(amount){
// Server calls this function (SignalR)
// This is where I want to update text element created above with data from SignalR
// Update text with callback data "amount"
};
You can go ahead and define a variable at the same level as game (for ease), and then set a variable equal to the text you add to the game.
var voteText;
// ...
voteText = game.add.text('world.width - 175, y, 0);
Then simply update the text, if voteText is defined.
voteText.text = 'data returned from the server'
Issue was finding the text after it has been created. I ended up creating an array outside game states and then push the texts into that array.
Then when I needed to edit the text, I'd search the array using grep (since Im already using jQuery)
var game = new Phaser.Game($window.innerWidth, $window.innerHeight, Phaser.Auto, 'gameCanvas');
// This is where I'll push my texts
var voteTexts = [];
var dayState = {
preload: function () {
// Preloaded stuff
},
create: function () {
var world = game.world;
// Players alive in game
var players = // Call to Server, retrieves list of players
// Add player
for (var i = 0; i < players.length; i++) {
var currentPlayer = players[i];
// Player name
game.add.text(world.width - 225, y, currentPlayer.Name);
// I WANT TO UPDATE THIS UPON CALLBACK
var vote = game.add.text(world.width - 175, y, 0)
vote.id = currentPlayer.Id;
voteTexts.push(vote);
// Vote button
game.add.button(world.width - 50, y + 2, //preloaded texture for button, voteFunction, currentPlayer.Id , 2, 1, 0);
}
}
};
game.state.add('DayState', dayState);
game.state.start('DayState');
function voteFunction() {
// Posts vote to server
};
function voteReturnedFromServer(amount){
var textToUpdate = $.grep(voteTexts, function (e) {
return e.id === votes.TargetId;
});
// Since I know there'll be a result and only one, then I use [0]
textToUpdate[0].text = votes.Count;
};
Use built-in method setText()
text.setText(amount);
https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Text.html
I am trying to get the area measurements of polygons so I can list them in a table to the side of the map, next to the name of the polygon. This is what I have tried with no success:
$("#polygon").on("click", function (){
createPolygon = new L.Draw.Polygon(map, drawControl.options.polygon);
createPolygon.enable();
}
var polygon = new L.featureGroup();
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon') {
polygons.addLayer(layer);
}
var seeArea = createPolygon._getMeasurementString();
console.log(seeArea); //Returns null
}
Any help on this would be appreciated!
You can access the geometry utility library provided with Leaflet.
var area = L.GeometryUtil.geodesicArea(layer.getLatLngs());
In your example, you are trying to access a control itself, which is what the variable createPolygon is assigned to. Instead, you want to take the area of the layer that got drawn.
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon') {
polygons.addLayer(layer);
var seeArea = L.GeometryUtil.geodesicArea(layer.getLatLngs());
console.log(seeArea);
}
}
Once you verify you are getting the area, you can just assign it to the variables that populate the table next to the map.
Note: area will be in squareMeters by default
I found that none of the above answers worked for calculating the area of non-contiguous polygons. Here's an example polygon where the above functions returned an area of 0:
For anyone who needs to do that, here is the code that worked for me (using the L.GeometryUtil function from Leaflet.draw):
var poly = // Your polygon layer here; may or may not be contiguous
var area = 0;
for (island of poly.getLatLngs()) {
// If the polygon is non-contiguous, access the island
if (island.length < 2) {
island = island[0]
}
// Sum the area within each "island"
area += L.GeometryUtil.geodesicArea(island);
}
L.GeometryUtil.geodesicArea(layer.getLatLngs())[0] should get you the area.
But I ended up using leaflet-geoman-free to do the drawing and use turf.js to get the area.
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
});
map.on('pm:create', e => {
const layer = e.layer
alert(turf.area(layer.toGeoJSON()))
});
add corrections:
var seeArea = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
console.log(seeArea);
I basically want to create a new path with the canvas globalCompositeOperation set to 'destination-out'. Is this possible? If so, how?
I noticed that Item has a blendMode property: http://paperjs.org/reference/item#blendmode, but trying the different values does not provide the desired effect.
http://jsfiddle.net/D8vMG/12/
In light of your recent comment, you would need to create layers, as described here:
http://paperjs.org/tutorials/project-items/project-hierarchy/#removing-items-and-children
and then you can add your paths to a layer and do something like this:
//add image to project as background
// ... your code here ...
var secondLayer = new Layer();
Whenever you create a new Layer, it becomes the active layer of the project, and then you can draw on top of the second layer all you want.
if you want a simple 'undo' button you can do:
function onMouseDown(event) {
if (window.mode == "drawing") {
myPath = new Path();
myPath.strokeColor = 'black';
}
else if (window.mode == "undo") {
myPath.opacity = '0'; //this makes the last path used completely see through
// or myPath.remove(); // this removes the path.
//if you're intent on something that writes and erases, you could do
}
}
But what you are looking for is something like this:
function onMouseDrag(event) {
if (window.mode == "drawing") {
myPath.add(event.point);
}
else if (window.mode == "erasing") {
myPath.removeSegment(0);
}
}
this erases the segments of the path from beginning to end. For the full functionality, you need something that identifies a path on click, (layer.getChildren() ? then select child). Then using the point on mouse move you need to identify the segment index and remove it from the path using .removeSegment(index).
http://paperjs.org/reference/path#removesegment-index
Well, the simple solution would be to just create a path which is white. http://jsfiddle.net/D8vMG/11/
function onMouseDown(event) {
if (window.mode == "drawing") {
myPath = new Path();
myPath.strokeColor = 'black';
}
else if (window.mode == "erasing") {
myPath = new Path();
myPath.strokeColor = 'white';
myPath.strokeWidth = 10;
}
}
I'm actually working on a html5 canvas project which uses the fabric.js Framework for the canvas interactions. Now I'm struggeling with the deletion of multiple objects. The following code does not seem to track the selected objects, but tracks all objects on the canvas.
var deleteSelectedObject = document.getElementById('delete-item');
deleteSelectedObject.onclick = function(){
var curSelectedObjects = new Array();
curSelectedObjects = canvas.getObjects(canvas.getActiveGroup);
canvas.discardActiveGroup();
for (var i = 0; i < curSelectedObjects.length; i++){
canvas.setActiveObject(curSelectedObjects[i]);
canvas.remove(canvas.getActiveObject());
}
};
Don't get my failure.
Due to #Kangax comment which solved most of the problem, I found the following solution to delete the currently selected objects from the canvas.
var deleteSelectedObject = document.getElementById('delete-item');
deleteSelectedObject.onclick = function()
{
if(canvas.getActiveGroup()){
canvas.getActiveGroup().forEachObject(function(o){ canvas.remove(o) });
canvas.discardActiveGroup().renderAll();
} else {
canvas.remove(canvas.getActiveObject());
}
};
The function checks whether a group is selected. If a group is selected every object of the group gets removed.
If no group is selected the function tries to remove a selected object. If nothing is selected, the canvas is not changed.
Your code seems like it is selecting and then de-selecting the objects.
This may work better:
var deleteSelectedObject = document.getElementById('delete-item');
deleteSelectedObject.onclick = function()
{
var curSelectedObjects = canvas.getObjects(canvas.getActiveGroup);
canvas.discardActiveGroup();
for (var i = 0; i < curSelectedObjects.length; i++)
{
canvas.remove(curSelectedObjects[i]);
}
};
Good information link:
https://github.com/kangax/fabric.js/wiki/Tutorial-2#wiki-modifying-objects
You can check any object property and can remove
var objects = canvas.getObjects();
for (var i = 0; i < objects.length; ) {
if (objects[i].name == 'cropArea' || objects[i].name == 'bleedLine') {
canvas.remove(objects[i]);
i = 0;
} else {
i++;
}
}
I done this:
var selectedObj = canvas.getObjects(canvas.getActiveGroup())
return me the array of the selected objects. :)
the last function paranthesis is missing in your code snippet
None of the solutions above (or anywhere elese on stackoverflow) worked for me except for this one solution I found on jsfiddle:
function deleteObjects(){
var activeObject = canvas.getActiveObject(),
activeGroup = canvas.getActiveGroup();
if (activeObject) {
if (confirm('Are you sure?')) {
canvas.remove(activeObject);
}
}
else if (activeGroup) {
if (confirm('Are you sure?')) {
var objectsInGroup = activeGroup.getObjects();
canvas.discardActiveGroup();
objectsInGroup.forEach(function(object) {
canvas.remove(object);
});
}
}
}
http://jsfiddle.net/beewayne/z0qn35Lo/
Did you know that canvas.remove can take more than one parameter? So the easiest way should be this one:
canvas.remove(...canvas.getObjects());
Other than canvas.clear this will only remove the objects in the canvas and not also the background.