Using Meteor and React, I am building an application that has a fabric canvas in the middle, a 'components panel' on the left (a library of objects to add to the canvas), and a 'layers panel' on the right (similar to Photoshop, where each object on the canvas is represented as a layer with specific controls like color, opacity etc.).
So far I have managed to display the Layers Panel by using a Meteor session variable that contains an array for the objects on the canvas.
So each time I add, remove, adjust or make any functional change to any object on the canvas, I manually make the adjustment for the object within the session variable array as well.
This has worked well so far but I now want to implement grouping functionality. While it is easy to do this with fabric.js alone, I am having trouble representing the exact state of the groups on the layers panel. It needs to be displayed in a tree format where nested groups and items are within/under other objects.
I also still need to have the layers panel sortable as well.
Using fabric, we can group a set of objects, group another set of objects, and then group the two sets of objects together. This gets quite complicated and is quickly becoming a nightmare to maintain using the method I described above.
So now I am thinking, it would be much easier to just have the layers panel look at the object data directly from the canvas (reactively). This way, I don't need to maintain a second array and manually match every change that occurs. I could then use something like React Sortable Tree.
So in an attempt to make this happen, I am trying to store a copy of canvas.getObjects() into a Meteor session variable after any change occurs on the canvas.
canvas.on({
'after:render': function() {
var canvasObjects = [];
var objects = canvas.getObjects();
objects.forEach(object=>{
canvasObjects.push(object);
});
Session.set('canvasObjects', canvasObjects);
}
});
I am getting the following error though...
TypeError: callback is not a function
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:261816
at onLoaded (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244313)
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244337
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:257179
at onLoaded (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244357)
at modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244379
at Array.forEach (<anonymous>)
at Object.enlivenPatterns (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:244370)
at Function.fabric.Object._fromObject (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:257171)
at Function.fabric.Path.fromObject (modules.js?hash=d9804d9fae07a146610fc3c3c610d3e79bf33006:261457)
If I replace this line...
Session.set('canvasObjects', canvasObjects);
with this line...
console.log(canvasObjects);
I see the array printed in the console.
Why is it able to log the array but unable to store it into the session variable?
Is this even a good idea? I feel like it will be bad for performance. Would there be any other way to keep the canvas state and the layers panel in sync with all the properties that I want (visibility, opacity, fill, lock, index, group hierarchy etc.).
Thanks in advance.
Related
Beginner and self-taught coder here (always open to learning please correct me) and I'm making a web app through pretty much exclusively HTML CSS and Javascript (I don't really want to use PHP or hosting-side processing because I don't know much about web security and it makes me nervous about uploading data to my hosted site).
Very unsure about the most efficient way to do this so I'm going to try to describe it below and I'd really appreciate your input.
My main question: Is there a more efficient way to do this?
The app eventually will have a javascript canvas, where it will draw an object ('track') at a specific location. This object will then move to another location based off nested data in an array ('step') when the user moves to the next item in an array.
As of now, how I'm going about it is having:
storing the location values in the steps array
have an array of 'tracks' for what shape/color/etc will be drawn on the canvas
linking the two elements by an arbitrary ID that is in both 'steps array' and 'tracks' array
A visual representation of what this might look like
steps[stepNumber].movedTracksInStep[movedTracksInStepNumber] holds object:
{track ID,
X location,
y location}
separate array trackList
trackList[trackNumber] holds object:
{track ID,
shape,
color,
bunchastuff}
I choose to do it like this because I figured it would be better to store the location in the steps array, store the visual data in a separate array, so that way it's not repeating the same data every step.
My question:
Is there a more efficient way to do this, especially in terms of search functions? I'm a newbie so there very well might be something I am missing.
Currently, I just have to search through all of the ID tracks in the step and see if there is a match. I'm wondering if there is a more direct way to link the two together than having to search each time.
I've thought about perhaps having all the data for the visual representation in the first step and then not having to repeat it (though I'm not quite sure how that would work), or having the numbers of arrays match up (but this would change if the user deletes a track or adds a track).
Thank you! Let me know if you need me to explain more.
Objects in JS are stored and copied "by reference", so if you assign value of one object to another, value will not be copied, but reference link will be created. Below is the example close to your code, check inline comments. And you can adopt this behavior to your task:
// Your tracks information
const trackList = {
1: {
shape: "rect",
color: "green",
bunchastuff: "foo"
}
};
// Your steps data
const steps = {
1: {
1: {
// Here we create reference to track 1 in
// trackList object data, without copying it
track: trackList[1],
x: 100,
y: 50
}
}
};
// Print step info
console.log("before track info edit:", steps[1][1].track);
// Update data in track 1
trackList[1].shape = "round";
// Print step info again and we'll
// see, that it also updated
console.log("after track info edit:", steps[1][1].track);
You can read more about object references here: https://javascript.info/object-copy
I wish to get handles of all the charts in a stage in order to modify background settings.
I noticed there is a stage.forEachChild(function(element) { … }) method that allows you to fire up a function for each stage element.
For example:
stage.forEachChild(function(element) {
alert(element.id());
});
The problem is that "element" type is anychart.graphics.vector.Element; instead I need a anychart.core.Chart object in order to call the background() method. Is there a way to do that?
Unfortunately, is a GraphicsJS entity and it returns its graphic vector elements as children. It doesn't control charts. You can store all chart in an object or array and iterate them, or apply a unique ID to every chart and get access to them at any moment by anychart.getChartById('CHART_ID');. For details, check the sample by the link in a comment below.
I am trying to remove all the objects on my Canvas without selecting them. The objects in the Canvas include Grouped and Ungrouped object. All the Examples I've seen demonstrate how to delete a single ungroup object.
Canvas.ForEachObject(function(o){
o.remove();
});
Please see the fiddle for an example of what I'm trying to achieve.
https://jsfiddle.net/Shane00711/r8su3ya0/
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.
You just need to call
canvas.clear()
it will remove all object
I figured out what I need to do in order to delete every object(grouped/ungrouped) from my Canvas.
First I had to get all the objects in the canvas.
var obj = canvas.getObjects();
once I got all the objects I simply had to loop through them removing each one as I did.
canvas.remove(obj[0]).
The reason I reference the index 0 in my
canvas.remove(obj[0])
is because every time and object was removed from the canvas the number of object in the list 'obj' also decreased by 1, moving all the objects up by 1 index. which meant that every object on my canvas would at some point be at index 0 of the 'obj' list.
here's a fiddle of a working example. where I delete all objects on the canvas without selecting a single one.
http://jsfiddle.net/Shane00711/r8su3ya0/8/
I'm drawing an entity using position data being fed from a database.
I'm currently using
viewer.entities.removeAll();
to remove all the entities every time I get a result from the database.
This causes a long and heavy process in the browser as the entities are currently 3D models.
Is there a better way for the data to update, for example an array of the entities.
Or a way to cache the entities that I could then alter the data.
The end result is so that I could alter the positions on the fly and then see the entity update on the display
I am using
viewer.entities.add({
name : name,
position : position,
orientation : orientation,
model : {
uri : url,
minimumPixelSize : 50
}
});
to add the entities
Take a look at the Picking Demo, you can see it assigning new Cartesian3 values to entity.position around line 26 in the live-editor window. Updating an entity is going to be much faster than destroying it and creating a new one.
Also, if your database knows that an entity has a position that changes over time, you can supply that via a SampledPositionProperty, and allow Cesium to animate the entity moving along a path over time.
I'm trying to write some javascript that will stack objects by setting z-index.
Test Case:
http://christophermeyers.name/stacker/
I've hacked that together, but I'd like to extrapolate that behavior to something a little more logical. That is:
Given x number of elements, when element C is moved to the top, all elements above that element must move down 1, while all elements below that element should remain in place.
A "linked list" makes for a good data structure when you're doing this kind of thing. Keep track of the order of your stackable elements via a series of simple nodes..
// ListNode
{
value: {}
next: {<ListNode>}
}
..and update the sequence as new nodes are added or selected.
I have posted a working example of a list being used for depth sorting at the following URL:
http://aethermedia.net/sandbox/depth-sorting.html
Sorry I don't have time to pull up a more appropriate tutorial =/