FabricJS - Set zorder on adding item / group - fabricjs

I'm trying to add a group of items to the canvas, but want the group to be set at the bottom of the zorder stack when it gets added. Heres the code I'm trying to do:
//Reload the SVG now
var site_url = "/components/<?=$designObj->svg_filename?>";
fabric.loadSVGFromURL(site_url, function(objects, options) {
let componentObj = fabric.util.groupSVGElements(objects, options);
componentObj.setControlVisible(false);
componentObj.sendToBack(); //THIS DOESNT WORK - THROWS TYPE ERROR
componentObj.selectable = false;
componentObj.jdeation_comp_id = "base";
componentObj.jdeation_base_svg = "<?=$designObj->svg_filename?>";
canvas.add(componentObj).renderAll();
});
I get the following error when I call sendToBack():
TypeError: Cannot read property 'sendToBack' of undefined
Not sure I totally understand whats happening here, appears that sendToBack expects the type to be something other than group or something. Is there a better way to do this?

You can't .sendToBack() on an object that hasn't been added to the canvas yet. Use canvas.insertAt(componentObj, 0) instead of canvas.add(componentObj) to specify that the object should be added to the bottom of the stack.

Related

svg.js with existing svg

I'm trying to implement svg.js to make a map of a clickable floorplan.
But I cannot make this work properly, or this works but doesn't work like I expected:
const map = SVG.get('mysvg');
map.click(function( event ) {
this.fill({ color: '#000' });
console.log(this);
console.log(event.target);
});
When I try to click on some area on the map, instead of change fill color I get nothing.
Actually svgjs triggers 'create' as you can see in console with inspector.
Not sure what am I doing wrong here?
I would expect that the area will change fill color?
https://codepen.io/bobz-zg/pen/LdyXBe
You can use the select function as described here to create a Set. You'd then use the each() method from Set to iterate over each entry and apply the click event handler. For example:
const map = SVG.get("mysvg");
var restaurants = map.select('[id*="restaurant"]:not(g)'); // Create an svg.js Set, but do not capture the group in the set
restaurants.each(function() {
this.click(function(event) {
this.fill({ color: "#000" });
});
});
If you can edit the svg that you are using as an input, I would suggest adding a class to each of the clickable elements to use that as the reference for select. Otherwise, you'll have to do something like selecting all element id 'types' (i.e. restaurant, retail etc.) separately and concatenating the Sets using Set.add() before you do the loop to add the click listener, but that could get messy.

Select and manipulate particular paths within a path group fabric.js

I am making an app that involves shapes like cylinders and boxes, and need to be able to do this with fabric.js. I know about three.js but for my purposes, it must be in 2D.
Initially I thought I would just create them in 3D software and render images which can then be added to the canvas, which I did successfully...
However, I have run into a hurdle where fabric only allows patterns to be filled onto paths or objects (rect, circle etc.)....not images (png).
Since I absolutely need patterns, I now need to create these cylinders in SVG. I have gotten as far as making the cylinders in Illustrator, saving them as SVG's and then using them on the canvas, then adding fill patterns on them. So far so good.
Now I want to be able to fill a different pattern for the top of the cylinder, and a different pattern to the side BUT still have it as one object.
So...How can I select and manipulate particular paths within a path group? Is there anyway to give each path within the group a custom attribute (eg. name) which I can then target? Do I need to create two seperate SVG files and then add them seperately, and if so, how can I do this and still have it as one object?
Here's how I am adding the svg to the canvas...
fabric.loadSVGFromURL("/shapes/50-250R.png", function(objects) {
var oImg = fabric.util.groupSVGElements(objects);
oImg.perPixelTargetFind = true;
oImg.targetFindTolerance = 4;
oImg.componentType = "Shape";
oImg.lockUniScaling = true;
oImg.lockScalingX = true;
oImg.lockScalingY = true;
oImg.setControlsVisibility({'tl': false, 'tr': false, 'bl': false, 'br': false});
canvas.add(oImg);
canvas.renderAll();
});
Here is how I am adding the pattern...
var textureIMG = new Image;
textureIMG.crossOrigin = "anonymous";
textureIMG.src = texture.image;
obj.setFill(); //For some reason, the fill doesn't happen without this line.
var pattern = new fabric.Pattern({
source: textureIMG,
repeat: 'repeat'
});
if (obj instanceof fabric.PathGroup) {
obj.getObjects().forEach(function(o) {
o.setFill(pattern);
});
} else {
obj.setFill(pattern);
}
canvas.renderAll();
Thanks in advance.
So I managed to figure this out. Each path within the path group is stored in the 'paths' array of the object.
I can now add a pattern to the top of the cylinder using...
var obj = canvas.getActiveObject();
obj.paths[0].fill = patternOne;
and to the sides using...
obj.paths[1].fill = patternTwo;

How to use Maki svg icons with Snap.svg?

I'm experimenting with Snap in order to use svg and need to be able to use the Maki icons defined in https://github.com/mapbox/maki.
My plan is to load the svg's I need, and then instantiate them for particular icons on a piece of Snap paper. But in order for this to work, I need to place the icon at a particular place on the paper, but I can't get translation to work. Neither one of the translation techniques below works; the code works as is, but always places the icon at the top left.
What am I missing? There's not enough documentation on Snap, and I don't know if the problem is with the way the Maki icon svg is defined, or my use of Snap.
var icon = Snap.load("maki/bicycle-24.svg", function(f) {
var g = f.select("g").clone();
// var g = f.select("#layer1").clone(); // also works
// g.transform("t120,120");
// var t = new Snap.Matrix();
// t.translate(120,120);
// g.transform(t);
paper.append(g);
});
The cloning needs to happen after the append, as when loading an svg in Snap its just a fragment.
So you will need to do something like...
paper.append(f);
var element = paper.select('#someId').clone();
element.transform( myTransform );
Thank you! That did the trick! And since Snap is so poorly documented, I'm going to insert the code here that allows a general solution.
// Base set from which markers are constructed
var iconSet = paper.group();
iconSet.attr({ class: 'hide' });
// Instantiations of icons
var markers = paper.g();
// Now, create SVG shape
var icon = Snap.load("maki/bicycle-24.svg", function(icon) {
// Add it to the icon set
iconSet.append(icon);
// Instantiate it and remove from main view
var element = paper.select('#svg4460'); // Copies it!
// var element = paper.select('#base'); // Selects something but doesn't work
// var element = paper.select('#layer1'); // Selects something but doesn't work
// var element = paper.select('#bicycle-24'); // Silent fail
element = element.clone();
element.remove();
// Clone this icon and move it
var t = new Snap.Matrix();
t.translate(10,120);
element.transform(t);
// Insert into main document view (markers)
markers.add(element);
});

Highlighting or Bolding the Borders of a kml Polygon

Hey after loading a kml file to google earth I was trying to have when a user clicks a certain polygon from the kml, to have that polygon highlighted.
So far I can record the click event, get the event type (KmlPlacemark) and grab its kml markup.
I tried doing something similar to this example where they add a placemark to the getFeatures of the kmlObject but both target and type don't seem to have 'getFeatures'. After looking around the documentation I think I might either want setOutline from Kml Polystyle class or setWidth() from KmlLineStyle class but am not sure. Also when I try something like target.setOutline(true); it doesn't work.
Can anyone tell me if I'm on the right track, hints to what I'm doing wrong, and if there's a better way to do this?
function recordEvent(event) {
var target = event.getTarget();
var type = target.getType();
if(type == "KmlPolygon") {
alert("KMLPolygon ");
}else if(type == "KmlPlacemark") {
// // get the data you want from the target.
var description = target.getDescription();
var balloon = target.getBalloonHtml();
var outputKml = target.getKml();
if ('getFeatures' in event) {
console.log("test");
event.getFeatures().appendChild(placemark);
}
console.log("hello?")
// target.setOutline(true);
console.log(outputKml);
}
};
google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);
Thanks!
I find the best way to do what you are asking is to:
Detect click events like you currently do
If clicked, create a new Style, then assign it to the target
var newStyle = ge.createStyle('');
// Assign your Style's attributes such as LabelStyle and IconStyle
// eg to set the scale of your label
newStyle.getLabelStyle().setScale(2.5);
// Set the Style
target.setStyleSelector(newStyle);
Edit to add in this link of a Google example showing it more in depth
https://code.google.com/apis/ajax/playground/#styling_placemarks_using_style_maps

Fabric.js loadFromJSON width/height properties deserialization

I have a problem with Fabric.js.
When I'm trying to save fabric canvas like this:
var data = JSON.stringify(self.canvas.toJSON(['width', 'height']))
I get well serialized json with height and width properties.
But when I'm trying to deserialize it like that:
self.canvas.loadFromJSON(data, function () {
self.canvas.renderAll();
});
All objects init fine except height and width properties.
I saw deserialization example: http://jsfiddle.net/fmgXt/3/
If I add to json height and width properties its ignores with deserialization.
But if I set them into code - it also works well. Am I missing something?
At http://fabricjs.com/docs/fabric.StaticCanvas.html#loadFromJSON I didn't find any mentions for loadFromJSON about includedPropertiesNames list or something like that.
I've found a solution to get missing attributes at deserialization :
var object = JSON.parse(json); //use default json parser
canvas.loadFromJSON(json, function(){
canvas.width = object.width;
canvas.height = object.height;
});
This one made no luck for me.
I was able to make it working by setWidth and setHeight methods:
var object = JSON.parse(json);
canvas.loadFromJSON(json, function() {
canvas.setWidth(object.width);
canvas.setHeight(object.height);
canvas.renderAll.bind(canvas);
});

Resources