parent previous next question (Smalltalk Textbook 42)

EngiBranch & EngiBranchWithArrow

Let us look at 'EngiBranch', used for connecting vertices in a graph. File in Appendix 26. Our "arc" is a subclass of 'EngiGeometric' just like 'EngiVertex' was. The class definition of the arc is:

---------------------------------------------------------------------
EngiGeometric subclass: #EngiBranch
        instanceVariableNames: 'startVertex endVertex branchWeight '
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Engi-Graph'
---------------------------------------------------------------------

Instance variables are 'startVertex' for the "from" node, 'endVertex' for the "to" node and 'branchWeight' for the weight of the arc.

The display area for the arc is calculated from the center of the start node and the end node with added consideration for 'lineWidth' of the 'EngiGraphicsState' instance, which is kept in the 'graphicsState' instance variable (inherited from 'EngiGeometric'). The display area calculation in 'EngiGeometric ' which is a super class of 'EngiBranch' is defined to be 'subclassResponsibility':

---------------------------------------------------------------------
!EngiGeometric methodsFor: 'bounds accessing'!

computeBounds
        ^self subclassResponsibility! !
---------------------------------------------------------------------

Therefore, 'EngiBranch' calculates it's display area as follows. 'startPoint' answers the center position of the display area of the start node, and 'endPoint' answers the center position of the display area of the end node.

---------------------------------------------------------------------
!EngiBranch methodsFor: 'bounds accessing'!

computeBounds
        | bounds margin expand |
        bounds := (self startPoint corner: self startPoint)
                                merge: (self endPoint corner: self endPoint).
        margin := (self lineWidth / 2) asInteger.
        expand := margin @ margin corner: margin + 1 @ (margin + 1).
        bounds := bounds rounded expandedBy: expand.
        ^bounds! !
---------------------------------------------------------------------

By the way, did you notice the first assignment to the temporary variable 'bounds'? It seems like extra work to create rectangles at each of the start and end points, but if you build the rectangle yourself, you must remember that you cannot assume any spatial relationship between start and end points, that is, the end point can be in any of the four quadrants relative to the start point.

You need to redefine 'displayFilledOn:at:' and 'displayStrokedOn:at:' to display an arc. Also, you need to redefine 'rotatedBy:', 'scaledBy:', 'translatedBy:' for affine translations.

After filing in Appendix 26, evaluate Program 42-1. The Program creates an instance of an arc ('EngiBranch') and displays it in a window.


Program-42-1: (EngiBranch, EngiVertex, EngiDisplayModel; vertexPoint:, 
startVertex:, endVertex:)
---------------------------------------------------------------------
| aBranch aVertex1 aVertex2 |
aBranch := EngiBranch new.
aVertex1 := EngiVertex new.
aVertex1 vertexPoint: 10 @ 10.
aVertex2 := EngiVertex new.
aVertex2 vertexPoint: 90 @ 90.
aBranch startVertex: aVertex1.
aBranch endVertex: aVertex2.
aBranch lineWidth: 3.
aBranch strokeColor: ColorValue red.
(EngiDisplayModel on: aBranch) open
---------------------------------------------------------------------

The program prints a red arc (a line) in a window. The yellow button menu leads to an inspector with the following data:

---------------------------------------------------------------------
anEngiBranch
        
                anEngiGraphicsState
                         ColorValue red
                         nil
                         1
         17@17 corner: 100@100
        
                anEngiVertex
                         anEngiGraphicsState
                         10@10 corner: 27@27
                         10@10
                         16@16
                         nil
        
                anEngiVertex
                         anEngiGraphicsState
                         90@90 corner: 107@107
                         90@90
                         16@16
                         nil
         nil
---------------------------------------------------------------------

Program 42-2 uses 'EngiVisualTransporter' to move an arc around via the mouse cursor.


Program-42-2: (EngiBranch, EngiVertex, EngiVisualTransporter, InputSensor)
---------------------------------------------------------------------
| aBranch aVertex1 aVertex2
  activeWindow activeSensor visualTransporter |
aBranch := EngiBranch new.
aVertex1 := EngiVertex new.
aVertex1 vertexPoint: 10 @ 10.
aVertex2 := EngiVertex new.
aVertex2 vertexPoint: 90 @ 90.
aBranch startVertex: aVertex1.
aBranch endVertex: aVertex2.
aBranch lineWidth: 3.
aBranch strokeColor: ColorValue red.
activeWindow := aBranch class activeWindow.
activeSensor := activeWindow sensor.
visualTransporter := EngiVisualTransporter load: aBranch.
visualTransporter
        follow: [activeSensor cursorPoint - aBranch bounds origin]
        while: [activeSensor noButtonPressed]
        on: activeWindow graphicsContext
---------------------------------------------------------------------

Now you can create arcs and nodes of any size and color. In addition you can use 'EngiBranchWithArrow' to create an arrow instead of just a straight line. 'EngiBranch' has no semantics associated with the "start" node and the "end" node, but 'EngiBranchWithArrow' draws the arrow end at the "end" node. Other than having this "direction" distinction, 'EngiBranchWithArrow' is used exactly like 'EngiBranch'.


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves