4. Shaping Elements

As already mentioned the Shape Script language is a bit C-like. So probably most people will not have much trouble to learn the syntax. Anyway it’s very limited. An EBNF syntax description can be found in the appendix.

Generally all keywords and even strings are case insensitive. So it does not matter whether you write LineTo rather than lineto in the above example. The auto-completion suggests the first variant in camel case which is definitely better to read.

4.1 The Main Shape

Or to talk Sparxian: shape main. As you already have seen, these two keywords introduce the body wrapped in curly brackets. The instructions inside will be executed each time a stereotyped element is shown on a diagram. As already mentioned each element has 100 units in width and depth (as opposed to height since the units increase downwards). Even shapes which appear oval (like Use Case) have that rectangular 100² units frame.

Now let’s see what can be done to actually draw something. Let’s start with the two methods LineTo and MoveTo used in the introductory example.

Simple Lines

There is not much to say:

MoveTo(x,y)
moves the graphic cursor to the specified coordinate. Initially in the script the cursor is located at (0|0).
LineTo(x,y)
draws a line from the current coordinate to the new (x|y) and sets the current cursor to that position.

A somehow more advanced way to draw a line is a bezier curve.

BezierTo(xStart,yStart,xBend,yBend,xEnd,yEnd)
draws a line from (xStart|yStart) to (xEnd|yEnd) bending it towards (xBend|yBend)

Other ways to get some rounded shapes are the Arc and ArcTo methods:

Arc(left,top,right,bottom,xStart,yStart,xEnd,yEnd)
will draw a partial ellipse (see Ellipse below in the next chapter) with the bounding specified by left, top, right and bottom. But it is only drawn from the intersection of the line crossing the ellipse which is drawn from its center to (xStartX|yStart) to that of the intersection center/(xEnd|yEnd).
ArcTo(left,top,right,bottom,xStart,yStart,xEnd,yEnd)
will draw the same as Arc except it will continue a straight line from the current pen position to the start of the arc and it will set the current pen position to the end of the arc (which is the counter-clockwise end)

Closed Lines

In contrast to the simple lines explained above the closed lines have an inside which can be filled with a solid color. There are different ways to create such a graphic element.

Rectangle(left,top,right,bottom)
will do as the name suggests where the edges are located at left,top,right and bottom.
RoundRect(left,top,right,bottom,cornerWidth,cornerHeight)
is almost the same as Rectangle but it will have rounded edges. The rounding is not related to the internal 100² units[^square] but given in absolute pixels! That means it will not scale with the shape as you can see below with the two differently sized shapes.
Polygon(xCenter,yCenter,numberOfSides,radius,rotation)
will draw a polygon with numberOfSides sides. The center will be at (xCenter|yCenter) and the radius (distance from center to an edge) is radius. Further the whole object will be rotated rotation degrees counter-clockwise. While all other parameters are integers, rotation is a float. Makes perfect sense to distinguish half a degree in 100² units.
Ellipse(left,top,right,bottom)
draws an ellipse inside the specified boundaries. For good reasons there is no Circle method as resizing the element on the diagram will in almost all cases distort it to an ellipse.

The following picture shows a number of example shapes.

The pentagon drawn in line 3 has the same radius as the (circular) ellipse drawn on line 2. The ellipses on line 4 and 5 mark the center and the right (and start-) margin of the pentagon.

A similar pentagon as on line 3 is drawn on line 7 except that it is rotated by 90 degrees counter-clockwise (and it is located at the right end of the shape).

Lines 8 and 9 show a triangle and a hexagon – hopefully self-explanatory.

There is also a way to draw non-regular shapes by specifying the edges and filling the contents.

StartPath()
introduces a sequences of MoveTo and LineTo commands describing the shape to be filled.
StartCloudPath(puffWidth,puffHeight,noise)
is almost the same as StartPath but it will make the shape cloudy. If you pass an empty parameter list EA will take some default values which mostly resemble a cloud. A value tuple of 40|20 for puffWidth|puffHeight will give a summer time cloud. Small values like 4|2 will result more in ripped paper shapes. Suppling zero as noise will result in absolutely regular fluffs. A value of about 1 will make it naturally irregular. All values can be supplied as float. Any integer will suffice however.
EndPath()
tells that the path is ended and going to be filled.
FillAndStrokePath()
Will fill the inside of the previously specified path and draw a border line.
FillPath()
is almost the same except it does not draw the border line.
StrokePath()
is another quite useless method as it just draws the border. Something you would achieve when simply not using all that path-stuff.

Finally there is a way to draw the native shape which will normally be used inside control structures (as we will see later).

DrawNativeShape()
will draw exactly the shape as if no Shape Script were applied.

The example above shows the preview and how a class will appear in the diagram. Since the preview does not know anything about the element where the Shape Script will be applied it will not draw anything for DrawNativeShape.

Painting

All of the above methods use default fill, border and font colors and border style. These can be changed using the following methods.

SetFillColor(red,green,blue)
replaces the fill color for the methods described in the previous chapter. The parameters take the RGB values as integer in the range from 0 to 255.
SetFillColor(newColor)
is the same as the previous except that it takes a color object as parameter. Currently9 there are just three methods to deliver a color object: GetUser***Color() (see below). You can not (yet?) define any color object in Shape Script.
SetFontColor(red,green,blue)
replaces the color for any text appearing in a shape.
SetFontColor(newColor)
is the same as the last method. Like for the above describe newColor you can only use one of the GetUser***Color() methods.
SetLineStyle( lineStyle)
sets the line style according to the lineStyle supplied as string. The string itself is interpreted case insensitive. You need to choose from one of the values solid, dash, dot, dashdot or dashdotdot. Choosing a different string value will result in an unpredictable line style
SetPen(red,green,blue)
replaces the color for border lines with the one supplied as RGB.
SetPen(red,green,blue,penWidth)
is the same as the previous one except that the additional parameter penWidth will set the width of the lines being drawn. The value for it must be between 1 (default thin line) and 5 (thickest line).
SetPen(newColor)
see the remark about color objects above.
SetPen(newColor,penWidth)
ditto
SetPenColor(red,green,blue)
yet another way to say SetPen(red,green,blue).
SetPenColor(newColor)
I must not repeat myself, must I?
SetPenWidth(penWidth)
just like SetPen(red,green,blue,penWidth) but without changing the color.
SetDefaultColors()
will reset any of the previously changed color attributes to the defaults.
GetUserFillColor()
returns a color object with the user defined color for use in one of the previously mentioned color parameters.
GetUserBorderColor()
ditto for the border
GetUserFontColor()
and for the font color.
GetUserPenSize()
returns the width (why be orthogonal?) of the pen as defined by the user. The only place where you can use this method is inside SetPen/SetPenWidth.

As you can see the colors shown in the painter for border, font and fill are used to color the three rectangles. Additionally the lower right rectangle is drawn with a thick border.

4.2 Text

Of course you need to be able to put some text inside a shape. The following methods can be used for that purpose. Note that any text goes into the whole active shape rectangle, which is shape main. So currently you will only be able to print text from top left to bottom right of the shape. We will see later how to place text at certain positions.

Print(text)
Renders the string text at the current print position (which starts top left of the actual shape).
Println(text)
is the same except that it renders a new-line at the end of the supplied text string.
PrintWrapped(text)
actually the same as Print. According to the help: Prints the specified text string, wrapped over multiple lines if the text is wider than its containing shape. Let’s see:

And the difference is —

None. Both texts are wrapped inside the shape. Further the text protrudes the shape on the bottom. Regardless of which Print variant is used.

PrintIfDefined(propertyname,truePart,falsePart)
will print the string truePart if the property is defined and has a not-empty value assigned. Else it will print falsePart.

Strings

The above mentioned Print methods and some of the methods described in the following take a string parameter. Since Shape Script is very limited you can not manipulate strings. To overcome this in a certain extent you can let Shape Script replace strings by some meaningful contents. That is e.g. the name of the element in place or the contents of tagged values. The mechanism is very simple. Shape Script interprets hash-tags and replaces them textually. E.g. the hash-tag #name# will be replaced by the name of the element. There is quite a number of properties you can use instead of name which will be covered in detail later in chapter Properties.

4.3 Shape Attributes

Before going on with more sophisticated methods we need to look at shape attributes. Some of these have global influence on the shape while others influence the behavior of certain methods. The general format for an attribute assignment is

<attribute> = <value>
where <attribute> is one of the names below and <value> is either a string (e.g. "string"), and integer or a tuple (e.g. (0,5)).
bottomAnchorOffset
is a tuple defining the offset for an embedded element related to the bottom of the element. E.g. a port will be (-7,-7) while a part will be (0,0). This offset is applied for embedded elements attached to the bottom of the parent element.
topAnchorOffset, leftAnchorOffset, rightAnchorOffset
are analogously attributes for the other edges.
dockable
is either standard or off.
editableField
Defines a shape as an editable region of the element. Valid Values: alias, name, note and stereotype.
fixedAspectRatio
either true or false. In the first case the element will scale always proportionally in width and height.
h_Align
Affects horizontal placement of printed text and sub-shapes (see next chapter) depending on the layoutType attribute. Valid values: left, center or right.
v_Align
Affects vertical placement of printed text and sub-shapes (see next chapter) depending on the layoutType attribute. Valid values: top, center or bottom.
layoutType
Determines how sub-shapes are sized and positioned. Valid values: leftright, topdown or border.
noShadow
can be true or false. Set to true if you want to turn of the shadow rendering.
orientation
Applies to decoration shapes (see below) only, to determine where the decoration is positioned within the containing element glyph. Valid values: NW, N, NE, E, SE, S, SW or W.
preferredHeight, preferredWidth
is used in sub-shapes (see next chapter) at N/S (height) or E/W (width) orientation. The defined size is used to calculate the offset for the center shape. You can supply both values in a single shape. In that case only the relevant size will be evaluated.
scaleable
Set to false to prevent rotation of the shape. This attribute is only applicable to the source and target shapes for line glyphs. Valid values: true or false (default = true)

4.4 Sub-Shapes

A so-called sub-shape is a rectangle placed inside10 of shape main. Its purpose is mainly to either place text blocks inside a shape and/or to repeat a certain graphic figure inside a shape. Each sub-shape has a limited width and a certain height. There are two variants of sub-shapes: local and global. Subsequently invoked sub-shapes will be stacked depending on the layoutType property:

topdown
The first sub-shape is placed on top of the main shape. The next one directly below the previous one.
leftright
Similarly but the sub-shapes join to their right edge.
border
when this value is set you can only use a sub-shape invocation with 2 parameters where the first is the name and the second is a compass orientation (N, S, E, W or CENTER). According to the help the shape shall be placed in the according part.

Local sub-shapes have an additional offset you must specify which can be used to compensate the offset and place the sub-shapes arbitrarily inside the main shape11.

A sub-shape is defined by

shape <non-reserved-name> { <block> }
where <non-reserved-name> is any string except main, childelement, label, target, source, LeftTopLabel, MiddleTopLabel, RightTopLabel, LeftBottomLabel, MiddleBottomLabel and RightBottomLabel. <block> consists of any of the previously defined graphic methods like e.g. LineTo, Rectangle, Print and so on. Global ones appears at any top level in a Shape Script like shape main. Local ones are declared inside shape main` at any statement position.

A global sub-shape is invoked inside shape main by calling

AddSubShape(shapeName,width,height)
where shapeName is the <non-reserved-name> of a sub-shape defined in the script. It must be supplied as a string. The width tells how many units of the width for shape main are being used. height specifies the height analogously. Placement of sub-shapes will be according to layoutType as described above.

A local sub-shape has more parameters and is invoked inside shape main by calling

AddSubShape(shapeName,width,height,xOffset,yOffset)
where shapeName is the <non-reserved-name> of a sub-shape defined locally in the script. It must be supplied as a string. The defined shape will be placed inside shape main according to the rules described for the global variant. Additionally, as said, the position will changed by adding xOffset|yOffset.

As you can see the stacking of the sub-shapes is compensated by the -100, -200 and -300 yOffset values. Since the x-coordinate always starts at zero the xOffset is actually always the x-coordinate where the shape starts.

Finally, as mentioned,

AddSubShape(shapeName,compass)
which is required for layoutType = "border".

4.5 Compartments

In the rectangular UML element representation you can find compartments beneath the top compartment showing the name and a few other things. Usually those compartments show attributes and methods. But you can also list anything else inside those compartments. In Shape Script you can create compartments for child elements of an element. For the main element you can have a shape main section. In that case you need to call DrawNativeShape or you will not see any compartments. Now for each child element the Shape Script part shape ChildElement is being processed. Here you can assign a compartment name using SetCompartmentName which will be a grouping criterium. By calling AddCompartmentText you can add the name (and additional information) to the compartment.

shape ChildElement { <block> }
defines the script to be executed for any child element.
SetCompartmentName(name)
will group a subsequent AppendCompartmentText under the specified name. The list of unique compartment names will appear
AppendCompartmentText(text)
will add the supplied text under the according compartment.

To make an example we assume the following element structure where main contains two differently stereotyped elements.

The diagram representation if achieved with the following script12.

1 shape ChildElement { // add Child Compartments to the parent.
2   if(HasProperty("stereotype", "part")) {
3     SetCompartmentName("Parts");
4   }
5   else if(HasProperty("stereotype", "mystereotype")) {
6     SetCompartmentName("My Stereotype");
7   }
8   AppendCompartmentText("#NAMe#"); // case insensitive...
9 }

As you can see the compartments are listed according to the found stereotypes part and mystereotype and they appear in the middle of the rendered element. The names (being print using hash-tags) appear left aligned under each compartment.

4.6 Decoration

If you mainly use the rectangular notation for an element but want to show a fancy icon somewhere you can use

decoration <some-unused-name> { <block> }
where <some-unused-name> is an arbitrary name. It is used at no place but should simply describe the shape in one word. However, the name must be unique amongst the declared decorations. <block> is any graphical description as explained previously.

The default decoration will appear top-left of the shape (NW). If you want it to appear somewhere else you need to assign the orientation property inside to one of the valid compass values.

The following Shape Script

 1 decoration triangle {
 2   orientation = "center";
 3   // Draw a triangle for the decoration 
 4   startpath(); 
 5   moveto(0,30); 
 6   lineto(50,100);
 7   lineto(100,0);
 8   endpath();
 9   setfillcolor(153,204,255);
10   fillandstrokepath();
11 }
12 
13 decoration a {
14   orientation = "NW";
15   setfillcolor(50, 100, 200);
16   rectangle(0,0,100,100);
17   print("test");
18 }
19 decoration b {
20   orientation = "N";
21   setfillcolor(50, 100, 200);
22   rectangle(0,0,100,100);
23 }
24 decoration c {
25   orientation = "NE";
26   setfillcolor(50, 100, 200);
27   rectangle(0,0,100,100);
28 }
29 decoration d {
30   orientation = "E";
31   setfillcolor(50, 100, 200);
32   rectangle(0,0,100,100);
33 }
34 decoration e {
35   orientation = "SE";
36   setfillcolor(50, 100, 200);
37   rectangle(0,0,100,100);
38 }
39 decoration f {
40   orientation = "S";
41   setfillcolor(50, 100, 200);
42   rectangle(0,0,100,100);
43 }
44 decoration g {
45   orientation = "SW";
46   setfillcolor(50, 100, 200);
47   rectangle(0,0,100,100);
48 }
49 decoration h {
50   orientation = "W";
51   setfillcolor(50, 100, 200);
52   rectangle(-50,-50,150,150);
53 }

displays as

You can see that the 100² units are located at fixed positions inside the main shape (the picture shows an enlarged class element). They do not scale with the element. Also if you print text in decorations the font is that being used inside elements in general. There is only one font setting in Enterprise Architect for general use, one for an element in general and one per diagram use. You can not have multiple fonts in one displayed element.

4.7 Labels

It is possible to place labels along with an element oriented at major compass positions. To actually create a label you use

shape label { <block> }
which will contain the instructions to create the label. Usually you will use Print statements and/or graphic methods.
SetOrigin(relativeTo,xOffset,yOffset)
will tell where to actually place the label. relativeTo must be a string constant being one of N, NE, E, SE, S, SW, W, NW or CENTER. The Offset values are measured in screen pixels and can be negative.

4.8 Miscellaneous

Here are a number of methods which will not group elsewhere:

DefSize(width,height)
sets the default size of the shape when it’s initially placed on a diagram or when Alt-Z is used to set the default size. Both parameters define screen pixels. The default for a class element is about 100|80.
DrawParentShape()
is similar to DrawNativeShape. This is used when referring to non-UML shapes defined in MDGs where it will use the shape of the element’s parent from which it is derived. For non-derived elements it will simply behave like DrawNativeShape.
DrawCompositeDiagram()
will enable the rendering of a possibly existing composite diagram.
Image(imageName,left,top,right,bottom)
will place a scaled image inside the units of the shape specified by left, top, right and bottom.