Paper.js unexpected position of an item from a group

2,241 views
Skip to first unread message

TT TY

unread,
Aug 24, 2012, 9:16:35 AM8/24/12
to pap...@googlegroups.com
var path = new Path(new Point(0, 0), new Point(100, 0));
var path2 = new Path(new Point(0, 0), new Point(0, 100));
var path3 = new Path(new Point(50, 50), new Point(100, 50));
var path4 = new Path(new Point(50, 50), new Point(50, 100));
var circle = new Path.Circle(new Point(0, 0), 50);
var circle2 = new Path.Circle(new Point(0, 0), 1);
// Create a group from the two paths:
var group = new Group();
group.addChild(path2)
group.addChild(path)
group.addChild(circle)
group.addChild(circle2)
// Set the stroke color of all items in the group:
group.strokeColor = 'black';
path3.strokeColor = path4.strokeColor = 'red'
// Move the group to the center of the view:
group.position = new Point(50, 50);
alert(circle.position.x);

Why is 25 instead of 50? or better 0, because should be relative to the group.


Thanks

Ryan Beasley

unread,
Aug 24, 2012, 12:25:32 PM8/24/12
to pap...@googlegroups.com
After all four addChild calls but before you move the group, do
alert(circle.position.x);  //  outputs 0
alert(group.position.x);  // outputs 25
So the group x position is 25 more than the circle x position.

Then you set the group's x position to 50...and since the group's x position is 25 more than the circle's x position, we expect circle.position.x to equal 50-25=25.

Perhaps you would rather use group.translate(new Point(50,50));

I disagree that it would be better for the positions to be relative to the parents...then to find the absolute canvas position of any element the code would have to traverse the entire scenegraph up to the root.
You can always create a function that returns the relative position of a child in the group.
~Ryan

TT TY

unread,
Aug 24, 2012, 5:07:55 PM8/24/12
to pap...@googlegroups.com
After all four addChild calls but before you move the group, do
alert(circle.position.x);  //  outputs 0
alert(group.position.x);  // outputs 25
So the group x position is 25 more than the circle x position.
Well this is OK, it's expected, but this is not the problem. The problem is:
I create a circle at x = 0; circle.position.x = 0
var circle = new Path.Circle(new Point(0, 0), 50); 
then I create a group with x = 0; circle.position.x = 0
var group = new Group(); 
I add the circle to the the group; circle.position.x = 0
group.addChild(circle) 
I change the position of the group; now the circle.position.x = 25, and the group.positions.x = 50. Shouldn't the circle.position.x === group.position.x?
If not, then I'm really missing something here, sorry.
When I add the circle to the group, this circle is added to the x:0 relative to the center of the group, at least I expected to, but is not happening. Instead
of this, the circle is added at {x: -25} relative to the group, but I don't really know where this x change happened and why.
group.position = new Point(50, 50); 
 
Then you set the group's x position to 50...and since the group's x position is 25 more than the circle's x position [wait? why the group position.x is 25 more than the circle.position.x? I added the circle at the x=0, relative to the group. The group.position.x = 50, then circle.position.x should also be 50 as I suppose is added at the x=0 relative to the group.], we expect circle.position.x to equal 50-25=25.

Perhaps you would rather use group.translate(new Point(50,50));
This will make those results:
circle.position.x = 50, which is right! BUT
group.position.x = 75, why?? It was created at x=0, moved 50 and x should be 50, instead of 75. Something more happened when the circle was added which changed the x of the group.
Hope I have some opportunity to work around this issue.

I disagree that it would be better for the positions to be relative to the parents...then to find the absolute canvas position of any element the code would have to traverse the entire scenegraph up to the root.
You can always create a function that returns the relative position of a child in the group.
Then the group it's not so useful as I thought because moving the circle within the group would be as I would move the circle without a group. In this case the group it's only useful to move a bunch of objects at the time, but not changing their position after. I think I must do something about it because I didn't expected this.

Ryan Beasley

unread,
Aug 24, 2012, 8:16:13 PM8/24/12
to pap...@googlegroups.com
The example you provided adds four items to the group.  Those items do not all have the same position.  So why would the group's position be equal to the circle's position?

The circle is not added to the x:0 relative to the center of the group.  The circle is positioned in absolute coordinates by the instantiation:  var circle = new Path.Circle(new Point(0, 0), 50);   That call puts the circle at (0, 0) on the canvas.   You then add the circle to the group...since addChild does not move the child (in this case the circle), then the circle is still at (0, 0) on the canvas.  As you add items to the group, the group's position is recalculated...I assume that the group is calculating the center of all its children and reporting that as its position.  That's why the group's position is not the same as the circle's position after you add the other three items.  If a group has a single child (the circle), then the group and its child will have the same position.

You cannot add items to positions relative to a Group.  All positioning is in absolute coordinates on the canvas.  If addChild forced all the children's positions to be equal to the group's position, then we would lose the relative positions between the children that is a primary reason for using groups.

Yes, moving the circle in the group is the same as moving the circle outside the group...either is perfectly valid because moving is moving whether in a group or not.  But the group allows you to rotate, translate, scale, and/or variously transform all the children in the group with one function call.

If you need the relative position of a child in a group, just do: relativePos = circle.position - circle.parent.position;

~Ryan

Ryan Beasley

unread,
Aug 24, 2012, 8:25:10 PM8/24/12
to pap...@googlegroups.com
On second thought, I think I understand what you're looking for.  The group does not provide a local state space...so its children are not positioned with respect to the (0, 0) of the group.  Instead, all positioning is done on the global canvas state space (aka axes).

I can think of a few uses for relative transformations, but all of them can be done with global transformations too.

If you need relative transformations and positioning, you can create such functions
e.g.,

setRelativePosition = function(point) {
this.position = point + this.parent.position;
}

~Ryan

Ryan Beasley

unread,
Aug 24, 2012, 9:54:05 PM8/24/12
to pap...@googlegroups.com
Ignore that setRelativePosition function.  The solution is not that simple; I did not think the problem through fully.
~Ryan

TT TY

unread,
Aug 25, 2012, 5:58:02 AM8/25/12
to pap...@googlegroups.com
As you add items to the group, the group's position is recalculated 
This is what messes up the group items. Why would I want to recalculate the position of this, [I assume that the group is calculating the center of all its children and reporting that as its position.as in this case is not even in the center. (If it would it should be 50 as the radius is 50 and the diameter is 100. Can't figure out the 25, really strange IMO.)
Also the group.position.x it's always 50, and never changes.
If I say I want the group at x:50 and y:50 then so should be, no changing it when I add a new child or I move a child inside the group, what a strange design choice.
In my opinion should allow the circle to have negative relative position if it's to the left of the center and not changing all the items and the group when a change is made, this is really unstable, I can't even figure out a case for this complicated and unpractical case.

Ignore that setRelativePosition function.  The solution is not that simple; I did not think the problem through fully. 
I'm already looking for a way to do this, but I should update the position of the item and it's children whenever the parent changed it's position.

Ryan Beasley

unread,
Aug 25, 2012, 10:08:55 PM8/25/12
to pap...@googlegroups.com
I'm going to try to explain what is happening in the code.  If you look at the source, line 2578 of v0.22, you will see that a Group is practically an Item, with just a small amount of extra code.  As an Item, it can have children (see line 2137).  When you ask for the position of a Group, it calls Item.getPosition()  (line 1881), which calls this.getBounds().getCenter().  getBounds (line 2325) runs through the children and finds the minimum and maximum x & y values for the children and returns a Rectangle containing x, y, width, & height.  getCenter then finds the midpoint of that Rectangle.

It's not a design choice that the position of a Group will change as its children move, that is simply a function of how Items are defined: "position" is the center of the bounds, which is the bounding Rectangle that contains all the children.  As you add children, the bounds may change, which would cause the center (aka position) to change.

So let's step through an example...I've removed path2 and circle2 to simplify the problem, leaving just path and circle.

var path = new paper.Path(new paper.Point(0, 0), new paper.Point(100, 0));
alert('path.bounds.x ' + path.bounds.x);  // 0
alert('path.bounds.width ' + path.bounds.width);  // 100
alert('path.position.x ' + path.position.x);  // 50

var circle = new paper.Path.Circle(new paper.Point(0, 0), 50);
alert('circle.bounds.x ' + circle.bounds.x);  // -50   The circle has a radius of 50, and is centered at (0, 0), so it reaches between -50 and +50 in the x dimension.
alert('circle.bounds.width ' + circle.bounds.width);  // 100
alert('circle.position.x ' + circle.position.x);  // 0

//Note how the position.x is always the bounds.x + 0.5*bounds.width...so the x position is at the x-center of the Path.

var group = new paper.Group();
alert('group.bounds.x without children ' + group.bounds.x);  // 0
alert('group.bounds.width without children ' + group.bounds.width);  // 0
alert('group.position.x without children ' + group.bounds.getCenter().x);  // 0    so a newly created group has no width and is at x = 0

group.addChild(path);
alert('group.bounds.x with path ' + group.bounds.x);  // 0
alert('group.bounds.width with path ' + group.bounds.width);  // 100
alert('group.position.x with path ' + group.bounds.getCenter().x);  // 50

// A Group with a single child has the same bounds and position as that child.

group.addChild(circle);
alert('group.bounds.x with path and circle ' + group.bounds.x);  // -50
alert('group.bounds.width with path and circle ' + group.bounds.width);  // 150
alert('group.position.x with path and circle ' + group.bounds.getCenter().x);  // 25

// The bounds.x for a Group with multiple children is equal to the minimum bounds.x in all the children.  The circle has a bounds.x of -50, which is lower than the path's bounds.x of 0, so the group's bounds.x is -50
// The circle stretches between -50 and +50 in x, while path stretches from 0 to 100 in x.  Therefore the group stretches from -50 to 100 in x, giving it a width of 150.
// The position of the group is then the center of its bounds rectangle...which is bounds.x + 0.5*bounds.width = -50 + 0.5 * 150 = -50 + 75 = 25.  Thus 25 is exactly what we expect for group.position.x


The group.position.x is not always 50...you can translate and reposition the group and watch it's position.x change.  Or as you add and remove children its position can change.

Note that the group has multiple children, not just the circle.  So the group's bounds must encompass all of its children, and therefore the group's position (i.e., its center) is a function of all the children.  If the group's only child was the circle, then the group's position would be the same as the circle's position.

Have you tried moving the group more than 50 in x?  That might help your intuition/understanding.   group.position = new paper.Point(500, 150);

By saying you want the group to be fixed in place, you seem to be assuming that the group has its own x-y axes that children can be positioned upon....then the group would be positioned on the canvas's absolute x-y axes, and the children would be positioned on the group's relative x-y axes.  But a Group is (practically) just an Item with children...it is better to think of a Group as a way to distribute rotate/translate/transform/scale operations among several (children) Items.
What you are describing is not a paperjs Group.  You may want to extend a Group to create the thing you are talking about (or put in a feature request on github), but I would recommend not re-defining "position" from its current definition of the center of the bounds.

What are you trying to do in the first place?  Your original code successfully put the group in the center of the view.  Did you want the circle in the center of the view instead?

Have you looked at code that successfully uses Groups?  Often once a Group has been created it does not gain children, lose children, or have its children moved except when the entire Group moves...thus avoiding such concerns about the recalculation of "position".
or 

hope this helps,
~Ryan

Simon Epskamp

unread,
Feb 21, 2014, 8:37:30 AM2/21/14
to pap...@googlegroups.com
Use this to keep position's relative to the group's coordinate system:

group.transformContent = false;
 
Works for layers as well.

Stefan Ix

unread,
Jul 14, 2016, 6:33:28 AM7/14/16
to Paper.js

This post saved my day!!!! Turning off transformContent is the only sane option when you come from an openGL background.

Jürg Lehni

unread,
Jul 14, 2016, 7:59:05 AM7/14/16
to pap...@googlegroups.com
Please note that #transformContent is deprecated, the property is now called #applyMatrix. Function is the same.

--
You received this message because you are subscribed to the Google Groups "Paper.js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to paperjs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Justin _

unread,
Jun 18, 2018, 3:26:38 AM6/18/18
to Paper.js
I was trying to make a simple game using paper.js and ran into this madness..
adding items to a group.. changes the group position. i don't understand what's going on at all.
and the sub item positions are changed too.

i think i might just switch to phaser.
Reply all
Reply to author
Forward
0 new messages