The Curl® language Shapes package is designed
to help you create diagrams, charts, graphs and other 2D
graphics. It offers an approach to layout of objects in a
container that is better suited to building a graph or diagram
than the GUI toolkit graphical layout approach.
When you put
Graphic objects into a graphic container,
the actual appearance of the result on a display device, such as a
monitor or printer, is controlled by the Curl layout system. This
system does the best job it can of fitting all the specified
contents into the container, considering any preferred sizes
supplied for the objects, and the display space available. See
Elastics and Page Layout
for more information on how the Curl layout system positions
objects.
This approach is very useful in situations where your layout needs
to adapt to unpredictable display conditions, such as fitting
components of a GUI into a browser window. It is less useful when
you need precise control over the size and placement of objects,
such as positioning data points in a line graph.
The Shapes package gives you the precision and control without the
programming effort and complexity involved in using 2D
rendering. Shapes also supports GUI toolkit event handling, which
means you can create diagrams that use animation and respond to
mouse events.
The following table lists some major differences between
Graphic objects and
Shape objects.
| Graphic Objects | Shapes |
Specify size preferences for an object. The object can stretch
or shrink to fit. | Specify object size exactly. The object is clipped if it does
not fit. |
Position objects by a process of layout negotiation. | Position objects by applying transformations. |
| You cannot move an object's origin with respect to its
dimensions. |
No rotation or scaling. | Supports rotation and scaling. |
The Curl language also provides the
Charts package, which is built on the Shapes
package, and provides specialized features for creating charts and
graphs of data in a
RecordSet.
The package provides a number of basic shapes, such as rectangle
and ellipse, and an arrow shape to provide various types of
specialized lines between shapes. The classes
PathShape
and
RegionShape enable you to create custom graphic
Shapes. You can also create subclasses of
Shape to
provide customized behavior or appearance.
When you create a
Shape, you specify its
dimensions. Sometimes, you specify these dimensions as a
GRect. Other times, you specify two end points of a line, or
perhaps a
Region that contains a complex
polygon. Whatever form you use to describe the shape's dimensions,
the dimensions are interpreted relative to the Shape's origin.
For instance, a
GRect can describe the dimensions of a
RectangleShape, and the shape's offset from its own
origin. You can then move the
Shape, including its
origin, to whatever position you want by modifying the
Shape's transformation. You can modify the transformation by
providing arguments to the
Shape's constructor or by
using one of the methods provided by the
Shape class.
Shapes must be placed in a
Graphic object in order to be
visible. In practice,
ShapeBox and
Canvas are
both suitable graphic containers for
Shapes. Both objects
inherit from
ShapeRoot.
ShapeRoot inherits from
ShapeContainerBase, and is an abstract base class for any
Graphical object that can anchor shape objects in a graphical
hierarchy. Note that
Canvas and
ShapeRoot are
not themselves shape objects, and do not inherit from
Shape.
Canvas has a very simple layout. You must specify a size
for the
Canvas, and it does not change its size in
response to the size of its contents, or change the size of its
contents. Its contents are always exactly where you place them,
and exactly the size you specify them to be.
ShapeBox uses the desired size of its contents to
determine its own size preferences for GUI layout, and it notifies
its contents of the size allocated to the
ShapeBox during
layout. For
Charts, this
notification results in the Chart occupying the allocated size,
which produces GUI-like layout; other shapes ignore it.
You can create a
ShapeBox explicitly, but often you do
not have to. If you attempt to insert a shape in a graphical
hierarchy, it is automatically wrapped in a
ShapeBox.
| Example:
RectangleShape automatically wrapped in
ShapeBox |
 |
{import * from CURL.GUI.SHAPES}
{value
{RectangleShape
{GRect 3.5cm, 3.5cm, 2.5cm, 2.5cm},
color = "wheat"
}
}
| |
ShapeBox uses the concept of layout bounds to interact
with its contents. Layout bounds are different from ordinary
bounds, as returned by
Shape.get-own-bounds, in that they
are not an absolute constraint.
Shape.get-own-bounds must
return a rectangle that includes all areas that may be drawn by
the
Shape, which often means that it is somewhat larger
than the specified area of the
Shape. Layout bounds, on
the other hand, are intended to reflect the specified dimensions
of the
Shape; they do not need to contain all drawn
areas.
ShapeBox uses layout bounds instead of bounds to
determine its size because using bounds would result in too much
space around the borders of the
Shape.
You need to understand how these properties of
GRect
define how objects are drawn. Consider a
GRect specified
as follows:
{GRect 3.5cm, 3.5cm, 2.5cm, 2.5cm}
The origin is at its center, and the following figure shows how
these distances relate to the origin.
Note that while all the values specified to the
GRect
constructor are positive, some of the points in 2D space covered
by the resulting object have negative values for their x- or
y-coordinate. The following example shows a
RectangleShape based on this
GRect, placed in a
Canvas.
| Example:
Origin of GRect is at the Center |
 |
{import * from CURL.GUI.SHAPES}
{value
{Canvas width = 8cm, height = 6cm,
{RectangleShape
{GRect 3.5cm, 3.5cm, 2.5cm, 2.5cm},
color = "wheat",
||-- translation = {Distance2d 3.5cm, 2.5cm},
border-color = "black",
border-width = 1px
}
}
}
| |
The origin of the
Canvas is at its upper-left corner. The
origin of the
GRect is at its center. As a result, you
see only the lower-right quadrant of the
RectangleShape. Uncomment this line and execute the example.
translation = {Distance2d 3.5cm, 2.5cm}
The rectangle is now completely visible because the translation
has moved its origin to the right and down with respect to the
origin of the
Canvas.
{GRect 0cm, 7cm, 0cm, 5cm}
As the following example illustrates, the rectangle is completely
visible, because the entire
GRect is drawn to the right
and down from its origin.
| Example:
Origin of GRect is at the Upper-left Corner |
 |
{import * from CURL.GUI.SHAPES}
{value
{Canvas width = 8cm, height = 6cm,
{RectangleShape
{GRect 0cm, 7cm, 0cm, 5cm},
color = "wheat",
border-color = "black",
border-width = 1px
}
}
}
| |
Finally, consider this example. No part of the
RectangleShape is visible, because the entire
GRect is
drawn to the left and above from its origin. The line of code
that specifies a translation moves the rectangle onto the
Canvas. Uncomment that line and execute the example. Note that
the entire rectangle is visible.
| Example:
Origin of GRect is at the Lower-right Corner |
 |
{import * from CURL.GUI.SHAPES}
{value
{Canvas width = 8cm, height = 6cm,
{RectangleShape
{GRect 7cm, 0cm, 5cm, 0cm},
color = "wheat",
||-- translation = {Distance2d 7cm, 5cm},
border-color = "black",
border-width = 1px
}
}
}
| |
The following example shows a simple diagram created using shapes,
in which two rectangles are joined by an arrow. Notice that the
GRect provided in the constructor sets the dimensions of
each rectangle. The
translation argument to the
constructor positions each rectangle in the
Canvas.
The head and tail of an arrow can both take any of the arrow-head
styles. In this case, both are ArrowStyle.solid, which
results in a double headed arrow.
| Example:
Simple diagram with shapes |
 |
{import * from CURL.GUI.SHAPES}
{value
let rect-left:RectangleShape =
{RectangleShape
{GRect 0cm, 1cm, 0cm, 1cm},
color = FillPattern.wheat,
translation = {Distance2d .5cm, .5cm}
}
let rect-right:RectangleShape =
{RectangleShape
{GRect 0cm, 1cm, 0cm, 1cm},
color = FillPattern.wheat,
translation = {Distance2d 3.5cm, .5cm}
}
let arr:ArrowShape =
{ArrowShape
{Distance2d 1.5cm, 1cm},
{Distance2d 3.5cm, 1cm},
arrow-body-width = 1px,
arrow-head-width = 11px,
arrow-tail-width = 11px,
arrow-head-style = ArrowStyle.solid,
arrow-tail-style = ArrowStyle.solid
}
{Canvas
height = 2cm,
rect-left,
arr,
rect-right
}
}
| |
The next example is similar, but shows some additional features of
shapes. Instead of positioning the ellipses with arguments to
their constructors, this example uses the method
Shape.apply-translation.
Notice also that because the
Canvas class inherits
AntialiasMixin, the Curl runtime can use antialiasing
when rendering objects in a
Canvas on the screen. For a
Shape contained in a
ShapeBox instead of a
Canvas, consider using
AntialiasedFrame as you
would for any Graphical hierarchy. Antialiasing can have a
negative impact on performance, so you need to use the property
AntialiasMixin.factor to control whether
Canvas
uses antialiasing, and the degree of oversampling provided. This
example uses antialiasing to improve the on-screen appearance of
the curved shapes.
In this example, both ends of the arrow use ArrowStyle.none and the resulting arrow looks like a line.
| Example:
Simple diagram with ellipses |
 |
{import * from CURL.GUI.SHAPES}
{let ellipse-left:EllipseShape =
{EllipseShape
{GRect 0cm, 1cm, 0cm, 1cm},
color = FillPattern.wheat,
border-color = FillPattern.maroon,
border-width = 1px
}
}
{let ellipse-right:EllipseShape =
{EllipseShape
{GRect 0cm, 1cm, 0cm, 2cm},
color = FillPattern.wheat,
border-color = FillPattern.maroon,
border-width = 1px
}
}
{let arr:ArrowShape =
{ArrowShape
{Distance2d 1.5cm, 1cm},
{Distance2d 3.5cm, 3.5cm},
arrow-body-width = 1px,
arrow-head-width = 10px,
arrow-head-style = ArrowStyle.none,
arrow-tail-style = ArrowStyle.none,
color = FillPattern.brown
}
}
{ellipse-left.apply-translation .5cm, .5cm}
{ellipse-right.apply-translation 3.5cm, 2.5cm}
{Canvas
factor = AntialiasFactor.high,
border-width = 1px,
ellipse-left,
arr,
ellipse-right
}
| |
ShapeGroup is a shape that has no visual representation,
and exists only to act as a container for other shapes. All shapes
can act as containers for other shapes, but
ShapeGroup is
useful when you want to group shapes in a container that does not
draw anything itself. Setting nonlocal options on a
ShapeGroup provides a useful way to set values for all child
shapes in the group.
This example uses
ShapeGroup to group the bars of a
bar chart. It also shows that the Shapes package uses hierarchical
transformation. Hierarchical transformation means that the
transformation applied to a shape when it is drawn is the
composite of all of its parents' transformations with its own. In
this case, the translation set on each rectangle positions the
rectangles in a horizontal row. The translation applied to the
parent
ShapeGroup moves the row of rectangles into the
proper position in the
Canvas.
Try changing the
y coordinate of the
ShapeGroup translation from
7cm to
6cm,
then execute the modified example. Note that all of the bars are
repositioned by 1cm, and the bars retain their positions relative
to each other.
| Example:
Bar chart using ShapeGroup |
 |
{import * from CURL.GUI.SHAPES}
{Canvas
width = 9cm,
height = 8cm,
{ShapeGroup
translation = {Distance2d 0cm, 7cm},
{RectangleShape
{GRect 0cm, 1cm, 1cm, 0cm},
color = "#006968",
translation = {Distance2d (1.1cm * 1), 0cm}
},
{RectangleShape
{GRect 0cm, 1cm, 5cm, 0cm},
color = "#006968",
translation = {Distance2d (1.1cm * 3), 0cm}
},
{RectangleShape
{GRect 0cm, 1cm, 3cm, 0cm},
color = "#006968",
translation = {Distance2d (1.1cm * 5), 0cm}
},
{RectangleShape
{GRect 0cm, 1cm, 2cm, 0cm},
color = "#2462a2",
translation = {Distance2d (1.1cm * 2), 0cm}
},
{RectangleShape
{GRect 0cm, 1cm, 4cm, 0cm},
color = "#2462a2",
translation = {Distance2d (1.1cm * 4), 0cm}
},
{RectangleShape
{GRect 0cm, 1cm, 5cm, 0cm},
color = "#2462a2",
translation = {Distance2d (1.1cm * 6), 0cm}
}
}
}
| |
GraphicShape enables you to embed
Graphic
objects into a
Shape hierarchy. Graphic objects include
the containers, controls, and graphics of the GUI toolkit. You
should note that because graphic objects do not support the
rotation and scaling transformations that Shapes supports, a
graphic ignores any rotation or scaling applied to a shape that
contains it. This approach is sometimes called "billboarding." You
can construct a
GraphicShape explicitly, but you do not
need to, because any graphic you add to a
Shape is
implicitly cast to
GraphicShape.
The following example embeds a
TextFlowBox in an
EllipseShape. Because the
TextFlowBox is a child of the
ellipse, the translation applied to the ellipse is also applied to
the text. However, the rotation applied to the ellipse is not.
A Graphic in a shape also picks up values of nonlocal options from
its parent. In this example, if you comment the line:
color = FillPattern.black
The result displays the text in a way that is not possible using
either the GUI toolkit or the Shapes package alone. The
TextFlowBox provides a way to include rich text, which is not
possible with
TextShape, and and the rotated ellipse adds
visual interest that the GUI toolkit cannot otherwise provide.
| Example:
Adding a Graphic to a Shape |
 |
{import * from CURL.GUI.SHAPES}
{let txt:TextFlowBox =
{TextFlowBox
width = 2.7cm,
horigin = "center",
vorigin = "center",
{paragraph
paragraph-justify = "center",
This text is contained in a
{monospace TextFlowBox}, which is a
{monospace Graphic} object.
},
color = FillPattern.black
}
}
{let ell:EllipseShape =
{EllipseShape
{GRect 2cm, 2cm, 2.5cm, 2.5cm},
color = "#ddffff",
font-size = 11pt
}
}
{value
{ell.add txt}
{ell.apply-translation 2.5cm, 2.5cm}
{ell.apply-rotation 45deg}
{Canvas
width = 5cm, height = 5cm,
ell
}
}
| |
Copyright © 1998-2007 Sumisho Computer Systems Corp.
All rights reserved.
Curl, the Curl logo, Surge, and the Surge logo are trademarks of Sumisho
Computer Systems Corp. that are registered in the United States. Surge
Lab, the Surge Lab logo, and the Surge Lab Visual Layout Editor (VLE)
logo are trademarks of Sumisho Computer Systems Corp.