Skip to main content

JavaFX1.2: Understanding Bounds

Posted by aim on July 9, 2009 at 8:15 PM PDT

This is the first in a series of articles to cover layout for JavaFX 1.2. We changed the api a bit (for the better of course), but that hasn't made it any easier to explain. However, if you're an impatient reader and want to cut to the chase, I recommend jumping to the tailing section Bounds in Practice.


The visuals displayed within a JavaFX scene are fully represented by a 2D scene graph where each visual element (line, path, image, etc) is represented by a distinct node with variables that can be easily manipulated dynamically. The node's size and position (otherwise known as its "bounds") becomes complicated when considering these many variables which contribute to its bounds, such as shape geometry (startX/startY, width, radius, etc), transformations (scale, rotate, etc), effects (shadows, glow, etc), and clipping. Understanding how each of these variables affects the bounds calculations of a node is crucial to getting the scene layout you want.


Bounds Class

In JavaFX1.2, a node's rectangular bounds are represented by the Bounds class which provides init-only minX, minY, maxX, maxY, width, height variables. Keep in mind that since the bounding box can be anywhere in the 2D coordinate space, the X/Y values may often be negative.


Note: The Bounds class was introduced in JavaFX1.2 in anticipation of adding Z in the future. BoundingBox (concrete 2D implementation of Bounds) has replaced Rectangle2D for bounds values, however Rectangle2D still exists as a general purpose geom class for other uses.


Node Bounds Variables

A Bounds object is always relative to a particular coordinate system, and for each node it is useful to look at bounds in both the node's local (untransformed) coordinate space, as well as in its parent's coordinate space once all transforms have been applied. The Node api provides 3 variables for these bounds values:


boundsInLocal (public-read) physical bounds in the node's local, untransformed coordinate space, including shape geometry, space required for a non-zero strokeWidth that may fall outside the shape's position/size geometry, the effect and the clip.
boundsInParent (public-read) physical bounds of the node after ALL transforms have been applied to the node's coordinate space, including transforms[], scaleX/scaleY, rotate, translateX/translateY, and layoutX/layoutY.
layoutBounds (public-read protected) logical bounds used as basis for layout calculations; by default only includes a node's shape geometry, however its definition may be overridden in subclasses. It does not necessarily correspond to the node's physical bounds.




It's worth pointing out that a node's visibility has no affect on its bounds; these bounds can be queried whether its visible or not.



This might be easier to visualize in the diagram below, which shows the sequence in which the bounds-affecting variables are applied; variables are applied left-to-right where each is applied to the result of the one preceding it (geometry first, layoutX/Y last):



NodeChainBounds3.png



The reason we need a more malleable definition for layout bounds (vs. just using boundsInParent) is because a dynamic, animating interface often needs to control which aspects of a node should be factored into layout vs. not (more on this in a forthcoming article on 1.2 layout).


Note: In JavaFX1.0, layoutBounds was originally defined to be boundsInLocal plus the transforms[] sequence; we changed this in 1.2 because we found that often layout did not want to factor in the effect or transforms and it feels cleaner to have no transforms included (vs. 'some' transforms). It also greatly simplifies implementations of Resizable nodes.



Stepping Through a Node Bounds Example



Example 1: Simple Rounded Rectangle

rectangle1b.png



Note that x and y are variables specific to Rectangle and that they position the rectangle within its own coordinate space rather than moving the entire coordinate space of the node. I throw this out as the first example because it is often the first thing that trips up developers coming from traditional toolkits such as Swing, where changing x,y effectively performs a translation of the component's coordinate space.
All of the javafx.scene.shape classes have variables for specifying appropriate shape geometry within their local coordinate space (e.g. Rectangle has x, y, width, height, Circle has centerX, centerY, radius, etc) and such position variables should not be confused with a translation on the coordinate space, as we'll look at in our next example.




Example 2: Translation

rectangle2c.png




Now boundsInParent has changed to reflect the translated rectangle, however boundsInLocal and layoutBounds remain unchanged because they are relative to the rectangle's coordinate space, which was what was shifted. Although layoutBounds is relative to the node's local coordinate space, I'm showing it in the parent's to emphasize how a parent container interprets its value when laying out the node.


inside design note: we debated endlessly on whether to rename translateX,translateY to "x","y" (as it's less typing and more familiar to traditional toolkit programming), however we decided that "translate" was more descriptive in the 2D sense and keeping it avoided renaming the x,y position variables in some shape classes.



Example 3: Effect


rectangle2b.png




The drop shadow is included in the physical bounds (boundsInLocal and boundsInParent), however it is not included in layoutBounds, which is often desirable for layout where the drop shadow is considered a subtle decoration rather than part of the object itself.




Example 4: Rotation & Scale


rectangle4.png




layoutBounds remains unchanged (from the perspective of the parent), even as the node's local coordinate system is scaled and rotated.




Group Bounds

The bounds of a Group node have a slight twist: layoutBounds is calculated by taking the union of the boundsInParent on all visible children (invisible children are not factored into its bounds). boundsInLocal will take the layoutBounds and add any effect or clip set on the group. Finally, the group's boundsInParent is calculated by taking boundsInLocal and applying any transforms set on the group.




Example 5: Group Bounds


group2.png




The group's layoutBounds are tightly wrapped around the union of it's children boundsInParent and do not include the drop shadow.



Bounds in Practice

Now that you understand (in excruciating detail) the difference between these bounds variables, you might be wondering why you really need to know or care (as did J.Giles in his blog). Aside from the personal glory of mastering the complexity of 2D geometry, it comes mostly into play when laying out your scene. Here are a handful of practical tips:



Remember that Bounds minX/Y and maxX/Y can be negative; don't assume nodes are anchored at their origin. (e.g. Circle is centered on 0,0).



Remember that layoutBounds is a logical concept -- layoutBounds needn't be tied to the physical bounds of a node and often you don't want it to be (ex. you want to spin an icon without disturbing the layout of its neighbors).



Always use layoutBounds when measuring the current size and position of nodes for the purpose of layout (either when using binding for layout or authoring Container classes).



Be aware of the layoutBounds on nodes you throw in Containers as all containers in javafx.scene.layout reference the node's layoutBounds.



If you want layoutBounds to match a node's physical bounds (including effects, clip, and transforms) then wrap it in a Group (ex. if you want a node to scale up on mouse-over and you want its neighbors to scoot over to make room for the magnified node).



You should rarely need to use boundsInParent.



You almost never need to use boundsInLocal.



Finale: Boundisizer Demo

During our JavaFX Controls session at JavaOne, I flew through an hour's worth of layout material in the meager 15 minutes that Rich and Jasper left me (the perils of speaking last!). In the session I showed a little demo that lets you tweak the variables and watch how the various bounds are affected. Action speaks way louder than words or color-coded diagrams:





Run the demo and browse the source at JFXtras .


Related Topics >>

Comments

I have uploaded a Java program to demonstrate how the bounds ...

I have uploaded a Java program to demonstrate how the bounds of a node are computed.

http://jdojo.com/wp-content/uploads/2012/11/NodeBoundsApp.zip

The ZIP file contains only one Java class. It can be run standalone. It requires JavaFX 2.2. The program lets you apply effects and transformations to a rectangle and shows its bounds as you make changes. It also lets you save the node with all coordinate axes with the current state of the scene in PNG format. A detailed post will follow. I will post the link to that post when it is ready.

Thanks
Kishori

Hello Amy, Thanks for this great article elaborating ...

Hello Amy,
Thanks for this great article elaborating different bounds of nodes.

I have a question about the way the boundsInLocal are shown in following three diagrams:
Example 2: Translation
Example 3: Effect
Example 4: Rotation & Scale

The bounds of a node are always described (and are meaningful) in a particular coordinate space. In Javadoc and in the beginning of this article, it is stated that boundsInLocal is in node's local, “untransformed” coordinate space. When we apply transformations to a node, we have three coordinate spaces at play:

1. The coordinate space of the parent
2. The untransformed coordinate space of the node
3. The transformed coordinate space of the node

In the above-mentioned three diagrams, you have shown the boundsInLocal for the rectangle relative to the transformed coordinate space of the node, which, in my opinion, is wrong. The boundsInLocal is relative to the "local, untransformed" coordinate space of the node, not the "local, transformed" coordinate space of the node.

These diagrams have shown the values for the (x, y) coordinates, width and height correctly. Because the (x, y) coordinates need to be interpreted always with respect to the untransformed coordinate space of the node, it has some wrong consequences, when you show the boundsInLocal w.r.t. the transformed coordinate space of the node. The untransformed coordinate space of the node and the coordinate space of the parent are the same in these cases. Therefore, boundsInLocal for the rectangle should be interpreted the same in these coordinate spaces: the untransformed local coordinate space and the parent coordinate space. However, showing the boundsInLocal relative to the transformed coordinate space of the node will result in a different Bounds, when they are interpreted relative to the parent coordinate space.

I have another question. Javadoc and your article have not mentioned “directly” about the coordinate space of the layoutBounds. Is it not also defined relative to the local, untransformed coordinate space of the node?

Thanks
Kishori

Hi, Amy Fowler. Rakesh blogs (http://blogs.sun.com/rakeshmenonp/entry/javafx_bind_with_caution) about the issues pertaining to inefficient trigger on initial value declaration with assignment, and the slider's firing on value change similar to what mention by Mikael. Maybe there are workarounds however explicit support will be best way. May I suggest the following solution to the problem: 1) Problem: Unwanted firing by trigger during value declaration with assignment. Sometimes, a bunch of related variables call the same function when triggers. However there are many cases the developer does want that to happen during value declaration with assignment so the function endup having to check for valid condition which is inefficient and make the code looks inelegant. Taken from Rakesh 's example. var x = 0 on replace { update(); } var y = 0 on replace { update(); } var width = 0 on replace { update(); } var height = 0 on replace { update(); } function update() { if((x > 0) and (y > 0) and (width > 0) and (height > 0)) { // Here perform valid check /** * Performs complex update process! */ } } Solution -====== Introduce the "next" keyword so that trigger happen only at next assignment. For example, var x = 1023 on next replace { update() } // only next value change of x will fire update() var y = 1023 on next replace { update() } function update() { // Function body no longer require check. } 2) Slider component keeping updating as user move the slider's bar The problem as highlighted Mikael . Solution ======= Add some kind of ValueAdjusting support just like the JSlider in Swing eg var slider = Slider { min: 0 max: 100 value: 50 valueAdjusting: { // function} }

Amy's Boundisizer example is now featured on the JFXtras Samples page. You can run the application, download the project, or browse the source code online: http://jfxtras.org/portal/samples

An un-expected result, of course...

Interesting article, I like the advices at the end and the insider views... Another interesting article (and companion test application) on the topic: http://blogs.sun.com/baechul/entry/node_bounding_rectangles_coordinates It shows an expected result: when we apply an effect (shadow) and a little rotation (eg. 45°), the boundInParent extends much farther than the extend of the shadow. Is that a bug we can expect to be fixed in next version, or is there some explanation on this behavior? Thanks.

@lstroud: the actual position of the rectangle looks to be off slightly in the shadow/rotate case you point out; I suspect this is a bug which I will whittle and file.

@mikael: The sliders do feel sluggish (especially width/height & x/y) and I'll investigate why. The demo makes heavy use of binding, which in theory is a perfectly acceptable practice in a JavaFX app, however we've found an over-use of binding can cause problems. My suspician is that the continuous resizing of the rectangle is causing a tremendous amount of garbage as the various levels of the scene-graph deal with it. We have a big focus on performance for the next JavaFX release. An app such as this should feel snappy.

@opinali: You're right about example 3! (a cut & paste error in illustrator) I fixed the values, which are actually 119x119 -- I'll have to check with Chris C. to find out why it's larger than the offsetX/Y values on DropShadow. Thanks for pointing that out.

Hello Amy,

I think the community would greatly benefit code that is really responsive. I mean one that would decouple the movement of the sliders and the rendering of the graphics. Now, at least on a newly updated fast Mac, movement of the sliders are very sluggish as soon as the shadow is enabled. This is probably because the rendering of both the gfx and the sliders is happening on the EDT, consecutively.

Developers need to learn right away that this is not the way to do it, ever. Rendering, unless it is always very fast, need to be decoupled from the rendering of the thing you are adjusting (slider). Remember, anything above 0.1s is sluggish according to several usability tests.

Writing demo apps is a big responsibility since it will set the standard for many developers. You, as in Sun, need to pump up the quality of the micro demos, IMO.

Great article btw. And I really mean that. :)

Cheers,
Mikael Grev

Ok, I think I get it. I do have one question, though. In your example applet, when I show the local bounds, add a drop shadow, and rotate the object, the object will extend outside of the local bounds (slightly). I'm not talking about the additional pixels that are emcompassed by the drop shadow. I am talking about the purple getting on the other side of the blue line. Clearly, this is related to the drop shadow, but shouldn't the local bounds be taking that into account?

The applet is not working anywhere, I'm getting an error too - JNLP file not found (http://download.java.net/javadesktop/blogs/aim/boundisizer/Boundisizer_b..., I checked this manually). Also, I think in Example 3, boundsInLocal and boundsInParent should be> 30, 30 110x110, right?

Eeeh, why JFX applets don't work on firefox under ubuntu? :(