Blogs

SOBRE ESTE BLOG

This community site is for software developers interested in topics related to Web 2.0 and mobile device user interface development. This includes both general trends and technology discussions, as well as specific discussions and other resources involv

Marcações

TWEETS RECENTES

Dojox.Gfx Recipe #1 : Composite Shapes, the basics

The dojox.gfx API provides a set of basic graphic shapes that cover all the 2D shape types spectrum. While this set of shapes allows building all kind of drawings made of individual shapes, complex graphical representations requires a different structure in order to be handled as one unique graphic object. It is particularly true when building business applications
that use specific symbology. The answer to this use case is composite 2D shape.

Composing 2D shapes : the Group shape

The idea behind composite shape is to group individual shapes into one logical shape that becomes the graphical object the user interacts with. So instead of handling several 2D primitives individually, there is only one logical shape.

In gfx, it is described by the dojox/gfx/Group class. This class inherits from the dojox/gfx/shape.Shape class (which makes it a standard shape), and mixes the Container and Creator implementations. In other words, this is a true graphic object that handles child shapes that can be dynamically created from, added to or removed from it.

Creating a composite shape

Let’s take a concrete example of a simple composite shape. In this example, we want to build a symbol like this one:

The symbol is made of a round rectangle in the background, a picture on the left, and four text lines. The first step is to create the Group instance that is going to contain the shapes mentioned previously:

If you run the application now, you may notice there is nothing displayed on the surface. The reason is that a Group shape has no graphical representation by itself. It is a pure logical container that handles a list of children. Its graphical representation is actually the one of its children.

So the next step is to populate the symbol with the child shapes (aka the picture and the texts). Let’s create the shape:

You may have noticed that the rectangle is created directly from the symbol and not from the surface, as the symbol is. It is because the gfx API also provides the shape creation methods on the Group class itself. So, when invoking the Group.createRect() method, the background Rect is added to the Group. From this point, the rect is defined as a child of symbol, and symbol defined as the parent (aka the container ) of bg. Running the application at this point displays the new rectangle, that is, the symbol content.

Note that in case a Group instance must be populated with a shape that has already been created (that is, already exists in the gfx scene), the Group class also exposes the Group.add() method, which takes as parameter a Shape instance to be added to the group. Similarly, it also exposes the Group.remove() and Group.clear() methods to remove a specific shape or all its children.

Following the same methodology, the remaining text shapes are created. Here is the full initialization code:

Order of drawing, aka z-order

In the previous example, you may notice that the background rectangle is the first shape we created and added to the group. The reason we did it this way is because we want the other shapes (the image and the texts) to be drawn on top of the background. Indeed, a Group draws its children in the order they have been added, which means the first shape in the children list is drawn first, then the second in the list, and so on and so forth, to the last one, which is drawn on top of all shapes. In other words, the first shape in the list lies at the back of the drawing, and the last one at the front of the drawing. This drawing order is usually called the z-order (‘z’ being the name of an imaginary axis going from the screen to the user eye).

In case the z-order needs to be changed dynamically after the creation of the shapes, the Gfx API provides the following methods :

Shape.moveToFront(): this method moves the shape to the end of its parent’s list, or in other words, to the front of the drawing (will be drawn on top of the other children).

Shape.moveToBack(): this method moves the shape to the start of its parent’s list, or in other words, to the back of the drawing (will be drawn below the other children).

Note: In 1.9, the z-order API will be extended to allow specifying the position of a child shape relative to another child. As of this writing, the proposed API is:

Shape.moveBefore(refShape): moves the shape before the specified reference shape.

Shape.moveAfter(refShape): moves the shape after the specified reference shape.

Group.insertBefore(newShape, refShape): insert the new shape before the specified reference shape.

Group.insertAfter(newShape, refShape): insert the new shape after the specified reference shape.

The corresponding dojo ticket that describes this proposal can be found here: http://bugs.dojotoolkit.org/ticket/15296 . Feel free to comment the proposed API on the ticket. You can test it locally by applying the patch attached to the ticket on a trunk or 1.8 src install. Also, bear in mind that as of this writing, it is still a proposal and no commitment can be made for its availability in the next release.

Dealing with transforms

As stated previously, one of the benefits of the composite approach is the ability to handle a set of shapes as only one logical shape. This is particularly useful when one needs to handle interactions at the group level (more on this later) or change the overall position of the group. For example, in the latter case, instead of changing the position of every symbol shapes, one would only have to translate the Group shape.

Transform propagation

An easy and quick way to see how a container transform impacts the children shapes is to use the dojox/gfx/Moveable class. The purpose of this class is to easily enable moving a shape using a mouse drag interaction. The underlying implementation consists of applying a translation transform to the dragged shape. So, taking the previous example, let’s add the corresponding module to the list of the require and make our Group instance a Moveable:

From this point, dragging any of one of the symbol shapes (the image, text, etc.) moves the whole symbol. Or, in term of API, the translation transform set on the group while dragging it impacts all the children shapes.

So, what’s happening ? As we saw, a Group is a shape, and as such, may have its own transform, as all shapes in gfx do. Also, remember that a shape transform is a matrix that converts the shape coordinates/geometry into its parent coordinate system. For example, a rect with the {x:10,y:0,width:100,height:100} geometry and a translation transform of {dx:10, dy:20} will be considered (i.e. displayed) at the position {x:20,y:20} in the parent surface.

So, similarly, setting a transform on a Group has the very same meaning: the “geometry” of the group (i.e.the set of its children geometry) is converted by the group transform to its parent coordinate system.

Note that transforms are cumulative. If a shape that has its own transform and is added to a Group which itself has a transform too, both combined together. So, for example, if a rect has a translation transform {dx:5} and the group has a translation transform {dx:10}, the shape will be translated by a delta-x of 15 in the surface.

Coordinates conversion from container to container

A usual pattern when using Group is to have nested containers (containers inside containers). When such nested hierarchy exists, it is often required to compute coordinates of nested children into the coordinate system of another container. Gfx does not provide anything built-in for this, but the required API to implement it is there.

The following method computes the transform from the shape coordinate system, taking into account the shape transform, to the specified container coordinate system.

A brief explanation for the sake of completeness: the code first gets the two transforms that goes up to the surface for each container (the _getRealMatrix calls), and then computes the new matrix that first applies the shape-to-surface transform (t1) then the surface-to-container transform (which is the invert of t2, the container-to-surface transform).

With this method, it is easy to express a coordinate in the coordinate system of another container. For example, still using the previous sample, assume we want to draw a line whose one extremity is attached to the picture, right in the center of the face, and we want the line end sticks to this connection point when the symbol is dragged. Let’s say the center of the face in the image corresponds to the {x:37, y:33} point (let’s call it ‘facePt’) in the image coordinate system (that is, {0,0} being the top left corner of the image). Also, the line is not part of the symbol but is added to another independent container (let’s call it ‘link’). So, in term of coordinates, it means the {x2,y2} property (i.e. the line end point) of the Line shape must be computed in the coordinate system of the ‘link’ container (because this container is the Line parent) from ‘facePt’ whose coordinates are expressed in the Image shape coordinate system. In other words, we need to convert ‘facePt’ from the image coordinate system to the link coordinate system.

Since the attach point of the line must sticks to the “face” when dragging the symbol, the point location must me recomputed on each move. To do this, a onMoved handler is set on the Moveable instance to get notified of the move:

Run the application and drag the symbol and/or the link: the attach point of the line sticks to the “face” of the symbol.

Interaction

As with any Gfx shapes, the Group shape supports input events handling via the Shape.connect() API. This method allows registering event handlers on a Group shape in order to be notified of any events that occurred on one of the Group children.

For example, let’s add some interactions to the previous sample. We would like the user be able to change the color of the symbol background rectangle on a mouse click, wherever the click occurs in the symbol. We could register an event handler on the rect shape itself, but in this case, clicking on the image or the text would not notify the handler. So instead, the handler is registered on the symbol, so that it receives the event notifications from `all its children.

Note that the parameter received in the event handler is the native DOM event. In some cases, you may also need to have a reference to the gfx shape that fired the event. To this purpose, the 1.7 release introduced a new expando property on the event (named “gfxTarget”) that references the target gfx shape. To illustrate its use, let’s change the previous “onclick” event handler implementation, so that when the user clicks on the symbol, it highlights the symbol child shape that has been clicked. The highlight is done via a new Rect shape that is added to the symbol, and whose geometry is set to the target shape bounding box.

Conclusion

When building business applications that require rich visualization, the composite approach and its Gfx implementation via the Group class offer an efficient answer to implement complex graphical representation. In this first recipe, we presented the basics to define a shape container by means of the Group class. We explained how to deal with deep nested container hierarchy to build complex graphic symbol and handle coordinates conversion as well as how to control the rendering via the children z-order. Finally, we illustrated how to handle user interactions at the symbol level. In the next recipe, we will learn how to go one step further in representing business data with rich graphic symbol, creating our own Group subclass and implementing the symbol behavior according to the data logic.

The sample source code used for this article is freely available here.