Fabric.js loadSVGFromUrl not displaying multiple imported SVGS - javascript

I'm using fabric.js and loading a number of SVG files into it. I have no problem displaying one of these imported files using this code;
fabric.loadSVGFromURL('ico-svg/drink.svg', function(objects, options) {
var drink = fabric.util.groupSVGElements(objects, options);
drink.set({left: 80,
top: 175,
width: 32,
height: 32 });
canvas.add(drink);
canvas.calcOffset();
canvas.renderAll();
});
However, when I repeat this code, the page only shows one of the two SVGs, and they actually change upon page refresh - only one icon will show one time, one icon will show the other, on the odd occasion they will both show but one of the SVGs won't display completely.
To load the other SVG, i'm simply copying the above code and changing the required variables;
fabric.loadSVGFromURL('exporttest.svg', function(objects, options) {
var dollars = fabric.util.groupSVGElements(objects, options);
dollars.set({left: 80,
top: 90,
width: 350,
height: 342 });
canvas.add(dollars);
canvas.calcOffset();
canvas.renderAll();
});
fabric.loadSVGFromURL('ico-svg/drink.svg', function(objects, options) {
var drink = fabric.util.groupSVGElements(objects, options);
drink.set({left: 80,
top: 175,
width: 32,
height: 32 });
canvas.add(drink);
canvas.calcOffset();
canvas.renderAll();
});
Is this something i'm doing wrong? I've read that the library support for loading SVGs from URLs isn't that fantastic - could it be that? Ideally, loading the SVGs from an URL this way or in a similar way is my best option as there are so many, and they are each quite complex and require different positioning.

I think this was a bug that has been fixed in the library.
Current fabricjs.com offer a kitchensink demo where you can try code.
http://fabricjs.com/kitchensink/
copy paste the code below in the execute tab to load 3 svg togheter without any strange issue or taking any precaution.
// clear canvas
canvas.clear();
// remove currently selected object
canvas.remove(canvas.getActiveObject());
fabric.loadSVGFromURL('../assets/1.svg', function(objects, options) {
var dollars = fabric.util.groupSVGElements(objects, options);
canvas.add(dollars);
canvas.calcOffset();
canvas.renderAll();
});
fabric.loadSVGFromURL('../assets/2.svg', function(objects, options) {
var drink = fabric.util.groupSVGElements(objects, options);
canvas.add(drink);
canvas.calcOffset();
canvas.renderAll();
});
fabric.loadSVGFromURL('../assets/3.svg', function(objects, options) {
var drink = fabric.util.groupSVGElements(objects, options);
canvas.add(drink);
canvas.calcOffset();
canvas.renderAll();
});

I actually do this as well, where a user can select any number of SVG files to load and later come back to edit their work. When they come back, I had the same issue while trying to reload the multiple files. In my case, I am actually building an array of objects that hold the svg url along with other useful pieces of information. This allowed me to load them into a stack (most suiting for my loading order, though you could easily implement it with a queue) and then pop them off one at a time.
var loadingLayerStack = [url1, url2, url3];
function ProcessLayerLoading()
{
var layerUrl = loadingLayerStack.pop();
DrawSvgToCanvas(layerUrl);
}
function DrawSvgToCanvas(url)
{
fabric.loadSVGFromURL(url, function(objects, options) {
var obj = fabric.util.groupSVGElements(objects, options);
// ...any code for special handling of the loaded object
// put object on the canvas
canvas.add(obj);
// get the next image
ProcessLayerLoading();
});
}
It is noteworthy to point out that I have the setting to enable Rendering when an object is added. So that canvas.add call also takes care of the initial rendering for me.
Hope this helps you somehow. :)

I just ran into this same problem, after seeing your post I decided to dig in to it a bit further myself. It turns out to be due to a scope issue in the onComplete callback within loadSVGFromURL. The problem stems from not isolating the url to a single scope; making multiple back-to-back calls results in the last url always being used when onComplete fires. As a workaround you could either chain your SVG loads or just make an ajax request yourself and load it using loadSVGFromString instead.
I'm learning fabric seems to be full of these bugs, errr, gotchas :(
Edit
I spoke too soon, loadSVGFromString suffers from the same weakness, it does not work asynchronously. That being said chaining is the most obvious work-around.

fabric.loadSVGFromURL() fetches the SVG via XmlHttpRequest. If you wait for each request to complete you can load multiple. Which means you need a tool for making and composing asynchronous promises in JavaScript. Check out q. https://github.com/kriskowal/q

Related

PIXI remove spriteSheet Texture cache

I have loaded spriteSheetFrame using json.
const loader = new PIXI.loaders.Loader();
loader.add('bunny', 'data/bunny.png')
.add('spaceship', 'assets/spritesheet.json');
loader.load((loader, resources) => {
});
I want to remove all the TextureCache which was loaded using this spritesheet.json only.
I have tried.
PIXI.Texture.removeFromCache("spaceship");
PIXI.Texture.removeTextureFromCache("spaceship");
But in PIXI.TextureCache names of all the spriteFrame were included there.
And still i am able to use image form frame. Using this.
var bgSprite2 = PIXI.Sprite.fromFrame("ship1");
bgSprite2.anchor.set(0.5, 0.5);
var pos = {x: 300, y: 200};
bgSprite2.position.set(pos.x, pos.y);
stage.addChild(bgSprite2);
I want to remove all the entries of spriteFrame in TextureCache and i want to load new set of spriteFrame.
I am doing this because i have spritesheet animations of two diffrent spaceship but the individual symbol name of both spaceship are same.
I would agree with Hachi that you could gain some performance from just replacing the texture rather than destroying and re-creating over and over. Caching could be the answer.
You could then eventually call destroy when your done with them to make sure there is nothing lingering around.

Store/Import variables & functions Processing(JS)

Stack Overflow! I wanted to store variables in another file so that I would load the variables in file 1, and draw the scene in file 2, ex.
closet.js
var message = "Hello there";
drawer.js
draw = function() { text(message, 100, 100); };
So I would do something like that, but instead of importing the files like this;
<canvas data-processing-sources="closet.js drawer.js"></canvas>
I wanted to be able to include them in file 2, sort of like this;
closet.js
var message = "Hello there";
drawer.js
import("closet.js");
draw = function() {
text(message, 100, 100);
};
Is there a way to do this without including them in the HTML file itself?
Thanks in advance :)
Check out this question, which yours might even be a duplicate of. It lists several ways to do this in JavaScript, including using JQuery or dynamically adding the <script> tags to the <head> of your page.
But if you're using the Processing editor, you have another option: use classes by creating a new tab in the Processing editor. For all your purposes, you can treat this as a separate file. So lets say you created a separate Closet tab with a message variable. Your main sketch code might look like this:
Closet c = new Closet();
draw = function() {
text(c.message, 100, 100);
};
I think this is probably the way to go. It seems like you're trying to over-engineer a solution: either include the files in the html (this is what 99% of all JavaScript code does) or use a class (this is what 99% of all Processing code does).

Pattern for using Meteor with advanced SVG or Canvas Ouput

Is it sensible to use Meteor for a reactive data display that isn't primarily HTML based?
To be specific, I want to display a graph database as a set of boxes connected by lines. I'd like to allow live interaction with these boxes, and I'd also like them to be reactive, so if one user edits the data the display of any other users currently viewing the graph will update.
Meteor seems great for the reactivity, but most of the examples I've found focus on either HTML templates or very simple API interactions for doing things like adding a pin to a map.
I am currently thinking about using SVG or Canvas to display the graph database, but I am very unsure how best to integrate that with Meteor and/or some other library like maybe D3.
I found that Meteor works perfectly with canvas, I don't know if what I do is the best practice but I got the best results using Kinetic.js (available for Meteor via "mrt install kineticjs" and I use the template engine to call on functions that set up the elements on my canvas, this is a small example of a code I use to place the players on my map:
the Template:
<template name="canvas_map">
<div id="grid_map" class="grid"></div>
{{#with clear_canvas}}
{{#each human}}
{{handle_member_pos}}
{{/each}}
{{/with}}
the "clear_canvas" helper sets up a new Kinetic.Stage and the "handle_member_pos" helper gets a human object and places it on said canvas.
here are the helpers (coffeescript):
Template.canvas_map.clear_canvas = =>
if Session.get('init')
kinetic_elements.stage = new Kinetic.Stage
container: 'grid_map'
width: 385
height: 375
kinetic_elements.layer = new Kinetic.Layer()
else
false
Template.canvas_map.handle_member_pos = ->
[x, y] = pos_to_str #profile.pos
left = Math.floor(11 * x)
top = Math.floor(11 * y)
name = #profile.name
unless kinetic_elements.avatars[name]?
imageObj = new Image()
imageObj.onload = =>
element = new Kinetic.Image
x: left
y: top
image: imageObj
width: 50
height: 50
element.on 'click', (evt) =>
Session.set 'selected', #profile._id
window.propogation = false
false
kinetic_elements.layer.add element
kinetic_elements.avatars[name] = [element, text]
kinetic_elements.stage.add kinetic_elements.layer
imageObj.src = 'human.png'
else
element = kinetic_elements.avatars[name]
layer = kinetic_elements.layer
element.setX left
element.setY top
layer.draw()
return
as I said, I'm not sure if that is the best practice, but it works great for me, hope this helps in any way.

How to force DOM element creation with qx.ui.embed.Html?

I am trying to embed a RaphaelJS paper into qooxdoo widget. RaphaelJS is a SVG drawing library for JavaScript, and it needs to bind to a HTML <div> prior to any drawing.
For that, I call new qx.ui.embed.Html("<div id='raphael'></div>") and add it to my widget. After that, I should initialize Raphael by passing the div ID to it.
Problem is that <div id='raphael'> is not committed to the DOM model (i.e., no real DOM element is created) right after qx.ui.embed.Html() constructor call. The DOM element creation is indeed deferred until the widget is painted to the screen. I've managed to catch an appear event for the widget, and, after that, element's existence is guaranteed, and I can initialize Raphael library and do some drawing.
This approach assumes that I have to run all my application logic from within that appear event handler, which is probably not what I want. Is there any other way to get a widget in its ready-for-drawing state in the main application flow?
What you could do is create your own widget RaphaelWidget.js:
qx.Class.define("myApp.RaphaelWidget",
{
extend : qx.ui.core.Widget,
construct : function()
{
this.base(arguments);
this.addListener("appear", this._onAppear, this);
},
members :
{
/**
* Overwritten from qx.ui.core.Widget.
*/
_createContentElement : function()
{
return new qx.html.Element("div", {
overflowX: "hidden",
overflowY: "hidden",
border: "1px solid #aaa" // just for debugging
}, {"id": "canvas-raphael"});
},
_onAppear : function()
{
var paper = new Raphael(document.getElementById('canvas-raphael'), 250, 250);
var circle = paper.circle(100, 100, 80);
}
}
});
And then do for example in your Application.js:
var raphael = new myApp.RaphaelWidget();
raphael.setWidth(250);
raphael.setHeight(250);
this.getRoot().add(raphael);
Now you can develop your Raphael specific code in this new widget class.
Forcing rendering/DOM manipulation actions is called "flushing" in qooxdoo. E.g. the qx.html.Element from Richard's solution has a .flush() method. You might want to try this, or search the API documentation for the term 'flush'.
That being said flushing is a last resort, and shouldn't be used excessively as this would severely degrade performance. You shouldn't shy away from asynchronous programming when you are doing JavaScript. Even your "main" method is a callback, called from the qooxdoo runtime at some point in time.
There are several qooxdoo contributions that integrate third-party libraries the likes of Rafael. For a more idiomatic solution of doing this see e.g. QxDyGraphs (part. the __addCanvas method), a contrib that integrates the Dygraphs JS library.

Adding IDs to Fabric.js elements

I am creating a 'map like' application in Canvas using the Fabric.js library. I essentially put an image down and then lay circles on top of the image with the correct coordinates. The dot locations and names come through as JSON data received from an ajax call. I need a way to refer to any individual circle so that I can fetch more information regarding that circle (each circle has a popup with more detailed information).
I can't find anywhere in the documentation that explains how to do this, I have tried adding an ID to an object like this:
tokens.add(new fabric.Circle({ radius: 25, fill: '#f55', top: 100, left: 70, id: "foo" }));
with no luck retrieving it. Any help would be greatly appreciated. Also this is my first interaction with this community, so I apologize if my question isn't detailed enough, or if there is some other problem with it.
You can add an attribute with fabric.object.prototype.
So, add :
fabric.Object.prototype.uid = ... ;
You can Extend fabric.Object class and add your own Property But easiest way would be using fabric.util.Object.extend().
You can add as many as custom property you want and access it using object.toObject().id.
extend(object, prop) {
object.toObject = ((toObject) => {
return function () {
return fabric.util.object.extend(toObject.call(this), prop);
};
})(object.toObject);
}
and then
this.extend(line , {
customType:'ArrowLine',
id:this.randomId(),
CommentId:activeObject.toObject().id, extensionId
});

Categories

Resources