© Alexandre Bergel 2022
A. BergelAgile Visualization with Pharohttps://doi.org/10.1007/978-1-4842-7161-2_9

9. Shape Composition

Alexandre Bergel1  
(1)
Santiago, Chile
 

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.

All the code provided in this chapter is available at https://github.com/bergel/AgileVisualizationAPressCode/blob/main/02-05-ShapeComposition.txt.

Composite 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):
canvas := RSCanvas new.
1 to: 10 do: [ :i |
    g := RSGroup new.
    i timesRepeat: [ g add: RSCircle new ].
    RSFlowLayout on: g.
    canvas add: g asShape.
].
RSGridLayout new gapSize: 50; on: canvas shapes.
canvas shapes @ RSDraggable @ RSHighlightable red.
canvas zoomToFit.
canvas open
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig1_HTML.jpg
Figure 9-1

Simple example of composed shapes

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):
canvas := RSCanvas new.
1 to: 10 do: [ :i |
    g := RSGroup new.
    i timesRepeat: [ g add: RSCircle new ].
    RSFlowLayout on: g.
    compositeShape := g asShape.
    compositeShape borderColor: Color black.
    compositeShape padding: 5.
    canvas add: compositeShape.
].
RSGridLayout new gapSize: 50; on: canvas shapes.
canvas shapes @ RSDraggable @ RSHighlightable red.
canvas zoomToFit.
canvas open
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig2_HTML.jpg
Figure 9-2

Setting a border with a padding to the composed shapes

Labels may be part of the composition. This is often useful for naming a composite shape. Consider this third revision of the script (see Figure 9-3):
canvas := RSCanvas new.
1 to: 10 do: [ :i |
    g := RSGroup new.
    i timesRepeat: [ g add: RSCircle new ].
    RSFlowLayout on: g.
    title := RSLabel text: i.
    RSVerticalLineLayout new alignCenter; on: { title . g }.
    g add: title.
    compositeShape := g asShape.
    compositeShape borderColor: Color black.
    compositeShape padding: 5.
    canvas add: compositeShape.
].
RSGridLayout new gapSize: 50; on: canvas shapes.
canvas shapes @ RSDraggable @ RSHighlightable red.
canvas zoomToFit.
canvas open
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig3_HTML.jpg
Figure 9-3

Adding a title to the composed shape

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 }.
    g add: title.
    compositeShape := g asShape.
    compositeShape borderColor: Color black.
    compositeShape padding: 5.
    canvas add: compositeShape.
].
RSGridLayout new gapSize: 50; on: canvas shapes.
canvas shapes @ RSDraggable @ RSHighlightable red.
canvas zoomToFit.
canvas open
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig4_HTML.jpg
Figure 9-4

A composing shape can contain lines

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:
c := RSCanvas new.
compoShape := RSComposite model: 'Hello' forIt: [ :composite :title |
    composite add: (RSLabel text: title).
    composite add: (RSCircle new size: 20).
    RSVerticalLineLayout new alignCenter; on: composite shapes.
    composite @ RSDraggable ].
c add: compoShape.
c open
Composite shapes can have a model and can be used by Line Builder. Consider the following example (see Figure 9-5):
classes := ByteArray withAllSubclasses.
c := RSCanvas new.
boxes := RSComposite models: classes forEach: [ :composite :cls |
    composite add: (RSLabel new text: cls name).
    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
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig5_HTML.png
Figure 9-5

Composing shapes

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):
classes := ByteArray withAllSubclasses.
c := RSCanvas new.
boxes := RSBox models: classes forEach: [ :s :cls |
    s size: cls numberOfLinesOfCode sqrt + 5.
    s @ RSDraggable ].
c addAll: boxes.
boxes @ RSLabeled.
lb := RSLineBuilder line.
lb withVerticalAttachPoint.
lb shapes: boxes.
lb connectFrom: #superclass.
RSTreeLayout on: c nodes.
c @ RSCanvasController.
c open
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig6_HTML.jpg
Figure 9-6

Using RSLabeled to label shapes

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):
classes := ByteArray withAllSubclasses.
c := RSCanvas new.
boxes := RSBox models: classes forEach: [ :s :cls |
    s size: cls numberOfLinesOfCode sqrt + 5.
    s @ RSDraggable ].
c addAll: boxes.
boxes @ RSLabeled highlightable.
lb := RSLineBuilder line.
lb withVerticalAttachPoint.
lb shapes: boxes.
lb connectFrom: #superclass.
RSTreeLayout on: c nodes.
c @ RSCanvasController.
c open
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig7_HTML.jpg
Figure 9-7

Highlighted RSLabeled

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
../images/489192_1_En_9_Chapter/489192_1_En_9_Fig8_HTML.jpg
Figure 9-8

Labeled circles

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.150.119