The previous chapters presented primitive shapes that are offered by Roassal. An important asset of Roassal is the composition of visual elements. This chapter reviews the mechanism to build composed shapes from primitives shapes.
Shapes can be composed in order to build sophisticated visual components. The RSGroup class is a collection of shapes and offers various convenient methods to perform operations on a set of shapes. A simple way to create a composite shape is to send asShape to an RSGroup. This message is used to convert a group of shapes into a single composed shape. Consider the following example (see Figure 9-1):
A layout and interactions can be applied to the added shapes, pretty much the same way that you do with non-composite shapes. In the loop, you create a group g to which you add some shapes. A flow layout is performed on the shapes to be composed using RSFlowLayout on: g. The group is converted into a composite shape with g asShape. In total, ten shapes, all composite, are directly added to the canvas. The grid layout operates on these composite shapes. Each of these can be highlighted by moving the mouse above them.
A shape can also have a border, simply by sending borderColor:. Often, padding is necessary to set the distance between the nested shapes and the border. The padding is set using padding: and expects a number as the argument. Consider the revised version of the script (see Figure 9-2):
In the script, a title is created, and a vertical line layout is performed on two elements: title and g. After the layout, the title is added to the group. This example illustrates the fact that shapes can go through a complex layout before being transformed into a composed shape.
Lines can also be added to a composite shape. Consider the following code (see Figure 9-4):
canvas := RSCanvas new.
1 to: 100 by: 10 do: [ :i |
g := RSCircle models: (1 to: i) forEach: [ :s :nb | s color: Color random translucent ].
lb := RSLineBuilder line.
lb canvas: g.
lb shapes: g.
lb connectFrom: [ :nb | nb // 2 ].
RSRadialTreeLayout on: g nodes.
title := RSLabel new fontSize: 20; text: i.
RSVerticalLineLayout new alignCenter; on: { title . g }.
The group g is provided to the Line Builder using canvas:. This has the effect of making the builder generate lines in the group g.
Model Object in Composite
The RSComposite class describes a shape that other shapes can be added to. Under the hood, sending asShape creates a RSComposite object. Consider the following code:
composite add: (RSBox new size: cls numberOfLinesOfCode sqrt + 5).
RSVerticalLineLayout new alignCenter; on: composite children.
composite @ RSDraggable ].
c addAll: boxes.
lb := RSLineBuilder line.
lb withVerticalAttachPoint.
lb shapes: boxes.
lb connectFrom: #superclass.
RSTreeLayout on: c nodes.
c @ RSCanvasController.
c open
The models:forEach: method takes two arguments:
A collection of objects, in which each object is used as a model of a new composite shape.
A block expecting two arguments: the new composite shape and the model object. The block is meant to fill the composite shape for a given model.
Labels Part of a Composition
In Roassal, a shape can augmented with a label in two different ways. Either by composing shapes (as you saw previously), or by using an interaction (RSLabeled). Consider the equivalent of the script given here, which uses RSLabeled instead of the composition (see Figure 9-6):
The visualization is significantly smaller. But as a consequence, all the labels are overlapping. You can use an option on RSLabeled to make them more apparent (see Figure 9-7):
A composite shape has a boundary box that encompasses all its nested shapes. As such, the layout considers this boundary box. However, when using RSLabeled, a label is added to the view and it does not take it into account when computing the labeled shape. Using a composite shape or an interaction depends very much on the effect that you want to have.
Labeled Circles
A frequent need when visualizing data is to have a shape with a label in its center. Consider the following script (see Figure 9-8):
canvas := RSCanvas new.
1 to: 30 do: [ :aNumber |
labeledCircle := { RSCircle new size: 30 . RSLabel new color: Color black; text: aNumber } asGroup asShapeFor: aNumber.
canvas add: labeledCircle ].
lb := RSLineBuilder line.
lb withBorderAttachPoint.
lb shapes: canvas nodes.
lb connectFrom: [ :v | v // 2 ].
RSTreeLayout on: canvas nodes.
canvas @ RSCanvasController.
canvas open
The Pharo syntax { ... } creates an array, which is converted to a RSGroup by sending asGroup to it. The asShapeFor: message creates a composite shape and provides a number as a model. This model is then used by Line Builder.
What Have You Learned in This Chapter?
Shape composition is an important asset of Roassal. A non-trivial visualization will likely involve composed shapes as a way to represent expressive shapes. This chapter highlights the concept of a composite shape as follows:
Simply sending asShape to a group converts it to a composite shape, ready to be added to a canvas.
A composite shape can have a border and a padding, which is useful to delimit its border.
A composite shape may have an object model, the same way that a non-composed shape does.
An alternative way to label shapes is using the RSLabeled class.