Adding IDs to Fabric.js elements - javascript

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
});

Related

NameSpace Issue in JointJS version 3

I am trying to convert a legacy app from JointJS v2.2.1 to v3.0.2. I’m hitting an error others have found:
Uncaught Error: dia.ElementView: markup required. (joint.min.js:8)
A helpful person said: “Please note you need to be careful with the cellViewNamespace for the dia.Paper and cellNamespace option for the dia.Graph in this setup. Running this snippet is a quick check you've set up the namespaces correctly:
const cells = JSON.stringify(graph.toJSON());
graph.clear();
graph.fromJSON(JSON.parse(cells));
”
Can anyone offer additional help? I don’t know enough about JointJS to resolve this issue and I don’t really understand the code snippet.
I met a similar error saying 'markup required' just today when using jointjs with Vue. I followed the code and found that it is assuming 'joint' is present in global environment. So I add the following line in my code, and the error is gone:
import * as joint from 'jointjs'
window.joint = joint
Hopefully this helps.
If there is no joint variable in the global environment, you need to pass the shapes namespace explicitly to the graph (for models) and the paper (for views).
If you do not mind adding joint reference to the window object see #duanbw answer.
Built-in shapes
import { shapes, dia } from 'jointjs'
const graph = new dia.Graph({ /* attributes of the graph */ }, { cellNamespace: shapes });
const paper = new dia.Paper({ cellViewNamespace: shapes });
Custom shapes
If you define your own shapes do not forget to add it to the namespace as well (this also apply for the custom views).
const { standard, devs } = shapes;
// Custom Element Model
const MyRectangle = standard.Rectangle.define('myNamespace.Rectangle', {
size: { width: 100, height: 100 },
attrs: { body: { fill: 'red' }}
});
const graph = new dia.Graph({}, {
cellNamespace: {
// Optionally, cherry-pick namespaces/shapes you will use in your application
standard,
devs,
myNamespace: { Rectangle: MyRectangle }
}
});
const myRectangle = new MyRectangle();
myRectangle.addTo(graph);
const circle = new standard.Circle();
circle.addTo(graph);

Sort order of layers in Leaflet layer control

I have created a leaflet map to display some geoJSON data in separate vertical layers.
It works fine, except that the layer control will list the options out of order, i.e. "layer 0, layer 2, layer 1" as opposed to "layer 0, layer 1, layer 2". The data is loaded through AJAX calls, and so the layers are added to the control in the order which the calls complete, i.e. basically random.
According to the documentation for the layer control [ https://leafletjs.com/reference-1.3.0.html#control-layers ], if sortLayers is set to true, the default is to sort the layers by name, but that seems to not be happening.
So, I have tried passing in a sort function explicitly, but the doc does not give an example of what one should look like.
When my code looks like this:
var layerControlOptions = {
hideSingleBase : true,
autoZIndex : true,
sortLayers: true}
var layerControl = L.control.layers(null, null, layerControlOptions).addTo(map);
The control looks like this:
layer control with sortLayers: true and no sortFunction being defined
My best guess at writing my own sorting function:
var layerControlOptions = {
hideSingleBase : true,
autoZIndex : true,
sortLayers: true,
sortFunction: function(layerA, layerB, nameA, nameB){return
[nameA,nameB].sort();}}
var layerControl = L.control.layers(null, null,
layerControlOptions).addTo(map);
With this, the layer names appear in the same order as when no function is given. Not sure if I am writing it incorrectly, or it is simply equivalent to the default, which is returning the wrong order for some reason.
Seems like the same issue here: Leaflet Layer Control: Ordering
which has no accepted answer and makes no mention of the layer control options. I did try the solution which is proposed here, but it did not fix the problem either.
Any help on this would be greatly appreciated, thank you.
You may look at this solution to order layers in the controls (it does not order layers). I've mainly borrowed code from Leaflet specs code;
If you have not problem to sort things in the control but you have issue related to loading JSON/GeoJSON (hence layer too) in a particular order, you may want to combine fetch with Promise.all but it's not anymore about Leaflet but about JavaScript knowledge (look at this page to try to grasp fetch + Promise.all)
You can add the layers as you normally would and then sort them with a function that removes labels and re-add them as sorted.
function sortLabels() {
var controlLayers = {}
layerControl._layers.forEach(function(x) {
if (x.overlay) {
controlLayers[x.name] = x.layer
}
});
names = Object.keys(controlLayers).sort()
//remove and add sorted layernames
names.forEach(x => layerControl.removeLayer(controlLayers[x]))
names.forEach(x => layerControl.addOverlay(controlLayers[x], x))
}
var baseMaps = { "OpenStreetMap": tile_layer_osm, "ESRI World Imagery" : esri_satelite };
var overlayMaps = {nameC: layerC, nameA: layerA, nameB: layerB}
var layerControl = L.control.layers(baseMaps, overlayMaps, null, {collapsed: false}).addTo(map);
sortLabels()

Opacity keeps increasing with each call

I'm working on a project where I combined KineticJS with SignalR to update the stage on server calls.
So far everything works pretty good, and for the things that didn't work I had great help with the questions and answers on SO.
However this time I can't get it to work I also can't find any questions with a similar problem.
Basically I'm dynamically generating the init script for the KineticJS stage.(this all works perfectly) the code output looks like this:
var stage = new Kinetic.Stage({
id: 1,
container: 'stage-container',
width: self.baseWidth,
height: self.baseHeight
});
And for a shape it looks like this:
var shape129 = new Kinetic.Line({
id: 'shape129',
points: [249,66, 1889,66, 1889,928, 249,928, 249,66],
fill: '#DADADA',
opacity: 1,
stroke: '#3B5A99',
strokeWidth: 0,
closed: true,
draggable: true
});
So far so good.
It all renders perfectly.
Then the magic happens and I use a SignalR method to change the color of the shape
With the following code I get perfect color switches but not the desired opacity:
signal.client.statuschange = function (id, message) {
var shape = Kinetic.stages[0].find('#' + id);
shape.setAttr('fill', message);
shape.setOpacity(0.5);
shape.getLayer().draw();
};
As I said the color changes perfectly but the opacity keeps increasing with each call to the method.
I'm kinda lost here and not sure what is happening and most of all Why it is happening.
Any help would be very much appreciated!
I think problem in find function. Your shape is not a node, but a collection. Try this:
var shape = Kinetic.stages[0].find('#' + id)[0];

Fabric.js loadSVGFromUrl not displaying multiple imported SVGS

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

Kinetic.js / Javascript: calling a variable as a property without using eval()?

I am trying to teach myself HTML5 and JavaScript, and would appreciate any help.
I solved a problem by using eval(), and was wondering if there was another way to solve it without using eval(). Everything I've tried doesn't seem to work.
I have two arrays, defined like this:
var europe = {
shapes: {
AL: "M520.651,114.27l-0.257,0.900l0.385,1.160l1.029,0.643l0,0.644l-0.901,0.386l-0.128,0.901l-1.288,1.287l-0.386-0.128l-0.127-0.644l-1.417-0.900l-0.259-1.288l0.259-1.803l0.256-0.901l-0.384-0.386l-0.258-0.901l1.287-1.288l0.129,0.516l0.771-0.258l0.516,0.773l0.643,0.257l-0.130-1.030z",
AM: "M582.697,116.33l3.605,-0.515l0.642,0.772l1.032,0.386l-0.516,0.773l1.416,0.900l-0.772,0.902l1.159,0.643l1.158,0.516l0.129,1.801l-1.029,0.129l-1.032,-1.544l0,-0.515l-1.287,0.129l-0.771,-0.772l-0.516,0l-1.029,-0.773l-2.059,-0.643l0.256,-1.288l0.386,0.901z",
AT: "M510.996,97.278l-0.257,1.158l-1.545,0l0.643,0.643l-0.900,1.674l-0.515,0.515l-2.446,0l-1.289,0.644l-2.315-0.258l-3.734-0.644l-0.644-0.900l-2.703,0.386l-0.258,0.514l-1.672-0.386l-1.416,0l-1.160-0.514l0.385-0.644l-0.128-0.515l0.903-0.128l1.285,0.772l0.387-0.772l2.446,0.128l1.931-0.515l1.287,0.128l0.773,0.515l0.258-0.386l-0.387-1.802l1.030-0.386l0.901-1.158l2.058,0.772l1.417-1.030l1.030-0.258l2.061,0.901l1.286-0.129l1.158,0.516l-0.127,0.256l-0.257-0.903z",
},
names: {
AL: "Albania",
AM: "Armenia",
AT: "Austria",
}
};
And what I am doing is iterating over the path data in europe.shapes to draw it, and then displaying the matching data in europe.names using this code:
for(var euroKey in europe.shapes) {
var pathEuro = new Kinetic.Path({
data: europe.shapes[euroKey],
name: euroKey,
fill: 'yellow',
stroke: '#555',
strokeWidth: 1
});
pathEuro.on('mouseover', function() {
var euroText = eval('europe.names.'+this.getName());
writeMessage(euroText);
this.setFill('#CCCCCC');
this.moveTo(topLayer);
topLayer.drawScene();
});
pathEuro.on('mouseout', function() {
writeMessage('');
this.setFill('#eee');
this.moveTo(mapLayer);
topLayer.draw();
});
textLayer.add(countryText);
mapLayer.add(pathEuro);
};
This is mostly stolen from the tutorial here: http://www.html5canvastutorials.com/labs/html5-canvas-world-map-svg-path-with-kineticjs/ but I am trying to add in the mouseover text of the country name.
This method using eval() works, but as I doing this on my own I don't want to learn bad habits.
The whole code is up on GitHub here: https://github.com/malkie-labs/html5-world-map if anyone is really, really bored and wants to take a look. Any constructive criticism or suggestions are appreciated!
Thanks!
-Scott
If you are only using eval to access variable name object members, you can do it w/o eval:
eval('europe.names.'+this.getName());
simply becomes
europe.names[this.getName()];

Categories

Resources