Agile Visualization
Agile Visualization
Alexandre Bergel
ii
The contents of this book are protected under Creative Commons Attribution-ShareAlike 3.0
Unported license.
You are free:
Attribution. You must attribute the work in the manner specified by the author or licensor (but
not in any way that suggests that they endorse you or your use of the work).
Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting
work only under the same, similar or a compatible license.
• For any reuse or distribution, you must make clear to others the license terms of this
work. The best way to do this is with a link to this web page: creativecommons.org/licenses/
by-sa/3.0/
• Any of the above conditions can be waived if you get permission from the copyright
holder.
• Nothing in this license impairs or restricts the author’s moral rights.
Your fair dealing and other rights are in no way affected by the above. This
is a human-readable summary of the Legal Code (the full license):
creativecommons.org/licenses/by-sa/3.0/legalcode
List of Figures ix
1 Introduction 1
1.1 Agile Visualization . . . . . . . . . . . . . . . . 2
1.2 The Roassal visualization engine . . . . . . . . . . . 2
1.3 Online and free version of this book . . . . . . . . . . 2
1.4 The Roassal community . . . . . . . . . . . . . . 2
1.5 Other visualization platforms . . . . . . . . . . . . 3
1.6 If you do not know Pharo . . . . . . . . . . . . . . 3
1.7 If you are already a Pharo/Smalltalk programmer . . . . . 4
1.8 Improving the book and Roassal . . . . . . . . . . . 4
1.9 Acknowledgements . . . . . . . . . . . . . . . . 5
I Roassal
2 Quick Start 9
2.1 Basic Installation . . . . . . . . . . . . . . . . . 9
2.2 Running Roassal . . . . . . . . . . . . . . . . . 10
2.3 First visualization . . . . . . . . . . . . . . . . . 10
2.4 Visualizing the file system . . . . . . . . . . . . . . 12
2.5 Geographical CSV data . . . . . . . . . . . . . . . 13
2.6 Seism activity over time . . . . . . . . . . . . . . 15
2.7 Charting . . . . . . . . . . . . . . . . . . . . 16
2.8 Timeline . . . . . . . . . . . . . . . . . . . . 20
2.9 Integration with OpenStreetMap . . . . . . . . . . . 21
iv Contents
3 Pharo In a Nutshell 25
3.1 Going over an example, Step-by-step . . . . . . . . . . 25
3.2 Pillars of Object-oriented programming . . . . . . . . . 27
3.3 Message sending . . . . . . . . . . . . . . . . . 28
3.4 Object creation . . . . . . . . . . . . . . . . . . 30
3.5 Class creation . . . . . . . . . . . . . . . . . . 31
3.6 Method creation . . . . . . . . . . . . . . . . . 31
3.7 Block closure . . . . . . . . . . . . . . . . . . 37
3.8 Control Structure . . . . . . . . . . . . . . . . . 38
3.9 Collection. . . . . . . . . . . . . . . . . . . . 38
3.10 Cascade . . . . . . . . . . . . . . . . . . . . 39
3.11 A bit of Meta-programming . . . . . . . . . . . . . 40
3.12 Summary and Further Reading . . . . . . . . . . . . 40
5.9 Normalization . . . . . . . . . . . . . . . . . . 66
5.10 Expressing constraints . . . . . . . . . . . . . . . 71
5.11 Shape and Edge builder . . . . . . . . . . . . . . 75
5.12 Dynamically update . . . . . . . . . . . . . . . . 77
5.13 Roassal visualization in a web browser . . . . . . . . . 80
5.14 Debugging a visualization. . . . . . . . . . . . . . 80
6 Coloring 81
6.1 Palettes . . . . . . . . . . . . . . . . . . . . 81
6.2 Multi-linear color normalizer. . . . . . . . . . . . . 82
6.3 Coloring objects . . . . . . . . . . . . . . . . . 86
7 Interactive Visualization 89
7.1 Draggable elements and view . . . . . . . . . . . . 89
7.2 Popup . . . . . . . . . . . . . . . . . . . . . 90
7.3 Fixed popup. . . . . . . . . . . . . . . . . . . 91
7.4 Graphical popup . . . . . . . . . . . . . . . . . 91
7.5 Highlighting elements . . . . . . . . . . . . . . . 94
7.6 Dynamically adding labels and edges . . . . . . . . . 96
8 Applying Layout 99
8.1 Element-based Layouts . . . . . . . . . . . . . . . 101
8.2 Edge-driven layouts . . . . . . . . . . . . . . . . 106
8.3 Layout builder . . . . . . . . . . . . . . . . . . 113
8.4 Creating custom layout . . . . . . . . . . . . . . . 116
II Builders
III Applications
Introduction
computational unit. Our advice is to resist the natural tendency to map your
knowledge from Java or PHP into Pharo.
• Let us know about your project. We will make sure that adequate ad-
vertisement will be made. All projects, ranging from a student effort to
an industrial case study, are relevant.
• As any piece of software, Roassal contains bugs. Reporting bug is a
great and easy way to help.
• Fixing bugs and proposing improvements.
• Buying a physical copy of this book contributes to support the engi-
neering effort and to prepare the next edition of the book.
• It is difficult for us to imagine all the different ways people will use the
artifacts promoted in this book. Actually, it would be presumptuous to
think so. All feedback is welcome. Let us know what you wish to read
and what you wish to read more about.
• Making a simple "Hello" on one of the multiple communication chan-
nels.
Acknowledgements 5
1.9 Acknowledgements
The book is the result of multiple and long-lasting collaborations. First of all,
we would like to thank the Lam Research company. Lam Research’s team
has always been supportive both morally and financially. Thanks CH and
Chris! You made this book a reality.
Some of the chapters are external contributions. We therefore thank their
authors:
• Bui Thi Mai Anh, Nick Papoulias and Serge Stinckwich for the chapter
Expressing Epidemiological Models.
• Leonel Merino for the chapter Network Latency.
• Yuriy Tymchuk for the chapter on Domain-Specific Visualization Made
Easy.
• Onil Goubier and Thierry Goubier for the chapter OpenStreetMap Inte-
gration.
• Peter Uhnák for the chapter Applying Layout.
Many people within the Moose, Pharo and ESUG communities have
deeply contributed to what is presented in the book. Your enthusiastic sup-
port and trust in what we do has always been invaluable.
We also would like to thank you, yes you, the reader, for your questions,
support, bug fixes, contribution, and encouragement.
We are deeply grateful to for their contributions to (no particular or-
der) CH Huang, Chris Thorgrimsson, Tudor Gîrba, Renato Cerro, Stéphane
Ducasse, Yuriy Tymchuk, Natalia Tymchuk, Juraj Kubelka, Juan Pablo San-
doval Alcocer, Milton Mamani, Vanessa Peña, Ronie Saldago, Alvaro Jose
Peralta, Pablo Estefo, Igor Stasenko, Faviola Molina, Ricardo Jacas, Daniel
Aviv Notario, Sergio Maass, Serge Stinckwich, Bui Thi Mai Anh, Nick Pa-
poulias, Johan Fabry, Nicolai Hess, Miguel Campusano, Peter Uhnák, Mar-
tin Dias, Jan Blizničenko, Samir Saleh, Nicolai Hess, Leonel Merino, Volk-
ert, Pierre Chanson, Andrei Chis, Thomas Brodt, Mathieu Dehouck, Miguel
Campusano, Onil Goubier, Thierry Goubier, Esteban Maringolo, Alejandro
Infante, Philippe Back, Stefan Reichhart, Ronie Salgado, Steffen Märcker.
Part I
Roassal
Chapter 2
Quick Start
This chapter gives an overview of what Roassal is up to. Many short code
snippets are provided and briefly described. These snippets are directly
copy-and-pastable within Roassal and typically each illustrates one partic-
ular aspect of the Roassal tooling.
Note that you need administrator access grant the Cairo library installa-
tion. You will need to manually install Cairo if you are using Ubuntu, De-
bian, or a fork such as Mint.
Roassal on VisualWorks
Roassal is available on the public Cincom Store, under the bundle roassal2-
full. Note that you need to have Cairo installed from your VisualWorks dis-
tribution.
b edges connectFrom: [ :i | i // 2 ].
b layout cluster.
b
12 Quick Start
Press the Do it all and go button, represented with a green triangle, on the
top right of the Playground window. Alternatively, select the whole content
of the playground with Ctrl-A or Cmd-A and press Ctrl-G or Cmd-G. You
should obtain Figure 2.2.
Mondrian is a high level code library for building expressive and flexible
visualizations. After selecting the Mondrian library, the script selects the
label shape. Nodes are then created, numbered from 1 to 100. Edges are
built as follows: for each number i between 1 and 100, an edge is created
from the element representing i // 2 and i. The expression a // b returns the
quotient between a and b, e.g., 9 // 4 = 2 and 3 // 2 = 1. Nodes are then
ordered as a cluster layout. Each element is draggable and has a popup with
the mouse.
The variable path contains a location in your file system. You need to
change the path to execute the script. Please note that indicating a large
portion of the file system may significantly increase the computation time.
The expression path asFileReference converts a string indicating a path as a
file reference. FileReference is a Pharo class that represents a file reference,
typically locally stored, on the hard disk. The message allChildren gets all the
files recursively contained in the path. The visualization paints in red files
for which their name ends with .pdf.
Compared with the previous example, this visualization uses a normal-
izer to give a size to each circle according to the file size. The size varies
from 10 to 150 pixels, and uses a square root (sqrt) transformation to cope
Geographical CSV data 13
with disparate size. You may want to click on the camera center icon button
just above the visualization to scale it down.
As a small exercise, you can replace #sqrt by [:s | (s + 1) log * 2 ], for a
logarithm scale, or #yourself for a linear scale.
b := RTMapLocationBuilder new.
b shape circle
size: [ :m | 2 raisedTo: (m - 1) ];
color: (Color red alpha: 0.3).
tab values do: [ :row | b addPoint: row second @ row third value: row fifth ].
14 Quick Start
v := RTView new.
es := RTEllipse elementsOn: tab values.
v addAll: es.
es @ RTPopup.
RTMetricNormalizer new
elements: es;
normalizeColor: #fifth using: { Color orange . Color red };
alphaColor: 0.3;
normalizeX: #first min: 0 max: 600;
normalizeSize: #fifth min: 0 max: 80 using: [ :mag | 2 raisedTo: (mag - 1) ].
es @ (RTLabeled new text: [ :row | row fifth > 6 ifTrue: [ row fifth ] ifFalse: [ '' ] ]).
v @ RTDraggableView.
v
The first column is converted into Julian day numbers. This is useful for
ordering the earthquakes later on. The fifth column is then converted as a
float. A circle is associated with each entry of the table. The size of each
16 Quick Start
ellipse and its colors is deduced from the fifth column (earthquake magni-
tude). The first column, the Julian day number, is used to horizontally order
the ellipse. Each earthquake with a magnitude greater than 6 has a title.
The Julian Day Number (JDN) is the number of elapsed days since the
beginning of a 7,980-years cycle. This number is frequently used by as-
tronomer. The starting point for the first Julian cycle began on January 1,
4713 BC.
2.7 Charting
Roassal has a sophisticated mechanism to draw charts. Grapher takes as in-
put a set of data points and a configuration (which may be minimal) to ren-
der these data points.
Plotting
The following example illustrates Ebola outbreaks (Figure 2.6):
"Preparing the data"
tab := RTTabTable new input: 'http://bit.ly/EbolaCSV' asUrl retrieveContents
usingDelimiter: $,.
tab removeFirstRow.
tab replaceEmptyValuesWith: '0' inColumns: #(10 11).
tab convertColumnsAsInteger: #(10 11).
tab convertColumnsAsDateAndTime: #(3 4).
data := tab values reversed.
ds := RTData new.
ds interaction fixedPopupText: [ :row | row value at: 12 ].
ds dotShape ellipse
color: (Color blue alpha: 0.3);
size: [ :row | (row at: 11) / 5 ].
ds points: data.
ds connectColor: Color blue.
ds y: [ :r | r at: 10 ].
ds highlightIf: [ :row | (row at: 10) > 100 ] using: [ :row | row third year ].
b add: ds.
Figure 2.6 gives the output of the script. The Y-axis represents the number
of fatalities. The X-axis is the sequential events. Note that time is not scaled
in this chart, as the data are simply stacked from left to the right. The size of
an event gives the rate of fatalities. Locating the mouse above an event gives
some contextual information.
The CSV data is obtained from a short bit.ly link we have set for the pur-
pose of keeping the script short. The original data comes from the website
http://mapstory.org. MapStory is a website with short and illustrative stories.
Outbreaks are contained in the file in a reverse chronological order, which
is why we first reverse order it. Each point is a row in the table. The use of
fixedPopupText: opens a popup for each data point when the mouse is above
the data point. Column 12 of a row is a description of the outbreak.
Each data point is a circle. Its size reflects the value of the column fatality
rate (% of death per contamination). The column 10 indicates the number
of fatalities. This value is used on the Y-axis. Only major events are labeled
with the year (the third column corresponds to the year of the outbreak).
Double charting
Charter offers several visualization types. A double bar chart is an effective
visual representation for small datasets and with two metrics to be repre-
sented. Consider the following example that illustrates the distribution of
people in North American states (Figure 2.7).
tab := RTTabTable new input: 'http://bit.ly/CensusGov' asUrl retrieveContents
usingDelimiter: $,.
tab removeFirstRow.
tab convertColumnsAsInteger: #('POPESTIMATE2013' 'POPEST18PLUS2013').
18 Quick Start
b := RTDoubleBarBuilder new.
b pointName: [ :row | row at: (tab indexOfName: 'NAME') ].
"Remove the first line, the sum"
b points: tab values allButFirst.
b bottomValue: [ :row | ((row at: (tab indexOfName: 'POPESTIMATE2013')) /
1000) asInteger ]
titled: 'Pop estimate (x 1000)'.
b topValue: [ :row | ((row at: (tab indexOfName: 'POPEST18PLUS2013')) / 1000)
asInteger]
titled: 'Pop +18 estimate (x 1000)'.
b
Multiple graphs
Several graphs may be represented simultaneously. Consider the following
example (Figure 2.8):
Charting 19
b := RTGrapher new.
numberOfDataSets := 5.
2.8 Timeline
Timelines are essential when representing process executions. A Gantt chart
is commonly employed to represent project development and implementa-
tion. Consider the following script (Figure 2.9):
data := #(
#(WP1 0 4) #(WP2 4 8)
#(WP3 8 12) #(WP4 3 4)
#(WP4 7 9) #(WP4 10 12)
).
b := RTTimeline new.
s := RTTimelineSet new.
s objects: data.
s lineIdentifier: #first.
s start: #second.
s end: #third.
b add: s.
b axisX
noDecimal;
title: 'Month';
numberOfLabels: 12.
b
Objects that will be represented are given by the variable data. Each object
is represented as an array of 3 elements, for example #(WP1 0 4). The name
of an event is given by the first element of the array. The beginning of an
object is given by the second element of the array and the event end by the
third position. There is no need to have decimal values on the X-axis since
only integer values are considered here.
Integration with OpenStreetMap 21
After a basic initialization, data are plotted as a bar chart on top of the
tiles. Zooming in and out apply on both the map and the graph. The view
initially focuses on Paris, when opened.
Roassal supports a wide range of charts. Another example is (Figure
22 Quick Start
2.11):
v := RTView new.
v @ RTDraggableView.
map := RTOSM new.
v add: map element.
b add: d.
b axisY
labelFontHeight: 6;
Integration with OpenStreetMap 23
Pharo In a Nutshell
creates an empty string character, Color new creates a black color. The view,
produced by executing RTView new, is said to be the object (for instance) pro-
duced by RTView.
The expression object asString sends the message asString to an object, ref-
erenced by the variable object. In Pharo, a class is also an object, which means
that objects are created by sending a message to a class. The message new is
sent to the class RTView, which has the effect of creating an object. This object
is assigned to the variable v using the operator := .
In the second line, the message elementOn: is sent to the class RTLabel. An
argument is provided to that message, which is a string character ’Hello World’
as argument. These Roassal instructions simply creates an element that has a
shape label. That element is passed as argument to the message add:. The ef-
fect of add: is simply to add the element in the view. The view, referenced by
v, understands the message add: because the class RTView defines a method
add:.
Consider this script (Figure 3.2):
values := #(20 40 35 42 10 25).
v := RTView new.
elements := (RTEllipse new size: #yourself) elementsOn: values.
v addAll: elements.
RTHorizontalLineLayout on: elements.
elements @ RTLabeled.
v
This example renders 6 circles, each having a proper size. The expres-
sion #(20 40 35 42 10 25) defines an array, containing a few numbers. The
expression RTEllipse new size: #yourself creates an object of the class RTEllipse
Pillars of Object-oriented programming 27
by sending the message new. The message size: is sent to that ellipse ob-
ject, with the symbol #yourself as argument. This message size: configures
the size of the ellipses: The size of each circle is computed with the model
object when creating the element. In particular, the message #yourself will be
sent to each element of the array values. For example, the size of the circle
representing the value 35 has a diameter of 35 pixels. Circles are then lined
up using a dedicated layout, invoked by sending the message on: to it with
the elements as argument. The expression elements @ RTLabeled labels each
elements.
Most visualization engines and data analysis environments operate on
the principle illustrated above: scripts are typed in a workspace or a web-
page, and executed to produce a visualization. This approach to build a
visualization or a small application is appealing since it is self-contained:
all one needs to know is within the linear script and the expressed logic is
made explicit. However, this way of developing software artifacts has seri-
ous limitations. Maintenance and evolution are seriously diminished. For
example, a 200-line long script is painful to modify and confusing to look at.
If not properly structured, adapting a complex visualization may have the
fantastic ability to consume a ridiculously large amount of time. This is a sit-
uation well known to journalists, data scientists, and also software engineers.
Fortunately, a couple of decades ago the Software Engineering research com-
munity produced a way of programing that is able to cope with the inherent
complexity of software artifact development. Object-oriented programming
is the most successful way to handle complex and large development.
expression:
'the quick brown fox jumped over the lazy dog' replaceAllRegex: 'fox' with: 'cat'
This expression sends to the string object ’the quick brown fox jumped over
the lazy dog’ a message having the name #replaceAllRegex:with: and two argu-
ments, ’fox’ and ’cat’, themselves two string objects. The result of sending this
message is ’the quick brown cat jumped over the lazy dog’, another string.
In Pharo, a character string (often simply called a string) is a collection of
characters written between two accents (e.g., ’fox’). A string is a plain object,
which means one can send messages to it. A message is composed of two
essential ingredients: a name and a collection of arguments. It may be that
the set of arguments is empty. For example, the expression ’fox’ asUppercase,
which evaluates to ’FOX’, sends the message #asUppercase to the string ’fox’.
No arguments are involved here.
Message sending is at the heart of the Pharo language, and is therefore
well expressed within its syntax. There are three kinds of message sending:
• Unary message: a unary message is a message that does not take any
argument. The expression ’fox’ asUppercase sends a unary message to
the string ’fox’.
• Binary message: a binary message has exactly one argument and its
name is not made of alphanumerical characters. Instead one or two
characters are common for binary messages, such as +, /, -, <, >>. The
expression 2 + 3 sends to the object 2 a binary message named + with
the argument 3. You may notice that this expression has therefore a
different semantic than 3 + 2, although the result is obviously the same.
Note that the expression 3 + 2 * 2 returns 10, and not 7 as one may
expect. If you wish to enforce mathematical priorities in arithmetic
operations, use parenthesis, as in 3 + (2 * 2).
This expression sends three messages, twice the message new and once
the message ==, used to compare object identities. The expression evaluates
to false, since the two objects are different, i.e., located at different physical
memory location.
The expression Point new creates a point by sending the message new to
the class Point. There are several ways to create a point:
• Point new creates a point (0, 0). All classes in Pharo understand the
message new. Except when explicitly prohibited, an object is created
by sending new to the class.
• Point x: 5 y: 10 creates a point (5, 10). This expression sends the message
x:y: to the class Point, with 5 and 10 as arguments. The class Point defines
the class method x:y:. The difference between new and x:y: is that the
latter allows one to create and initialize a point with a given value for
x and y.
Each class has its way to create objects. For example, a point is not created
the same way as is a color. Creating an object is also commonly mentioned
as "instantiating a class" and an object is often referenced as "instance".
A class is an object factory and an object is necessarily created from a
class. An object associates values to the attributes defined by the class of
the object. As discussed above, objects interact by sending messages. An
object is able to understand messages corresponding to methods defined in
its class, and methods defined in the chain of superclasses.
Class creation 31
2. Right click on the top left list panel, and define a package called Tweet-
sAnalysis.
3. Create the class Tweet. Classes are created by filling the following tem-
plate in a code browser:
The text NameOfSubclass has to be replaced by the name of the class you
wish to create. After the keyword instanceVariableNames: you need to provide
the instance variables, and after classVariableNames: the class variables. Right
click on the code and select the menu accept to effectively create the class.
You should have
Object subclass: #Tweet
instanceVariableNames: 'content sender date'
classVariableNames: ''
package: 'TweetsAnalysis'
You should obtain something similar to Figure 3.3. We have defined the
class Tweet, contained in the package TweetsAnalysis. The class contains three
instance variables, content, sender, and date. No methods have been defined
so far. Note that in Pharo, an instance variable name begins with a minuscule
letter.
After having entered the code, right click on it and select Accept. Accept-
ing a method compiles it and makes it executable.
Open a playground, type and execute 10 fibonacci. You will see 55, its
result (Figure 3.5).
The self word refers to a pseudo-variable that designates the object hav-
ing received the message. When executing the expression 10 fibonacci, self
Method creation 33
refers to the object 10. The expression self <= 1 is true if self is either 1 or
smaller. If this is the case, then we exit the method with ifTrue: [ ↑ self ]. The
caret character (↑) is a return statement: it exits the method and returns a
value. If self is greater or equals 2, then the result is the sum of (self - 1)
fibonacci and (self - 2) fibonacci.
Another common pseudo-variable is super. The two pseudo-variables self
and super reference the same object, the object that has received a message.
The unique difference between self and super is characterized when when
one sends a message to it, in particular:
• sending a message to self triggers the method lookup from the class of
the object,
• sending a message to super triggers the method lookup from the super-
class of the class that contains the call on super.
Coming back to our Tweet example. Define the following six methods on
the class Tweet:
date
^ date
date: aDate
date := aDate
content
^ content
content: aContent
content := aContent
sender
^ sender
sender: aSender
sender := aSender
These methods will enable one to set the content of a tweet and query
about it.
Click on the Class button in the system browser. Clicking on it switches
the system browser to the class side: methods defined on that side are class
methods of the class Tweet. Define the method (Figure 3.6):
createFromURL: urlAsString
"Method to be defined on the CLASS side"
| content lines sender date |
content := (ZnEasy get: urlAsString) contents readStream.
Method creation 35
The method createFromURL: fetches a CSV file we have prepared for that
example. The file contains 1000 random tweets. It does a simple parsing of
the content by identifying the comma.
Next, you can define the method:
createFromExample
"Method to be defined on the CLASS side"
^ self createFromURL: 'http://bit.ly/exampleTweetCSV'
isSimilarTo: aTweet
^ (self words intersection: aTweet words) size >= 6
The method words simply returns all the words defining the content of
a tweet. It uses substrings which returns a list of words from a string. For
example, the expression ’fox and dog’ substrings return #(’fox’ ’and’ ’dog’). The
method isSimilarTo: takes as argument another tweet and returns true or false
whether the tweet argument is similar to the tweet that receives the message
isSimilarTo:. The notion of similarity we use here is: two tweets are similar if
they have at least 6 words in common.
36 Pharo In a Nutshell
So, we have some objects and a way to establish a relation between them.
This is more than enough to start to visualize them. Open a playground and
type (Figure 3.7):
tweets := Tweet createFromExample.
positive := tweets
select: [ :t | #('great' 'cool' 'super' 'fantastic' 'good' 'yes' 'okay' 'ok') includesAny:
t words ].
negative := tweets
select: [ :t | #('bad' 'worse' 'down' 'no') includesAny: t words ].
b := RTMondrian new.
b shape circle
if: [ :t | positive includes: t ] color: Color blue;
if: [ :t | negative includes: t ] color: Color red.
b interaction popupText: #content.
b nodes: positive, negative.
b edges connectToAll: [ :tweet |
tweets select: [ :t | t isSimilarTo: tweet ] ].
Block closure 37
b := [ :value | value + 5 ].
b value: 10. "Return 15"
b value: -5. "Return 0"
Recall the definition of the fibonacci method, defined on the class Integer:
fibonacci
self <= 1 ifTrue: [ ^ self ].
^ (self - 1) fibonacci + (self - 2) fibonacci
The message ifTrue: takes a block [ ↑ self ] as argument. In case that self
<= 1 evaluates to true, the block is evaluated and triggers an early exit of the
method. The expression ↑ self exits the method. The block uses the pseudo-
variable self. A block may access variables defined in the outer lexical scope.
A block may use temporary variables, instance variables, and argument vari-
ables.
3.9 Collection
A Collection is a very common data structure. As previously illustrated, the
expression #(23 42 51) defines an array, instance of the class Array. This class,
and its superclasses, defines a large number of methods. Two operations are
very common in Pharo: transformation and filtering of collections.
A transformation is typically realized using collect:. For example, #(23 42
51) collect: [ :v | v > 30 ] returns #(false true true). The initial array of numbers
is transformed as an array of booleans.
Filtering is carried out using select:. For example, #(23 42 51) select: [ :v | v
> 30 ] returns #(42 51).
Both collect: and select: takes a block as argument. In case of select:, the
block has to evaluate to a boolean.
Collections in Pharo are rooted into the Smalltalk programming lan-
guage, and is often an inspiration for other programming languages. Pharo’s
Cascade 39
collections are rich and expressive. We have just seen the example of Ar-
ray. Another collection is OrderedCollection representing an expandable collec-
tions. Elements may be added and removed during the program execution.
For example:
v := OrderedCollection new.
v add: 23.
v add: 42.
v add: 51.
elements := (RTBox new size: #yourself) elementsOn: v.
RTVerticalLineLayout on: elements.
RTView new
addAll: elements;
yourself
3.10 Cascade
The last bit of syntax is yet to be described. A cascade allows one to send
several messages to the same object receiver. For example, instead of writing:
v := OrderedCollection new.
v add: 23.
v add: 42.
v add: 51.
Pharo is a beautiful, elegant, and simple language. Pharo has a small and
concise syntax, which makes it each to learn. Its programming environment
is also highly customizable.
Building a sophisticated visualization or any non-trivial software artifact
often face complex development. Mastering object-orientation is not strictly
necessary in order to use Roassal. However, having a good command of
object-oriented programming will considerably alleviate development and
maintenance effort.
Pharo offers a powerful meta architecture. Do you remember that an ob-
ject is created by sending the message new to a class? In Pharo a class is
also an object since we send new to it, as in the expression Color new. A class
is therefore an object, itself an instance of another class, called a metaclass.
And it does not stop here. A metaclass is also an object. Methods are also
objects, a collection of bytecodes. Many parts of Pharo are truly beautiful,
but going into more detail is out of the scope of this book.
Chapter 4
c := TRCanvas new.
c addShape: (TRBoxShape new size: 40).
c
Execute the code by pressing the Do it all and go button (the same option
is accessible from right clicking on the whole selection, or pressing Cmd-G
or Ctrl-G). Figure 4.1 shows what you are currently seeing.
A canvas is simply created by instantiating the class TRCanvas. A shape
is added to the canvas by sending the message addShape: with a shape as
argument to a canvas. In the example given above, the TRBoxShape describes
a box which has a size of 40 pixels. Since we have not specified a color of a
shape, gray is used. Gray is the default color for most shapes.
A shape may be removed from a canvas by simply sending the remove
message to shape.
c := TRCanvas new.
shape := TRBoxShape new size: 40.
c addShape: shape.
shape when: TRMouseClick do: [ :event | event shape remove. c signalUpdate ].
c
On the example given above, the shape is removed from the canvas by
clicking on it. This example uses events, which will be described below. The
message signalUpdate is sent to the canvas to refresh the window. This mes-
sage has to be sent whenever the canvas is modified after being opened. The
message addShape: does not need to be followed by signalUpdate since the
canvas is opened after the script execution.
Adding shapes 43
• extent:, extent to set and query the dimension of the shape. The result
of extent is a point for which the x component represents the width and
the y the height.
Most of the methods listed above requires a point as argument. Each tra-
chel shape is described by a matrix underneath. This affine matrix contains
the position, scale factors and rotations values.
The color of a shape may be set using colors:. The border color using
strokePaint: and the border width using strokeWidth:.
100 timesRepeat: [
shape := TREllipseShape new.
shape
color: Color random;
size: 30 atRandom;
translateTo: (400 atRandom @ 400 atRandom).
c addShape: shape ].
The code given above creates a new canvas, and adds one hundred el-
lipses, each having a random size, color, and position. The variable button
holds a label. By sending the message setAsFixed to it, the label remains lo-
cated on the top left corner. Clicking on it slightly moves the camera. All
Scaling and rotating 45
the circles moves accordingly. The label button remains at the same position,
since it is fixed.
A callback refers to a piece of code that is trigged from an event. Such events
are typically mouse movements, mouse clicks or keyboard strokes. Call-
backs are registered using the message when:do:. The first argument is an
event class (subclass of TREvent). Several events classes are available to cover
common user actions. The example given above associates a callback to the
object button: the block provided as second argument is evaluated when click-
ing on the Trachel shape.
46 Painting with Trachel
Roassal maps objects and connections to graphical elements and edges. Val-
ues and metrics are mapped to visual dimensions, such as the width, height,
or the color intensity. Mapping objects to visual attributes is an expressive
way to build flexible and rich visualizations, and easily define this mapping
significantly reduce the effort to build a visualization. This chapter gives
an overview of Roassal and its main API. It covers the essential concepts of
Roassal, including the view, elements, shapes, and interactions.
when locating the mouse above, highlighting other elements, getting a menu
by right-clicking on an element are all common interactions.
The following example renders a tree, where each node is a number (Fig-
ure 5.1):
v := RTView new.
shape := RTBox new color: Color blue trans.
elements := shape elementsOn: (1 to: 50).
v addAll: elements.
elements @ RTPopup.
RTEdgeBuilder new
view: v;
objects: (1 to: 50);
connectFrom: [ :i | i // 3 ].
The first line creates a new view as an instance of the class RTView and as-
sign it to the variable v. A colored box shape is then created. The expression
Color blue trans defines a transparent blue color. The shape is then used as
a factory of Roassal elements using the message elementsOn:. This message
takes a collection of object model as argument, and returns a collection of
RTElement. Each element has a numerical value, between 1 and 50 as object
model. These elements are then added into the view v. The expression @
RTPopup makes each element react when the mouse is above it: a popup is
displayed that shows the model of the element, the number in our case.
The relations between the elements are then expressed using the class RT-
EdgeBuilder. An instance of RTEdgeBuilder is created and then parametrized
with the view. This edge builder retrieves particular elements from the view
and draws edges between two elements. The code specifies that for each
number i between 1 and 50, an edge is drawn between i and i // 3. The
message // refers to the integer quotient: 10 // 4 = 2.
Computing shapes dimensions 49
v := RTView new.
colorNormalizer := RTMultiLinearColorForIdentity new
objects: (tab values collect: #fourth) asSet sorted;
command: #fourth.
shape := RTBox new
width: #second;
height: #third;
color: colorNormalizer.
elements := shape elementsOn: tab values.
v addAll: elements.
RTEdgeBuilder new
view: v;
objects: tab values;
connectFrom: [ :entry | tab values at: entry fifth ].
RTHorizontalTreeLayout new
verticalGap: 30; on: elements.
v
The script above extracts and visualizes data from a TSV table (any
spreadsheet application generate documents in that format). In this exam-
ple, we assume that each row contains a numerical identifier, two values,
and the identifier of a parent table entry. The class RTTabTable converts the
textual description of the table (i.e., the raw file content) into actual values
that are usable by Roassal. The first row of the table is removed since it sim-
ply names the columns and these names are not meaningful for our example.
The four columns are then converted into integer numerical values.
Since each table entry contains two values, we define a graphical box for
which the width and the height are directly mapped to the first and second
value, respectively. A popup is then defined that gives more details about the
table entry. This is followed by labeling each element with the first numerical
entry value, the identifier.
Lastly, edges are built, linking each entry to its parent, and all elements
are laid out as a horizontal tree. Note that we assume here that (i) entries
are ordered in the table according to their identifier and (ii) the parent of the
first entry is the entry itself.
A graphical element acts as a visual facade for the object that the element
represents, i.e. models provided using elementOn: or elementsOn:. Roassal
transparently associates each element to this model object. Typically these
objects are externally provided. For example, in the example with the small
table given above, each Roassal element represents an entry from the table.
Each element has a particular size that is computed from the table entry.
Shapes 51
5.3 Shapes
In Roassal, several primitive shapes are offered, both for individual elements
and for edges.
Element shapes
Six shapes are meant to cover most typical usages:
• RTLabel: a textual label, e.g., RTLabel new text: ’hello world’. The text may
be multi-lined.
• RTSVGPath: an SVG path, e.g., RTSVGPath new path: ’M150 0 L75 200
L225 200 Z’
• RTArc: arc portion, e.g., RTArc new externalRadius: 100; innerRadius: 20;
outerAngle: 45 innerAngle: 90
For each of the examples above, filling in the ... in the following code
template produces an executable code snippet:
v := RTView new.
shape := ... .
v add: shape element.
v
• width: and height: set the width and the height, respectively
The complete set of messages that may be used with element shapes is
declared in the classes RTShape and RTAbstractElementShape. As already men-
tioned, these methods may accept numerical values, or block functions. In
case a block is provided, it has to return a numerical value when evaluated
against the object model.
Edge shapes
Edges are typically drawn between two elements. Similar to elements, an
edge may have a shape.
• RTLine: a straight line between its extremities, e.g., RTLine new color:
Color blue
shape := ... .
Shapes 53
Shape composition
In some cases, primitive graphical elements are simply not enough. One
tipical scenario is labeling, often achieved by combining a label and a graph-
ical element. Roassal offers a simple and expressive mechanism to compose
shapes in order to produce more elaborated ones (such as a labeled rectangle)
Consider the following example (Figure 5.3):
v := RTView new.
The script above creates a composite shape made of two shapes: a box
and a label. The use of the vertical message sent to this composite shape
makes the first subshape (i.e., the box) above the second subshape (i.e., the
label).
Composed shapes are often employed to give a title to elements. The
following example visualizes some of the classes defined in Roassal (Figure
5.5):
v := RTView new.
shape := RTCompositeShape new.
shape add: (RTLabel new color: Color gray).
shape add: (RTBox new
color: Color lightRed;
width: [ :cls | cls numberOfVariables * 8 ];
Shapes 55
height: #numberOfMethods).
shape vertical.
es := shape elementsOn: RTShape withAllSubclasses.
v addAll: es.
RTGridLayout on: es.
v
v addAll: es.
es @ RTLabeled.
RTGridLayout on: es.
v
Figure 5.7: Giving a title to some boxes using RTLabeled and highlight.
v addAll: es.
es @ (RTLabeled new setAsHighlightable) .
RTGridLayout on: es.
v
e @ RTDraggable.
e @ RTPopup.
view open
Clicking on the element resizes the second and third inner shapes. Since
the automatic relayout has been set using #setAutomaticRelayout, shapes will
be properly ordered.
Shapes may also be translated within a composite shape. Consider the
following example that paints four national European flags (Figure 5.8):
The message on:nest: takes as the first argument an element, onto which
you wish to nest a group of elements provided as second argument. The nest-
ing element (i.e., the first argument provided to on:nest:, el in our example)
is resized to encapsulate the nested elements. Dragging an encapsulating
element also drag its child elements.
The precedent example first sets the layout for the inner elements, then
nests the elements (RTGridLayout on: inner. RTNest new on: el nest: inner). A
layout may be specified when using RTNest. This may be written as follows:
RTNest new
layout: RTGridLayout new;
on: el nest: innerElements.
The message for:add: is useful for iterating over a group of elements and
defines nested elements for each element of a set. Consider the example
(Figure 5.11):
v := RTView new.
60 Visualizing with Roassal
Figure 5.12 illustrates the usage of for:add: in which edges are added in
the group.
v := RTView new.
v @ RTDraggableView.
v := RTView new.
e := (RTEllipse new size: 30) elementOn: 42.
v add: e.
e @ (RTLabeled new text: [ :value | 'My value is ', value asString ]).
v
v := RTView new.
e @ RTResizable.
v add: e.
v
box2 translateBy: 50 @ 0.
box2 @ RTDraggable.
box1 @ RTDraggable.
box1 @ (RTEventForwarder new toObject: box2).
64 Visualizing with Roassal
v add: box1.
v add: box2.
v
Dragging the left box forward the dragging events to the right box. An-
other example of RTEventForwarder is in case elements are above other ele-
ments. Consider the following example:
v := RTView new.
inner := (RTBox new color: Color green; size: 30) elementOn: 'world'.
outer := (RTBox new color: Color blue) elementOn: 'hello'.
The code above creates a composite shape with two shapes, a label and a
box. Elements are then built from the composite shape.
Using the RTLabeled interaction, a similar code may be (Figure 5.15):
v := RTView new.
• The label added with RTLabeled is not taken into account when com-
puting the encompassing boundary of the element. As a result, the
layout does not take the label into account, which may result in over-
lapping labels, as Figure 5.15 illustrates. There is no general rule on
whether the label has to be taken into account when layouting. It all
depends on the purpose of the visualization.
66 Visualizing with Roassal
5.9 Normalization
Being able to quickly compare data elements is essential in a visualization.
Pre-attentive processing refers to the ability of the human brain to uncon-
sciously accumulate information from the surroundings. Without even re-
alizing what is in front of our eyes, the brain filters and processes what is
important. This phenomena is particular relevant in data visualization. Our
brain is able to immediately spot an element that is different shaped or col-
ored.
The class RTMetricNormalizer normalizes one or more visual dimensions
of a set of Roassal elements. Consider the following example (Figure 5.16):
v := RTView new.
es := (RTEllipse new color: Color blue) elementsOn: #(4 5 1 2 3 5).
v addAll: es.
es @ RTPopup.
RTMetricNormalizer new
elements: es;
alphaColor;
normalizeSize: #yourself.
es @ RTLabeled.
v
Normalization 67
The script above creates six elements, each representing one of the values
in #(4 5 1 2 3 5). Elements are then added to the view and a popup interaction
is given to each element.
The class RTMetricNormalizer is then instantiated. Elements to be normal-
ized are set in the metric normalizer using elements:. Elements are first made
translucent (note that strictly speaking the message alphaColor is not a nor-
malization, but rather a convenience). The message normalizeSize: normal-
izes the size of each element against the set of elements provided with ele-
ments:. The default minimum size is 5 pixels and the maximum is 30 pixels.
Such values may be specified using normalizeSize:min:max:. For example:
v := RTView new.
es := (RTEllipse new color: Color blue) elementsOn: #(4 5 1 2 3 5).
v addAll: es.
es @ RTPopup.
RTMetricNormalizer new
elements: es;
alphaColor;
normalizeSize: #yourself min: 20 max: 50;
normalizeColor: #yourself.
es @ RTLabeled.
v
The example given above normalizes both the size and the color of the
elements. The color palette is continuous, ranging from gray to red. Colors
68 Visualizing with Roassal
es @ RTPopup.
RTMetricNormalizer new
elements: es;
alphaColor: 0.3;
normalizeX: #yourself min: 0 max: 30.
es @ RTLabeled.
es @ RTPopup.
RTMetricNormalizer new
Normalization 69
elements: es;
alphaColor: 0.3;
normalizeHeight: #yourself min: 0 max: 80.
es @ RTLabeled.
You can notice this histogram is not quite right. The bar for the element
1 is not visible. This is because it has a height of 0. This is expected since
the minimum value of the elements model is 0 pixel (i.e., the value provided
to min:). A proper version of our (primitive) bar charting needs to specify a
70 Visualizing with Roassal
es @ RTPopup.
RTMetricNormalizer new
elements: es;
alphaColor: 0.3;
normalizeHeight: #yourself min: 0 max: 80 minValue: 0 maxValue: 5 .
es @ RTLabeled.
distinctColorUsing: #package.
RTHorizontalFlowLayout on: es.
v
The script above creates a boxed element for each subclasses of the class
Collection. These elements are then added in a view. The normalizer com-
putes the height using the number of methods, the width using the number
of variables, and gives a color to each class based on the package the class
belongs to.
Alignment
Adequately positioning some elements against other elements is often cru-
cial. It frequently happens that an element has to be resized or positioned
against other elements or even the window. The class RTAlignment offers a
number of methods dedicated to constrain the size or the position of some
elements.
Consider the following example (Figure 5.22):
values := #(35 26 5 18 17 60 36 13 16 28 29 3 8 0 3 2 14
12 13 17 1 9 3 3 4 1 1 1 2 1 1 61).
v := RTView new.
n := RTMultiLinearColorForIdentity new objects: values.
72 Visualizing with Roassal
Figure 5.22 illustrates the different ways to line up elements using the
class RTAlignment. Elements can be lined up with their lowest point (bottom),
with the left-most position (left), and with the right-most position using right.
You may want to replace RTHorizontalLineLayout by RTVerticalLineLayout to try
left and right in the script given above.
Alignment may also be defined using a particular element. You simply
need to set that element using fixedElement:. You can then use the messages
top, left, right, bottom.
The class RTGroup is polymorphic to RTElement, which means that you
can provide a group of elements where an element is expected. Consider the
following example (Figure 5.23):
rootClasses := { RTShape . RTLayout }.
view: g;
objects: cls withAllSubclasses ;
connectFrom: #superclass.
RTTreeLayout on: elements edges: edges.
g ].
v := RTView new.
groups do: [ :aGroup | v addAll: aGroup ].
RTHorizontalLineLayout new gapSize: 30; on: groups.
Positioning
The class RTConstraint offers a number of methods to position elements
against other elements. A constraint has to be properly initialized using fix:
to set those that are fixed elements and movable: to set the elements that have
to be moved and/or constrained.
v := RTView new.
RTConstraint new
74 Visualizing with Roassal
movable: l;
fix: e;
inner;
bottom;
move.
v
The code above simply moves the label at the bottom of the square, while
being in the inner side. You may experiment by replacing:
• move by stick to make the label stick to the square when dragged and
dropped.
The move operation moves the movable element according to the fixed
element. Using the mouse to drag the box does not move the label. To do so,
you need to use the stick operation.
The example given above uses a single element for fix: and movable:.
Groups may be provided instead to handle a set of elements. Consider the
following example (Figure 5.24):
v := RTView new.
n := RTMultiLinearColorForIdentity new
numberOfColors: 20;
colors: { Color red . Color gray }.
es1 := (RTEllipse new color: n; size: 15) elementsOn: (1 to: 20).
v addAll: es1.
n := RTMultiLinearColorForIdentity new
numberOfColors: 20;
colors: { Color yellow . Color purple }.
es2 := (RTEllipse new color: n; size: 15) elementsOn: (1 to: 20).
v addAll: es2.
Two groups of elements are used in this example, es1 and es2. A force
based layout is applied to the first group while a grid layout is applied to
the second group. The use of RTConstraint positions the first group above the
second group.
Shape and Edge builder 75
Element shapes
Consider the following example (Figure 5.25):
v := RTView new.
sb := RTShapeBuilder new box
height: #numberOfMethods;
width: [ :cls | cls numberOfVariables * 4 ];
withTextAbove.
es := sb elementsOn: RTShape withAllSubclasses.
v addAll: es.
RTFlowLayout on: es.
v
Edge shapes
The edge builder create connections between elements and it is an important
asset for Roassal. The expressiveness of the Edge Builder is based on a semi-
declarative relation. Consider the following example (Figure 5.26):
v := RTView new.
shape := (RTEllipse new size: 20; color: Color red trans) + RTLabel.
es := shape elementsOn: (1 to: 20).
v addAll: es.
eb := RTEdgeBuilder new.
eb view: v.
eb shape orthoVerticalLine.
eb
objects: (1 to: 20);
connectFrom: [ :n | n // 3 ].
edge builder is obtained by sending the message #shape to the builder. In the
example above, we simply select orthogonal vertical lines using the message
#orthoVerticalLine.
Some objects may be specified to the edge builder. These objects define
the scope of how to build edges. The message #objects: sets the objects from
which the edges are starting and ending. In the example above, an edge is
built from each object ranging from 1 to 20.
The message #connectFrom: takes a bloc as parameter. In the example the
bloc is [ :n | n // 3 ]. The bloc accepts one argument and is evaluated for each
object. Evaluating the bloc returns a new number to which the edges will be
connected.
The class RTEdgeBuilder contains numerous methods that offers a great
flexibility on building edges. We advice you to browse the class to have the
complete list of methods. Methods that are common used are:
• connectFrom: builds an edge from the result of the bloc to the current
object.
Updating edges
As soon as an element is moved (using translateBy: or translateTo:), edges
connected to it are updated. Consider the following example (Figure 5.27):
v := RTView new.
es := RTBox elementsOn: (1 to: 300).
RTGridLayout new gapSize: 30; on: es.
v addAll: edges.
All the arrowed edges points towards the mouse cursor. The variable
edges is initialized with an empty collection. This collection is filled with
edges obtained from an arrowed line. Note that the elements contained in
es and e are not added in the view; only the edges are.
A callback is set to the view when the mouse cursor is moved: the to
extremity of the edge is translated accordingly. The message positionFrom-
Camera is used to get the mouse cursor position in the space coordinate.
Dynamically update 79
Elements may be easily added and removed. Consider the following exam-
ple (Figure 5.28):
v := RTView new.
v @ RTDraggableView.
The Trachel canvas offers facilities to add menu to the canvas. We use
the message addMenu:callback: to let the user to add nodes and edges. Most
of the script given above deals with the dynamic spring layout: adding an
element or an edge needs to update the layout.
80 Visualizing with Roassal
Coloring
6.1 Palettes
v := RTView new.
es := (RTBox new color: [ :index | RTPalette c3 at: index ]) elementsOn: (1 to: 5).
v addAll: es.
RTHorizontalLineLayout new gapSize: 1; on: es.
v
v addAll: elements.
RTGridLayout on: elements.
v
The code given above first creates a view. It then creates an instance of
RTMultiLinearColor. This normalizer is polymorph to the class Color, which
means you can provide a color normalizer where a color or a block is usu-
ally expected. The example simply uses the normalizer as a color by being
provided to color:.
Multi-linear color normalizer 83
v addAll: elements.
RTGridLayout on: elements.
v
The example above uses a palette made of two colors, red and gray. These
two colors are then interpolated to give a value ranging from 0.0 and 1.0. The
value 0.0 is mapped to red, and 1.0 to gray.
In the examples given above each element has a numerical value as object
model. It does not have to be so. Consider the following example (Figure
6.4):
classes := Collection withAllSubclasses.
maximumNumberOfLinesOfCode := (classes collect: #numberOfLinesOfCode)
max.
v := RTView new.
n := RTMultiLinearColor new.
n colors: RTPalette c4.
n command: [ :cls | cls numberOfLinesOfCode / maximumNumberOfLinesOfCode
].
v addAll: elements.
RTGridLayout new gapSize: 1; on: elements.
v := RTView new.
n := RTMultiLinearColor new.
n colors: RTPalette c4.
n command: [ :cls | cls numberOfLinesOfCode / maximumNumberOfLinesOfCode
].
v addAll: elements.
eb := RTEdgeBuilder new.
eb view: v.
eb moveBehind.
eb connectFrom: #superclass.
v := RTView new.
v addAll: elements.
RTMetricNormalizer new
elements: elements;
normalizeColor: #numberOfLinesOfCode using: RTPalette c4.
eb := RTEdgeBuilder new.
eb view: v.
eb moveBehind.
eb connectFrom: #superclass.
Interactive Visualization
Roassal elements (and Trachel shapes) may react to user events such as
mouse clicking, mouse moving and key pressing. Making a visualization
interactive is important to enable the following: navigate within a data
set, drill-down/up, details on demand without overloading a visualization.
Roassal offers a number of interaction, implemented as a subclass of RTInter-
action.
RTMetricNormalizer new
elements: es;
normalizeColor: #yourself;
normalizeSize: #yourself.
Interactions may be added at anytime to the element and the view. The
instruction es @ RTDraggable may be located before or after adding the ele-
ments in the view.
7.2 Popup
A popup is a contextual information that appears when the mouse is above
an element. The information is removed when the mouse leaves the ele-
ment. Adding the expression es @ RTPopup makes the element react to
mouse movements.
Without being configured, as it is the case with the expression es @ RT-
Popup, the popup content is directly generated from the model behind the
pointed element. For example:
v := RTView new.
es := RTEllipse elementsOn: (1 to: 100).
es @ RTPopup.
v addAll: es.
RTGridLayout on: es.
v
v := RTView new.
es := RTEllipse elementsOn: (1 to: 100).
popup := RTPopup new.
popup alphaPopupBackground: 0.5.
popup backgroundColor: Color blue.
popup borderColor: Color red.
popup textColor: Color orange.
popup text: [ :object | 'Value ', object asString ].
es @ popup.
v addAll: es.
RTGridLayout on: es.
v
A popup supports multi-lined text. For example, you may insert a car-
riage return character in the text: instruction:
...
popup text: [ :object | 'Value = ', String cr, object asString ].
...
The message group: takes as argument a block with two arguments. The
first argument, called aGroup in our example, receives an empty group of
elements. The block is intended to add elements in the aGroup variables.
Those elements will be used by the popup. Layout may be applied.
Edges may be also added. Consider (Figure 7.3):
v := RTView new.
v addAll: es.
Graphical popup 93
es @ (RTPopup new
background: Color blue
group: [ :group :el |
| elements |
elements := RTEllipse elementsOn: (1 to: el model).
group addAll: elements.
RTEdgeBuilder new
view: group;
elements: elements;
connectFrom: [ :aNumber | aNumber // 2 ].
RTClusterLayout new
verticalGap: 0;
on: elements.
]).
The edge builder is set with group as the view. This is where the
edge builder will look into elements to link. Methods alternative to back-
ground:group: may be used, such as group: if the background is not necessary.
A title is added to the popup by using named:background:group: as in:
94 Interactive Visualization
...
es @ (RTPopup new
named: [ :aNumber | 'Cluster for ', aNumber asString ]
background: Color blue
group: [ :group :el |
...
]
...
es @ RTHighlightable.
v
When the mouse enters an element, the color of that element changes.
When the mouse leaves, the original color is restored. The default highlight-
ing color is blue. It can be set using the color: message. For example, the
following instruction es @ (RTHighlightable new color: Color pink) set the color
to pink.
A set of object models or Roassal elements may be provided using high-
light: or highlightElements:. Consider the second version of the previous script:
v := RTView new.
es := RTEllipse elementsOn: (20 to: 70 by: 5) asArray shuffle.
v addAll: es.
es @ RTPopup.
RTMetricNormalizer new
elements: es;
normalizeSize: #yourself;
normalizeColor: #yourself.
Highlighting elements 95
Object models greater than the one on which the mouse is above are high-
lighted.
v := RTView new.
es := RTEllipse elementsOn: (20 to: 70 by: 5) asArray shuffle.
v addAll: es.
es @ RTPopup.
RTMetricNormalizer new
elements: es;
normalizeSize: #yourself min: 5 max: 30;
normalizeColor: #yourself.
RTFlowLayout on: es.
RTNest new
for: es add: [ :group :model |
group addAll: (RTBox new elementsOn: model methods).
RTGridLayout on: group ].
es @ (RTShowEdge new
connectTo: #dependentClasses;
shape: (RTLine new color: Color red);
yourself).
es @ (RTShowLabel new
color: Color red;
highlight: #dependentClasses; top; yourself).
RTGridLayout on: es.
v
Applying Layout
Note:
This chapter was written with the participation of
Peter Uhnák ([email protected]).
RTEdgeBuilder new
view: view;
elements: es;
connectFrom: [ :value | value // 2 ].
The script builds some elements and edges that link these elements. Each
element is translated to a random location. Applying the RTTreeLayout layout
highlights the structure from the connection between the elements (Figure
8.2):
view := RTView new.
es := (RTEllipse new size: 12; color: Color blue)
100 Applying Layout
RTEdgeBuilder new
view: view;
elements: es;
connectFrom: [ :value | value // 2 ].
The layout reveals that the elements form a binary tree and one can dis-
tinguish what the top root node is and what the leaves are.
Roassal offers over 30 different layouts, each useful in its own rights.
Element-based Layouts 101
Circle Layouts
Circle layouts arrange elements along a geometrical circle. The order of the
elements, as with all circle-based layouts, is the same as the collection on
which the layout operates.
v := RTView new.
RTMetricNormalizer new
elements: es;
normalizeColor: #yourself using: (Array with: Color red with: Color lightRed).
RTEdgeBuilder new
view: v;
elements: es;
connectFrom: [ :value | value // 2 ].
Without any option, the circle layout distributes all the elements evenly
along a circle (2pi/elements size). Additionally radius can be either set abso-
lutely via initialRadius:, or as a scalable factor scaleBy: - then the radius will
be elements size * scaleFactor.
It is important to note that RTCircleLayout does not take into considera-
tion the size of the elements; this is enough when the elements are uniform,
however if their sizes vary, different layouts may be considered.
102 Applying Layout
Variants of the default circle layout, named equidistant and weighted lay-
outs, are also available:
Figure 8.5: Equidistant (left) and Weighted (right) layout with non-uniform
sizes.
size of the elements. Thus there will be less space between smaller
elements, and more space between large ones.
v := RTView new.
elements := (RTEllipse new color: Color red; size: [:vv | vv * 4 ])
elementsOn: (1 to: 10).
v addAll: elements.
RTEquidistantCircleLayout on: elements.
elements translateBy: -150 @ 0.
v add: ((RTLabel new elementOn: 'Equidistant Layout') translateTo: -40 @ 100).
Flow Layouts
The flow layout arranges elements in lines, each line flowing from left to
right; Horizontal flow on the other hand is in columns, flowing from top to
bottom.
v := RTView new.
RTNest new
for: es
inShape: #second
add: [ :group :layout |
group addAll: ((RTBox new size: [:m | m * 10])
elementsOn: (1 to: 6)).
layout new on: group.
].
Alignment
Line Layouts
Tree Layout
The beginning of this chapter gives an example of using the tree layout.
Note that in the picture above the horizontalGap is applied only to the
leaves of the tree; distance between parents is then accommodated automat-
ically, so no overlapping or crossing occurs. The tree Layout orients the tree
vertically, while the RTHorizontalTreeLayout uses an original orientation, from
left to right.
elsBuilder := [ | es |
es := (RTEllipse new size: 12) elementsOn: (1 to: 40).
v addAll: es.
RTEdgeBuilder new
view: v;
elements: es;
connectFrom: [ :value | value // 2 ].
RTMetricNormalizer new
elements: es;
normalizeColor: #yourself using: (Array with: Color red with: Color green).
es
].
g := RTGroup with: elsBuilder value with: elsBuilder value with: elsBuilder value.
eb := RTEdgeBuilder new.
eb view: v; objects: classes.
eb shape arrowedLine; color: Color red.
eb
connectFrom: #yourself toAll: #dependentClasses.
RTDominanceTreeLayout new
verticalGap: 30;
horizontalGap: 15;
on: es.
v
Cluster Layout
Cluster is visually similar to radial tree; it groups related elements together
(Figure 8.12:.
view := RTView new.
view @ RTDraggableView @ RTZoomableView.
colorize := nil.
colorize := [ :root :color |
root outgoingEdges do: [ :edge |
edge shape color: color.
edge signalUpdate.
colorize value: edge to value: color.
].
].
The difference between the cluster and radial layouts has to do with the
layers forming circles.
110 Applying Layout
Sugiyama
Sugiyama layout is a hierarchical layered layout. It places elements in hi-
erarchical order such that edges goes from higher layer to lower layer. At
the same time the layout minimizes the amount of layers and edge crossings
(Figure 8.13).
classes := RTLayout withAllSubclasses.
b := RTMondrian new.
b shape ellipse color: Color purple; size: 5.
b nodes: classes.
b edges connectFrom: #superclass.
b layout sugiyama.
b
RTEdgeBuilder new
elements: es;
view: v;
moveBehind;
connectToAll: #dependentClasses.
RTMetricNormalizer new
elements: es;
normalizeColor: #numberOfMethods;
normalizeSize: #numberOfMethods.
v
The force based layout is appealing due to its simplicity of use and nice
results that it produces. However, it badly scales. Performing a force based
layout on hundreds of nodes and edges may take hours (literally). Before
performing this layout on a large graph, one may want to try it on a reduced
graph.
112 Applying Layout
Rectangle Pack
v := RTView new.
v addAll: ((RTEllipse new color: (Color red alpha: 0.3)) elementsOn: Collection
withAllSubclasses) @ RTPopup.
RTMetricNormalizer new
elements: v elements;
normalizeSize: #numberOfLinesOfCode min: 10 max: 60;
normalizeColor: #numberOfMethods using: (Array with: Color gray with: Color
red ).
RTRectanglePackLayout on: v elements.
v
b := RTNameCloud new
addString: 'open
| v shape |
v := RTView new.
shape := RTLabel new height: [ :assoc | assoc value ]; text: #key.
v addAll: (shape elementsOn: table associations).
RTFlowLayout on: v elements.
v open'.
b
Layout builder 113
b layout
force;
ifNotConnectedThen: RTGridLayout new.
b
Using the composition of layout and the layout builder (Figure 8.19):
classes := RTLayout withAllSubclasses, RTBuilder withAllSubclasses, RTShape
withAllSubclasses.
Layout builder 115
b := RTMondrian new.
b shape circle size: 5.
b nodes: classes.
b edges connectFrom: #superclass.
b normalizer
objects: classes;
normalizeSize: #numberOfMethods min: 5 max: 30;
normalizeColor: #numberOfLinesOfCode using: (Array with: Color green with:
Color red ) using: #sqrt.
b layout
for: [ :c | c includesBehavior: RTLayout ] use: RTForceBasedLayout new;
for: [ :c | c includesBehavior: RTBuilder ] use: RTForceBasedLayout new;
for: [ :c | c includesBehavior: RTShape ] use: RTForceBasedLayout new;
flow.
b
• sugiyama for the Sugiyama layout, which is a vertical tree layout that
minimizes edges crossing
9.1 Operations
A camera is accessible from the canvas and is described by the class TRCam-
era. This class defines the following methods:
9.2 Initialization
When a view is opened, the camera is positioned at the center of it. This is
the defaut behavior of a camera. This behavior may be changed in case you
need to position the camera at a different location. Consider the following
example:
118 Using the Camera
v := RTView new.
map := RTOSM new.
e := map element.
v add: e.
v @ RTDraggableView.
This short script use the OpenStreeMap integration to move the camera
above Paris. Coordinate of Paris are here specified in latitude and longitude.
The call to latLonToRoassal: converts the point argument, specified as latitude
@ longitude, into the Roassal space. The camera is then moved to the com-
puted point.
Part II
Builders
Chapter 10
Domain-Specific
Visualization Made Easy
with Builders
As seen in the chapter about the Pharo language, the class RTPunchcard
contains two instance variables, called metrics and objects. These instance
variables have to be initialized. Defining an initialize method in RTPunchChard
is therefore our next step:
initialize
super initialize.
objects := OrderedCollection new.
metrics := OrderedCollection new.
All the methods provided in this chapter are defined in the class we have
just defined. An end-user defines a visualization by specifying objects and
metrics. Two methods have to be provided for that purpose:
addMetric: blockOrSymbol named: aName
metrics add: aName -> blockOrSymbol
addObject: anObject
objects add: anObject
The method renderIn: is key in the builder framework. This is where the
visualization is constructed, i.e., Roassal elements are created and the view
is filled. The method renderIn: is intended to be overridden when creating
a new builder class. Here is a first version of the method renderIn: which
displays a list of objects, and computes the metrics for each object:
renderIn: aView
aView add: (RTLabel elementsOn: ' ').
objects
do: [ :object | aView add: (RTLabel elementOn: object) ].
RTCellLayout new
lineItemsCount: objects size + 1;
on: aView elements
Line 1 inserts an empty label, used for the first column. Line 3 - 4 inserts a
label for each object. Line 6 - 11 inserts the name of each metric and compute
the metrics against the objects. Lines 13 - 15 layout the elements as a grid.
At that stage, the builder may be invoked as:
124 Domain-Specific Visualization Made Easy with Builders
b := RTPunchcard new.
b addObject: RTPunchcard.
b addObject: RTBox.
b addObject: RTBuilder.
b addMetric: #numberOfMethods named: 'NOM'.
b addMetric: #numberOfVariables named: 'NOA'.
b
The objects used in the visualization are three classes: RTPunchcard, RT-
Box, RTBuilder. For each of these classes, the messages numberOfMethods and
numberOfVariables will be sent to return a numerical value.
The produced rendering is given in Figure 10.2. At that stage, the visu-
alization is very simplistic: circles may be excessively large if metric values
are high and the exact value behind each circle is not accessible. We address
this in the subsequent sections.
The method renderIn: directly uses RTEllipse, at Line 11. Having this use
of the shape prevents one from changing the shape. Instead, this method can
be rewritten:
renderIn: aView
aView add: (RTLabel elementsOn: ' ').
objects
do: [ :object | aView add: (RTLabel elementOn: object) ].
RTCellLayout new
lineItemsCount: objects size + 1;
on: aView elements
Circles have been replaced with boxes. Each box reflects the square root
of a metric value and the minimum size is set to 5 pixels. The visualization is
still limited. One cannot compare metrics across the different classes. Values
have to be normalized for this.
The builder keeps track of the objects that are created thanks to the call
self elementOn: value. Having the list of created objects is useful to configure
the normalizer. In this case, we consider a line as the scope of the normaliza-
tion.
A more elaborated example is:
b := RTPunchcard new.
b normalizer
normalizeSize;
normalizeColorUsing: { Color green . Color red }.
b addObject: RTPunchcard.
b addObject: RTPunchcard.
Specifying interaction using RTInteractionBuilder 127
b addObject: RTBox.
b addObject: RTBuilder.
b addMetric: #numberOfMethods named: 'NOM'.
b addMetric: #numberOfVariables named: 'NOA'.
b addMetric: #numberOfLinesOfCode named: 'LOC'.
b
Result of the example is given in Figure 10.4. Size and color reflect the
metric value, which enable an easy comparison.
renderIn: aView
| el |
aView add: (RTLabel elementsOn: ' ').
objects do: [ :object |
| objElement |
objElement := RTLabel elementOn: object.
self setUpInteractionFor: objElement. "New line"
aView add: objElement ].
b normalizer
normalizeSize;
normalizeColorUsing: { Color green . Color red }.
b addObject: RTPunchcard.
b addObject: RTBox.
b addObject: RTBuilder.
b addMetric: #numberOfMethods named: 'NOM'.
b addMetric: #numberOfVariables named: 'NOA'.
b addMetric: #numberOfLinesOfCode named: 'LOC'.
b
Right-clicking on the class name will open the menu to open a system
browser on that class (i.e., the result of sending browse to a class).
Chapter 11
Visualizing Polymetric
Graphs using Mondrian
Mondrian is a rich API which offers facilities to describe and render graphs.
Mondrian is an essential piece of Roassal due to its expressiveness and sim-
plicity of use. For any data structure, Mondrian allows mapping of metric
values and properties into visual dimensions, such as shape and colors.
Mondrian is often considered as being the most expressive part of Roas-
sal: highly interactive and flexible visualizations may be the result of just a
few lines of code. Mondrian is often the starting point of the Roassal plat-
form.
An online video illustrates this chapter: http://bit.ly/
Mondrian-AgileVisualization
Six gray squares are horizontally lined up. Each square corresponds to a
word contained in the array passed to nodes:. Several layouts are available,
thanks to the layout builder (see the description of the layout builder in the
Layout chapter). For example, the force layout is activated as:
b := RTMondrian new.
b nodes: #('hello' 'world' 'bonjour' 'tout le monde' 'Guten' 'Morgen').
130 Visualizing Polymetric Graphs using Mondrian
b layout force.
b
Edges may be simply added using the edge builder, accessible by sending
edges to the Mondrian builder:
b := RTMondrian new.
b nodes: #('hello' 'world' 'bonjour' 'tout le monde' 'Guten' 'Morgen').
b shape line.
b edges useAssociations: {
'hello' -> 'world' .
'bonjour' -> 'tout le monde' .
'Guten' -> 'Morgen' }.
b layout force.
b
A label may be used instead of a grayed box and the layout may be con-
figured as follows (Figure 11.2):
b := RTMondrian new.
b shape label.
b nodes: #('hello' 'world' 'bonjour' 'tout le monde' 'Guten' 'Morgen').
b shape line.
b edges useAssociations: {
'hello' -> 'world' .
'bonjour' -> 'tout le monde' .
'Guten' -> 'Morgen' }.
b layout force charge: -200.
b
b shape
bezierLineFollowing: [ :value | value // 2 ];
color: Color blue trans.
b edges
A first visualization 131
notUseInLayout;
connectTo: [ :value | (value / 10) asInteger + (value \\ 10) ].
b layout cluster.
b
The example creates an instance of the class RTMondrian and assigns this
object to the b variable. Nodes are then set in the builder, which are 300
successive numbers, starting from 1. Each node is represented as a little gray
square. Two sets of edges are defined. The first set, described using b edges
connectFrom: [ :value | value // 2 ], models the relation between a value and
value // 2. The message // returns "the integer quotient defined by division
with truncation toward negative" as indicated by the comment of the method
// defined in the class Number (e.g., 9//4 = 2).
The second set of edges, described using b edges notUseInLayout; con-
nectTo: [ ... ], is defined as the sum of the two digits. For a given numerical
value, the expression (value / 10) asInteger returns the first digit or it and value
\ 10 the last digit. Each number is linked to another number representing
the sum of its digits (e.g., 15 is linked to 6). These edges are not taken into
account for the layout as specified with the message notUseInLayout.
The first set of edges uses the default shape for lines, which is straight,
gray, and non-directed. For the second set, a shape is defined as a Bezier line
following the control points given by the expression value // 2, painted in a
translucent blue.
The layout used is cluster, forming a radial-like appearance. For this set
of elements and edges, horizontalTree, tree, radial are all relevant layouts.
132 Visualizing Polymetric Graphs using Mondrian
b := RTMondrian new.
Mondrian Script Structure 133
b shape circle
size: [ :aName |
(Float readFrom: (values detect: [ :a | a first = aName ]) second) log * 30 ];
withTextAbove.
Figure 11.4 illustrates the result. Data obtained from a CSV description
is used in the visualization. Each city and country is represented as a circle.
The size of the circle represents the number of people living in the city or the
country. We use a logarithmic scale to cope with large variations.
To keep the example concise and self-contained, the CSV content is pro-
vided as a Pharo String. If you wish to have the file externally provided, then
you may simply have
csvContent := 'file.csv' asFileReference contents.
values := (NeoCSVReader on: csvContent readStream) upToEnd.
...
In that case, a file named file.csv has to be in the same folder as your Pharo
image. A full path may be provided.
The script visualizes the collection class hierarchy obtained with the ex-
pression Collection withAllSubclasses. Relation between nodes is obtained by
linking each nodes to its subclasses. A tree layout is then set.
Each box of the visualization represents a class. The height of a class indi-
cates its amount of methods that it defines. The width indicates the number
Nesting 135
of variables the class defines. The color indicates the number of lines of code
the class defines (white = the class with the smallest number of lines of code,
black = the heaviest class). Each of Mondrian node is draggable and has a
tooltip.
The normalizer may also give a particular color to an element based on
an attribute or a property. Consider the following example (Figure 11.6):
b := RTMondrian new.
b shape circle.
b nodes: Collection withAllSubclasses.
b shape line color: Color veryLightGray.
b edges
moveBehind;
connectFrom: #superclass.
b layout cluster.
b normalizer
normalizeSize: #numberOfMethods min: 6 max: 40;
distinctColorUsing: #package.
b
The script simply associates a circle to each class, subclass of the class Col-
lection. Edges indicates superclass links. The cluster layout makes subclasses
located around a superclass. Each class has a color, representing the package
of a class. Each package has a color, given by the message distinctColorUsing:.
11.5 Nesting
Mondrian offers a convenient way to nest elements. Each node may act as a
space to which nodes may be added. The message nodes:forEach: has to be
used for that purpose. Consider the following example (Figure 11.7):
b := RTMondrian new.
b nodes: (0 to: 90 by: 10) forEach: [ :each |
b nodes: (1 to: each).
b layout grid
].
b layout flow.
b
There are 10 top level nodes, representing the values 0, 10, 20, ..., up to
90. Each node contains an amount of inner values that correspond to the
number it represents.
Encapsulating nodes may also have a title and be connected (Figure 11.8):
b := RTMondrian new.
b shape rectangle withTextAbove.
136 Visualizing Polymetric Graphs using Mondrian
Figure 11.6: Each circle is a class and its color indicates the class packages.
Inner nodes may have a proper shape and be connected. Consider the
following version of the visualization of the Roassal shapes (Figure 11.10):
b := RTMondrian new.
b shape rectangle withTextAbove;
fillColor: Color white; borderColor: Color black.
b nodes: RTShape withAllSubclasses forEach: [ :class |
138 Visualizing Polymetric Graphs using Mondrian
b shape rectangle
if: [ :m | m numberOfLinesOfCode < 5 ] fillColor: Color green;
if: [ :m | m numberOfLinesOfCode >= 5 ] fillColor: Color orange;
if: [ :m | m numberOfLinesOfCode >= 10 ] fillColor: Color red;
size: [ :m | m numberOfLinesOfCode sqrt * 5 ].
b nodes: class methods.
b edges connectToAll: #dependentMethods.
b layout sugiyama.
].
b edges connectToAll: #subclasses.
b layout tree.
b
b := RTMondrian new.
b layout tree.
].
b layout verticalLine.
b normalizer
objects: allChildren;
distinctColorUsing: #extension.
methods to require input from the user. The message allChildren, sent to the
FileReference returned by chooseDirectory, returns a collection of all the file
references. For each file directly contained in the selected directory, a hierar-
chy is shown. The size of a box representing a file depends on the number of
characters contained in that file. A logarithmic scale is used to cope with file
size disparities. Thanks to the GT infrastructure, clicking on a file displays
its content.
Chapter 12
Graphing, plotting, and curving data points is essential when analyzing data.
Roassal provides Grapher, an API implemented as a builder dedicated to
building interactive charts. A graph is represented as an instance of the class
RTGrapher. A set of data points is represented by an instance of the class
RTData. A graph is therefore built by adding data sets in a grapher object.
This chapter first covers the different charts supported by Grapher. Sub-
sequently, customization will be presented to fine-tune a chart.
12.1 Scatterplot
A scatterplot is a collection of Cartesian coordinates to display values for two
variables in a data set. A scatterplot is a common way to represent bi-variate
data.
Simple chart
Grapher offers a flexible way to draw scatterplots. Consider the following
example, to be evaluated in a playground (Figure 12.1):
b := RTGrapher new.
ds := RTData new.
ds dotShape color: Color blue.
ds points: { 4 @ 5 . 10 @ 5 . 3 @ 2 }.
ds x: #x.
ds y: #y.
b add: ds.
142 Charting, Plotting and Curving using Grapher
b := RTGrapher new.
ds := RTData new.
ds interaction popup.
ds dotShape color: (Color blue trans).
ds points: tab values.
ds x: [ :row | row at: 4 ].
ds y: [ :row | row at: 5 ].
b add: ds.
b maxY: 8.
Scatterplot 143
Figure 12.2: Depth and magnitude of seisms greater than 2.5 during the last
30 days.
b maxX: 700.
The code above fetches CSV data from a server. Try to open the used web
address to see what the data looks like before processing it. After having
created the table RTTabTable, the first row, which contains the column names,
is removed since it is of no use in this example. Columns 4 (depth) and 5
(magnitude) are converted into float numbers. The x value is obtained by
fetching the 4th element of a CSV row, and the y value from the 5th element.
Note that the data set we use does not contain information about earthquake
with a magnitude below 2.5.
Here is another example that visualizes application source code (Figure
12.3):
methods := Collection withAllSubclasses flatCollect: #methods.
b := RTGrapher new.
ds := RTData new.
ds dotShape circle color: Color red trans.
ds points: methods.
ds x: #numberOfLinesOfCode.
ds y: [ :m | m getSource size ].
b add: ds.
b
The variable methods contains all the methods of the Pharo Collection
class hierarchy. Approximately 3,500 methods are defined in the collection
144 Charting, Plotting and Curving using Grapher
class hierarchy.
Each data point is represented as a circle using the default size of 5 pixels,
and colored with a translucent red. The collection of data points is specified
using points:.
The x-value and y-value have to be computed for each data point. The
number of line of code for a method is obtained with numberOfLinesOfCode.
The block [ :m | m getSource size ] returns the number of characters of the
method.
Figure 12.3 reveals an obvious correlation between the number of lines
of code and the number of characters of the method. Deviation from the
diagonal indicates methods with either very long or very short lines of code.
Multiple charts
Grapher supports different data sets to be simultaneously displayed. Con-
sider the following example (Figure 12.4):
methods := Collection withAllSubclasses flatCollect: #methods.
trachelMethods := TRObject withAllSubclasses flatCollect: #methods.
b := RTGrapher new.
The first data set, the methods of the Collection class hierarchy (methods),
is colored in red. The second data set, all the methods defined in Trachel
(trachelMethods), are colored in blue.
Axis configuration
By default, the X and Y axes have four ticks, and each tick is labeled with
a numerical value with a precision of one decimal point. Grapher however
offers a number of options for configuring the axes. For example, in our sit-
uation, both axes are labeled with integer values: the number of lines and
the size of method definitions are integer values. Consider the example seen
previously, for which only integer values with a comma as thousand separa-
tors:
...
"Axis configuration"
146 Charting, Plotting and Curving using Grapher
• labelConversion: to transform the value being used in the axis. See the
section on translating the Y-axis.
The complete list of options to configure axes is given by the class RTAxis-
Configuration. Browse it for an overview of the different options.
12.2 Curve
A curve is obtained by connecting data points with a line.
Curve 147
Function
Consider the following script:
b := RTGrapher new.
ds := RTData new.
ds noDot.
ds points: (0 to: 3.1415 * 5 by: 0.01).
ds y: #sin.
ds x: #yourself.
ds connectColor: (Color red alpha: 0.4).
b add: ds.
ds := RTData new.
ds noDot.
ds points: (0 to: 3.1415 * 5 by: 0.01).
ds y: #cos.
ds x: #yourself.
ds connectColor: (Color blue alpha: 0.4).
b add: ds.
b
Stacking
Data points may be stacked, meaning that the X value is not computed, but
instead determined by the index in the collection. All the data points are
therefore equally horizontally distributed. The following example shows 4
data points horizontally ordered in the same order as they were provided to
points:, as seen in Figure 12.7.
b := RTGrapher new.
ds := RTData new.
ds dotShape color: Color red.
ds points: #(5 1 20 5).
ds y: #yourself.
b add: ds.
148 Charting, Plotting and Curving using Grapher
b := RTGrapher new.
b extent: 400 @ 200.
classes do: [ :c |
ds := RTData new.
ds dotShape rectangle color: (normalizer rtValue: c) trans.
ds points: (c methods reverseSortedAs: #numberOfLinesOfCode ).
ds interaction popup.
ds connectColor: (normalizer rtValue: c) trans.
ds y: #numberOfLinesOfCode.
b add: ds.
].
b
ds := RTData new.
ds dotShape size: 8; color: Color red.
ds points: #(5 6 7).
ds x: [ :v | v ].
ds y: [ :v | v * v ].
b add: ds.
b
In Figure 12.9, each point has both its X and Y values that are computed.
Consider this slightly modified version:
b := RTGrapher new.
ds := RTData new.
ds dotShape size: 8; color: Color red.
ds points: #(5 6 7).
ds y: [ :v | v * v ].
b add: ds.
In Figure 12.10, each point has both its Y value that is computed. The X
value is determined from the order of the objects provided to points:. Stacked
data sets are useful in bar charts, as in the following example:
b := RTGrapher new.
ds := RTData new.
ds barShape color: Color red.
ds points: #(5 6 7).
Curve 151
ds y: [ :v | v * v ].
b add: ds.
Figure 12.10 shows the result of the previous script. Bars are used instead
of dots.
b := RTGrapher new.
ds := RTData new.
ds points: points1.
ds connectColor: Color blue.
ds y: #yourself.
b add: ds.
152 Charting, Plotting and Curving using Grapher
ds := RTData new.
ds points: points2.
ds connectColor: Color green.
ds y: #yourself.
b add: ds.
The visual aspects of data points may be customized. Consider the fol-
lowing example.
b := RTGrapher new.
ds := RTData new.
ds dotShape rectangle color: Color red.
ds points: (RTShape withAllSubclasses sortedAs: #ageInDays).
ds y: [ :c | c ageInDays ].
b add: ds.
ds := RTData new.
ds dotShape circle color: Color blue.
ds points: (TRShape withAllSubclasses sortedAs: #ageInDays).
ds y: [ :c | c ageInDays ].
b add: ds.
Figure 12.13 represents the age of shape classes contained in Trachel (i.e.,
subclasses of the class TRShape) and Roassal (i.e., subclasses of the class
RTShape).
Labeled bar chart 153
b := RTGrapher new.
ds := RTData new.
ds barShape color: Color red.
ds points: #(-5 -9 10 -2).
ds barChartWithBarCenteredTitle: [ :value | '##', value asString ].
b add: ds.
b axisX noLabel; noTick.
b
12.4 Interaction
Interactions may be defined to get particular behavior upon user actions. A
typical case is getting a data point when the mouse is located above it. Per
default, all the data points have the popup activated.
Interaction 155
Sophisticated popup actions may give details about the pointed element.
Consider the following code (Figure 12.17):
b := RTGrapher new.
ds := RTData new.
ds barShape color: Color veryLightGray.
ds points: (RTShape withAllSubclasses).
ds y: [ :c | c methods size ].
ds interaction highlightColored: Color red.
ds interaction popup
named: [ :c | c name, ' methods' ]
background: Color lightBlue
group: [ :group :element |
| s ms |
s := RTBox new size: #numberOfLinesOfCode; color: Color red.
ms := element model methods sortedAs: #numberOfLinesOfCode.
group addAll: (s elementsOn: ms).
RTGridLayout on: group ].
b add: ds.
Two interactions are defined on each bar. First, the bar on which the
mouse is above is red. The original bar color is restored when the mouse
leaves the bar. The second interaction is a grouped popup, which has a name,
a background color, and a set of method elements. The size of a method
reflects the number of lines of code defining the method.
Note that this interaction is available for all kinds of charts.
156 Charting, Plotting and Curving using Grapher
12.5 Decoration
A chart often needs to be decorated, e.g., labeling particular values, adding
an average, threshold. Grapher supports such operations thanks to a dedi-
cated class hierarchy. All subclasses of RTAbstractGrapherDecorator describes
a particular decorator.
For example, a line indicating average may be added using (Figure 12.18):
b := RTGrapher new.
ds := RTData new.
ds dotShape color: Color red.
ds points: #(5 1 20 8).
b add: ds.
b addDecorator: (RTAverageDecorator new withLabel;
labelConvertion: [ :aValue | 'average = ', aValue asFloat asString ]).
b
Translating the Y axis 157
ds := RTData new.
ds points: #(5 10 6 2 -2.5).
b add: ds.
Color and the label computation may be parametrized. For example, the
example above sets a color to the following lines and labels. The class RTCur-
sorFollower offers the following configuration methods:
d := RTData new.
d points: (0 to: 3.14 * 2 by: 0.05).
f := [ :x | x sin + 4 ].
d y: [ :x | f value: x ].
d x: #yourself.
b add: d.
b := RTGrapher new.
d := RTData new.
d points: (0 to: 3.14 * 2 by: 0.05).
f := [ :x | x sin + 4 ].
d y: [ :x | (f value: x) - 4 ].
d x: #yourself.
b add: d.
b axisY
labelConversion: [ :y | y + 4 ].
b
b := RTGrapher new.
ds := RTData new.
ds dotShape circle size: 5; color: (Color blue alpha: 0.1).
ds interaction popup.
ds points: methods.
ds y: #numberOfLinesOfCode.
ds x: [ :m | m date julianDayNumber - oldestMethod date julianDayNumber ].
b add: ds.
b axisX
title: '';
labelRotation: -30;
numberOfTicks: 10;
numberOfLabels: 10;
labelConversion: [ :v | (Date julianDayNumber: v + oldestMethod date
julianDayNumber) ].
b
160 Charting, Plotting and Curving using Grapher
The class RTMultipleData provides a way to handle more than one metric per
data point, represented by grouped bars. Consider the following example
(Figure 12.23):
b := RTGrapher new.
d := RTMultipleData new.
d barShape color: Color blue.
d points: #( #('hello' 1 2 1) #('world' 2 4 2) #('bonjour' 3 5 4) #('Gutten Morgen' -1
4 -5)).
d addMetric: #second.
d addMetric: #third.
d addMetric: #fourth.
"Rotated text"
d barChartWithBarTitle: #first rotation: -30.
b add: d.
There are two ways for doing naming groups of bars. On the example
above labels begins on the central element of each group and is rotated. An
alternative is to use barChartWithBarCenteredTitle: which use a horizontal la-
bel.
Adding a legend 161
b := RTGrapher new.
The legend summarizes the color and the title of each data set.
Chapter 13
Visualization Composition
c := RTComposer new.
"First visualization"
g := RTGrapher new.
g extent: 200 @ 200.
g view: c view.
ds := RTData new.
ds interaction popup.
ds barShape color: Color blue.
ds points: data.
g add: ds.
g axisX noTick; noLabel.
g build.
c group: #graph.
"Second visualization"
164 Visualization Composition
b := RTPieBuilder new.
b view: c view.
b interaction popup.
b objects: data.
b slice: #yourself.
b labeled.
b build.
c group: #pie.
"Layouting"
c move: #graph onTheLeftOf: #pie.
RTMetricNormalizer new
view: c view;
objects: data;
distinctColor.
c view
The variable data contains four numbers and represents the input of the
script. Two visualizations are composed, using RTGrapher and RTPieBuilder.
The variable c represents a composer. The two visualizations are composed
of:
• Sharing the view to both builders. This is carried out with g view: c view
and b view: c view
c := RTComposer new.
"First visualization"
g := RTGrapher new.
...
g build.
c group: #graph.
"Second visualization"
b := RTPieBuilder new.
...
b build.
c group: #pie.
"Layouting"
c move: #graph onTheLeftOf: #pie.
c propagateHighlightToAll.
RTMetricNormalizer new
view: c view;
objects: data;
distinctColor.
c view
In total, four bars and four squares are represented. Each group contains
the model given by the variable data. Using propagateHighlightToAll highlights
elements in the view that have the same model. This is useful when elements
have to be globally highlighted.
"Layouting"
c move: #graph onTheLeftOf: #pie.
RTMetricNormalizer new
view: c view;
objects: data;
distinctColor.
c view
"========"
b := RTMapBuilder new.
b view: v.
cn := RTNColorLinearNormalizer
inContext: (countries collect: [:c | c at: 2])
lowColor: (Color r:0.8 g:0.8 b:1)
highColor: (Color r:0 g:0 b:0.3).
World population Example 167
"========"
grapher := RTGrapher new.
grapher extent: 600 @ 300.
grapher view: v.
ds := RTData new.
ds barShape width: 5; color: Color blue.
ds points: ((countries reverseSortedAs: #second) copyFrom: 1 to: 50).
ds y: #second.
ds interaction popupText.
grapher add: ds.
grapher axisX noLabel; noTick; title: 'countries'.
grapher axisY noDecimal; labelConversion: [ :aValue | (aValue / 1000000) round:
2]; title: 'Millions'.
grapher build.
composer group: #graph.
"========"
composer move: #graph above: #worldMap.
Applications
Chapter 14
es := RTBox new
size: #numberOfMethods;
color: [ :cls |
cls numberOfLinesOfCode <= 1000
ifTrue: [ Color green ]
ifFalse: [ Color red ] ];
elementsOn: Collection withAllSubclasses.
v addAll: es.
RTFlowLayout on: es.
The first code given above creates a small visualization and adds a legend
to it. The legend is available on-demand, meaning that the user has to locate
the mouse cursor above the ? character, located on the top-left corner of the
visualization. The view, referenced by the variable v, has to be passed to the
legend builder. The legend then has to be configured. The message addText:
provides a descriptive text of the legend and addColor:text: is used to give a
Building a legend 173
meaning to a color. The message build adds the legend in the view v.
lb := RTLegendBuilder new.
lb view: b view.
lb addRectanglePolymetricWidth: 'number of instance variables' height: 'number
of methods' box: ''.
lb addColorFadingFrom: Color gray to: Color red text: 'Number of lines of code'.
174 Documenting with a Legend
b view
Expressing Epidemiological
Models
S),
• Once infected, the individual is Infectious (status I) and spreads the in-
fection,
• After Recovery (status R), the individual is immunized and cannot be-
come infected again.
b := RTGrapher new.
b extent: 400@200.
ds := RTData new.
ds noDot.
ds connectColor: Color blue.
ds points: data.
ds y: [ :v | v at: 3 ].
ds x: [ :v | v first julianDayNumber - minValue ].
b add: ds.
ds := RTData new.
ds noDot.
ds connectColor: Color green.
178 Expressing Epidemiological Models
ds points: data.
ds y: [ :v | v at: 4 ].
ds x: [ :v | v first julianDayNumber - minValue ].
b add: ds.
b axisX
labelRotation: -30;
labelConversion: [ :v | (Date julianDayNumber: v + minValue) ].
b build.
lb := RTLegendBuilder new.
lb view: b view.
lb addText: 'Ebola cases'.
lb addColor: Color blue text: 'Guinea'.
lb addColor: Color green text: 'Liberia'.
lb build.
b view
model
atParameter: #mu
assignValue:
[ :aModel| |c val|
c := aModel currentCompartment at: #species.
c = #mosquito ifTrue: [ val := 12.17 ].
c = #reservoir1 ifTrue: [ val := 0.05 ].
c = #reservoir2 ifTrue: [ val := 0.05 ].
val
].
model
atParameter: #N
assignValue:
[ :aModel| |c|
c := aModel currentCompartment at: #species.
aModel sizeOfPopulation: c
].
model
atParameter: #beta
assignValue:
[ :aModel| |c val|
c := aModel currentCompartment at: #species.
c = #mosquito ifTrue: [ val := #(0 0.02 0.02) ].
c = #reservoir1 ifTrue: [ val := #(0.02 0 0) ].
c = #reservoir2 ifTrue: [ val := #(0.02 0 0) ].
val
].
model
atParameter: #lambda
assignValue:
[ :aModel|
((aModel atParameter: #beta) *
(aModel atCompartment: {#status->#I})) sum
].
An example of Ebola spatial model with Kendrick 183
simulator := KESimulator new: #Gillespie from: 0.0 to: 0.5 step: 0.0027.
simulator executeOn: model.
db := (KEDiagramBuilder new) data: ((simulator timeSeriesAt: '{#status: #I}') sqrt).
to: { #status->#I }
probability: 'lambda'.
sirConcern
addTransitionFrom: { #status->#I }
to: { #status->#R }
probability: 'gamma'.
model
atParameter: #beta
assignValue: 0.0002.
model
atParameter: #gamma
assignValue: 0.1.
model
atParameter: #lambda
assignValue:
[ :aModel| |c|
c := aModel currentCompartment at: #country.
(aModel atParameter: #beta)*(aModel atCompartment: {#status->#I. #country-
>c}) ].
100 days. The right hand graph visualizes six countries in Africa, the color
of each one depends on its infection degree.
free network.
The visualization of network in Kendrick is done by the network builder
module. The example below represents a random network of a population
of 100 individuals. Each node of the network corresponds to an individual
of the population. The model is run as an individual-based simulation.
model := KEModel new population: (KEPopulation size: 100).
model
atParameter: #lambda
assignValue:
[ :aModel||node|
node := aModel currentCompartment at: #node.
((aModel atParameter: #network)
contactsOf: {aModel. #node->node. #status->#I})
* (aModel atParameter: #beta)/(aModel atParameter: #N)
].
model atParameter: #beta assignValue: 100.
model atParameter: #gamma assignValue: 0.1.
The scale free network of the model after 10 days can be seen in Figure
15.9.
As can be seen on these figures, the random network shows a lack of clus-
tering because each two random nodes have the same probability to make
Network Visualization of Epidemiological Models 189
an edge. In the scale-free network, some individuals may have many more
contacts than others. The smallworld network is formed from a lattice grid
with some rewire nodes. This network is defined below:
network := KEContactNetwork nodes: 100 topology: { #smallworld. #K->2. #beta
->0 }.
model
atParameter: #lambda
assignValue:
[ :aModel||node|
node := aModel currentCompartment at: #node.
((aModel atParameter: #network)
contactsOf: {aModel. #node->node. #status->#I})
* (aModel atParameter: #beta)/(aModel atParameter: #N)
].
model atParameter: #beta assignValue: 1.
model atParameter: #gamma assignValue: 0.1.
simulator := KESimulator new: #IBM from: 0.0 to: 100 step: 0.1.
simulator executeOn: model.
nb := KENetworkBuilder new
data: simulator allTimeSeries;
network: (model atParameter: #network);
status: #(I); viewDataAtTime: 30.
nb open
192 Expressing Epidemiological Models
OpenStreetMap Integration
v add: e.
v @ RTDraggableView.
v add: e.
v @ RTDraggableView.
movingCamera := [ :locationLatLong |
v canvas camera translateTo:
(map latLonToRoassal: locationLatLong).
v signalUpdate ].
"Adding a menu"
mb := RTMenuBuilder new.
mb view: v.
mb menu: 'Paris' callback: [ movingCamera value: 48.8567 @ 2.3508 ].
mb menu: 'London' callback: [ movingCamera value: 51.50722 @ -0.12750 ].
mb menu: 'NewYork' callback: [ movingCamera value: 40.7127 @ -74.0059 ].
mb build.
RTEmptyViewContext setOnView: v.
v
196 OpenStreetMap Integration
The script above defines the block called movingCamera which takes as
argument a geographical location. The blocks move the camera accordingly
and signal a refresh. A menu is then defined to let the user click on each city
name to move the camera.
v := RTView new.
map := RTOSM new.
e := map element.
v add: e.
tab values
do: [ :row |
e := (RTEllipse new
size: (2 raisedTo: row fifth) * 10;
Decorating the map 197
v @ RTDraggableView.
RTEmptyViewContext setOnView: v.
v
The script begins by fetching data from the Earthquake Hazards Program
server. The fetched CSV file has to be slightly processed to replace double
commas („) entries by a zero (,0,) and to convert some columns into float
numbers. Column 2 of the file corresponds to the latitude of the event; Col-
umn 3 to the longitude; Column 5 to the seism intensity.
For each table row an ellipse is created with an exponential size. Each
produced element has a popup and is highlightable. The ellipse is then trans-
lated to its calculated position in the Roassal space.
A scale of 0.02 is used to give an overview of the map. Thanks to the noIni-
tializationWhenOpen setting the map keeps the 0.02 scale value when opened.
Without this setting, the value of 1 is used. The view is made draggable.
198 OpenStreetMap Integration
"We are interested in countries that have at least one refugee and we wort the
countries in reverse order according to the number of refugies they have. This
is helpful in the case of element overlaping"
interestingRows := (tab values reject: [ :row | row third < 1 ])
sorted: [ :a :b | a third > b third ].
Getting country location 199
v @ RTDraggableView @ RTZoomableView.
RTEmptyViewContext setOnView: v.
v canvas camera focusOnCenterScaled.
v
The method peopleForUNHCR contains the data fetched from the UNHCR
server.
Chapter 17
Network Latency
Nowadays, people spend a lot of time using electronic gadgets such as com-
puters, smartphones or tablets. As time goes by, all of these devices seem
useless without Internet connectivity. Users always want to improve their
experience while using Internet services. They acquire Internet connections
with the highest bandwidth possible; they buy the phone which promises
the fastest connectivity by using 4G; and they sign up for residential fiber-
optic Internet. All of these options ar eexpected to offer the best experience.
However, there is a hidden element that can affect user experience that most
users do not consider, which is network latency.
This chapter is written by Leonel Merino ([email protected]), PhD stu-
dent from the University of Bern.
17.1 Prelude
The latency of a network is the sum of the latency added by every node that
takes part in a network communication. Each network node has an inherent
latency, specified by its manufacturer. The latency of a network node —such
as a router, a switch, a hub— is the time elapsed from the moment when it
receives a packet and when it sends a response. The path between two com-
puters in a network is dynamic and normally implies several hops through
intermediate nodes. Therefore, the latency of a connection will depend on
the path that it takes. The range of network latency in Internet are normally
in the order of milliseconds. Since the latency is a base delay in each con-
nection, protocols that open many connections are more affected. In conse-
quence, services such as music, video and online games which typically split
data into a bunch of small packets are more sensitive to high latency.
202 Network Latency
Motivation
When a node of the network fails, the communication paths that pass by the
node increase their latency (the impact depends on the nature of the failure).
From the point of view of the company which offers the Internet service,
there is a need for monitoring how is latency levels among the nodes of the
network. In case of a failure, the nodes may react and take corrective actions.
Visual tools are extensively used for monitoring services. They allow opera-
tors to spot failures at a glance, or in other cases to be aware of how close the
latency is to the threshold the user experience is compromised.
Case study
In our case study we analyzed a proxy service that operates 145 nodes dis-
tributed in different cities around the world. By using Roassal we developed
a tool for visualizing the status of the service. We called the tool Network
Latency Visual Analyzer (NLVA). The tool envisions two kinds of users: a
company that manages a network and clients of that company. The tool of-
fers different views of the dataset which helps the company to be aware of
failures (to spot where possible failures occurred and what potential users
can be affected); to take actions for improving the service by highlighting
nodes that are geographically close but that have a high latency —in com-
parison with the rest of them— allowing the company to possibly change
the service provider between those nodes; and to spot where the best end-
points are for each node that offers the lowest latency, which can be of help
to end users —e.g., a gamer who wants to know to which server should he
connect to minimize the lag.
is the main class that holds methods to retrieve the dataset, to trigger ping
commands in the local machine, and to build the visualization.
Resources
NLVA comes with a dataset that contains a snapshot of the latency between
servers of a network. It also includes the location (lat/long) of the cities
highlighted in the visualization. In addition, it holds IP addresses that are
physically located in each city (used when testing user local connectivity).
These data were collected from the following sites:
Resource URL
Network Latency dataset https://wonderproxy.com
IP Location http://www.iplocation.net
Geolocation http://www.latlong.net
Visual Queries
The following queries are examples showing that a visualization of the
dataset provides a quick notion of the answer that can be followed by a de-
tailed analysis of the latency measures. At the end, it can help to exclude
candidates from the set of possible answers, narrowing the space of solu-
tions. As an exercise the reader can surely find more queries that can be
supported by the different views included in the tool.
1. Where should a company using this network store content for deliver-
ing to users in Paris? (Hint: Best)
2. Do you notice some possible failures in the network that may impact
the latency? (Hint: Worst)
4. What are the cities where the company should invest to improve over-
all network latency? (Hint: CloseHigh)
5. If you want to provide a service in both the US and Europe (using this
network), where would be the best two cities to locate a host? (Hint:
FarLow)
Figure 17.2: Worst latency for each city. Note that Valencia and Nairobi con-
centrate the most of the worst endpoints for connections.
Figure 17.4: MST shows what probably are physical wires connecting cities.
Figure 17.5: FarLow. Far cities that show low latency. Most East Coast cities
of the US are the best endpoints for European connections. A couple of un-
expected results Joao Pessoa-Orlando and Portland-Anchorage.
208 Network Latency
those results with the integrated data that comes with the tool —caveat: it
can only be done for the cities that come with the visualization (left and
right-click).
Chapter 18
Moose is a platform for data and software analysis. Moose is the result of a
collaborative effort, initiated at the University of Bern, which now comprises
several companies and research groups spread all over the world. The web-
site of Moose is http://moosetechnology.org.
Visualization plays an important role in assessing the quality of a soft-
ware. The source code defining a software may be very large and is charac-
terized using a wide range of metrics and structural properties. Identifying
anomalies, and more importantly, proposing actions to improve the quality
is difficult. It is known that visualization greatly alleviates software mainte-
nance.
Moose is often used to craft specific software engineering tools that guide
engineers in making proper decisions about the course of a software develop-
ment. Humane Assessment is a method designed by Tudor Gîba and largely
described on the website http://www.humane-assessment.com.
What Moose is good for: Moose is made to carry out analysis on the source
code structure. A whole range of analyses may be conducted by analysis
source code. Moose is also frequently used to analyze configuration files,
process descriptions, or mailing lists.
What Moose is not good for: Moose cannot directly be used to analyze the
dynamic execution of an application. Dynamic analysis consists in recording
relevant information during a program execution. Such information may
then be used to spot particular behavior.
212 Analyzing Software using Moose
Moose has two built-in parsers: one for Pharo, and another for MSE. The
first parser is accessible by clicking on the ST icon, top-right of the Moose
Panel. Clicking on it will open a package selector. You simply have to select
the Pharo packages you wish to include in your model. The second parser
takes as input a MSE file, accessible by clicking on the MSE button.
Consider the following Java code:
package agilevisualization.example;
The corresponding MSE file of that code is 48 lines long. The first few
lines are:
(
(FAMIX.Inheritance (id: 3)
(subclass (ref: 10))
(superclass (ref: 4)))
(FAMIX.Class (id: 4)
(name 'Object')
(container (ref: 7))
(isStub true))
(FAMIX.FileAnchor (id: 5)
(element (ref: 1))
(endLine 6)
(fileName './agilevisualization/example/HelloWorld.java')
(startLine 4))
(FAMIX.FileAnchor (id: 6)
(element (ref: 10))
(endLine 7)
(fileName './agilevisualization/example/HelloWorld.java')
(startLine 3))
...
)
Producing a MSE file from Java source code is done using VerveineJ. Un-
fortunately, at the time this chapter was written, VerveineJ has a proprietary
license. Contact the book author or the Moose mailing list for more informa-
tion.
Along this chapter, we will use a running example, obtained from the
Java application checkstyle. Checkstyle is a development tool to help pro-
grammers write Java code that adheres to a coding standard. The source
code of Checkstyle is available from its website: http://checkstyle.sourceforge.
214 Analyzing Software using Moose
The item All classes lists all the classes defined in the application and all
Browsing the source code 215
the classes used by checkstyle that are not defined in it. Some examples of
such classes are Object, String, Integer, which are defined in the Java runtime.
Select the All model classes item to list all the classes defined in the appli-
cations. Below the list of classes, a text input accepts filtering queries. Enter
each numberOfMethods > 30. A third pane appears and lists the classes de-
fined in checkstyle that have more than 30 methods (Figure 18.3).
Since the items displayed in the panel All model classes are instances of
the class FAMIXClass, the each variable refers to an FAMIXClass. Browsing
this class using the Pharo code browser will give you the list of methods that
are understandable by the each variable.
Queries involving the class name are also often formulated. For example,
the query
'*Log*' match: each name
designates classes defined in Checkstyle that have the word Log in their
name. The string being an expression regular, matched using match:.
Another relevant query to type in is: each isStub. You need to select the
item All classes instead. This simple query filters out all the classes that be-
longs to the checkstyle application, to leave the classes are used by Check-
style. Said in other words, all the externally provided classes used by Check-
style are remains. This is handy to assess external dependencies. In the
Moose jargon, a stub class is a class that is used by the analyzed application
but not defined in it. Checkstyle contains 398 stub classes, corresponding
to classes defined in other libraries, not part of the .MSE file. For example,
216 Analyzing Software using Moose
This query returns the classes that override more than 1/3 of the classes
that it inherits. Such classes are rather suspicious since it may be worth con-
sidering whether they belong to the adequate class hierarchy.
This simple visualization shows that not all the packages con-
tain the same amount of code. The first package, the largest, is
com.puppycrawl.tools.checkstyle. Clicking on the package opens up a new pane.
The second tab gives the list of properties for that package. In indicates that
it defines 142 classes, spread over 5,617 lines of code.
Clicking on the entry Types lists all the classes defined in the package
checkstyle. The following script reveals a number of facts regarding these
142 classes (Figure 18.5):
b := RTMondrian new.
b shape rectangle
width: #numberOfAttributes;
height: #numberOfMethods.
b nodes: (self reverseSortedAs: #numberOfMethods).
b edges connectFrom: #superclass.
Visualizing the code distribution 217
b normalizer
normalizeColor: #numberOfLinesOfCode.
b layout flow.
b
Most of the classes are small, in terms of lines of code, number of defined
methods and attributes. Moreover, only two of these classes are subclasses.
218 Analyzing Software using Moose
b := RTMondrian new.
b shape box
if: [ :c | c methods anySatisfy: [ :m | '*error*' match: m name ] ]
fillColor: Color blue;
if: [ :c | c methods anySatisfy: [ :m | 'visit*' match: m name ] ]
fillColor: Color red.
b nodes: self.
b edges connectFrom: #superclass.
b layout tree.
b normalizer
normalizeHeight: #numberOfMethods max: 100;
normalizeWidth: #numberOfAttributes max: 100.
b build.
b view @ RTDoubleScrollBar.
b view
The script colors in blue classes that have at least one method with the
word error in it and in red classes that are visiting some entities. The hierar-
chy of red classes indicates a specialization of visitors. Notice that that errors
are rarely handled.
Dependencies using Moose Chef 219
b := RTMondrian new.
b nodes: self.
b edges connectFrom: #superclass.
b layout cluster.
b shape
bezierLineFollowing: #superclass;
color: Color blue.
b edges
notUseInLayout;
connectToAll: [ :cls | cls queryAllOutgoingInvocations atTypeScope ].
b
Figure 18.10: A new entry has been added in the sub menu Visualize.
b edges
notUseInLayout;
connectToAll: [ :cls | cls queryAllOutgoingInvocations atTypeScope ].
b inspect
After defining this method, right clicking on the All model classes item in
the Moose Panel gives a new menu entry called Dependencies (Figure 18.10):