Java Programming Notes # 2220
- Preface
- General
background information- What is a path element?
- The graphic output from the program
- The SVG graphics library
- The moveto command
- Working with straight lines
- Working with text
- Working with Bezier curves
- Working with elliptical arcs
- Preview
- Discussion and
sample code- Description of the program
- The beginning of the class named Svg12
- Draw a diagonal line
- Draw the light blue grid lines
- Label the grid
- Draw the red and blue triangle
- Draw three cubic Bezier curves
- Draw a quadratic Bezier curve
- Draw six elliptical arcs
- Run the program
- Summary
- What’s next?
- Resources
- Complete program
listing - Copyright
- About the author
Preface
General
Although Bézier is the proper spelling of Pierre Bézier’s name,
the use of the special character as the second character in the name makes it
very difficult to compose text using a standard computer keyboard. The name
will appear dozens of times in this lesson.
Therefore, to make it easier to compose the remainder of this tutorial, I will
take the liberty of spelling it Bezier.
Part of a series
This lesson is part of a series (see Resources)
with the following major objectives:
- To teach you how to write Java code that will produce SVG/XML output,
which can be rendered in graphic form by an SVG graphics engine such as
Firefox 1.5. - To teach you how to write servlets that will produce SVG/XML output,
which can be rendered in graphic form by an SVG-capable browser such as
Firefox 1.5.
What you have learned
In previous lessons, you have learned:
- How to write Java code that will deposit SVG/XML code into an
output file that I refer to as an SVG file. - How to write Java code that will deposit in-line SVG code
into an XHTML file. - How to write Java servlet code that will deposit in-line SVG
code into XHTML code in the servlet’s output data stream. - How to write Java code that will create an output XHTML file
that references an external SVG file. - How to write Java servlet code that will create an output
XHTML data stream that references an external SVG file. - How to write a Java/SVG graphics library that removes much of
the pain from writing Java code to produce SVG/XML output. - How to program and use many of the features of SVG to produce
rich graphics in an SVG-capable browser window.
The servlet objective has been satisfied
At this point, I believe that I have satisfied the second objective listed
above involving servlets. However, even though I have taught you a lot about the
many features of SVG, there are other important features that I have not yet
covered. Therefore, the first objective listed above has not
yet been
satisfied.
Will assume that you understand SVG/servlet code in
general
Beginning with this lesson, most of the remaining lessons in this series will
assume that once you understand how to write Java code to implement a particular
SVG feature, you will already understand how to incorporate that Java code into
a servlet such that the output data stream from the servlet will deposit that
SVG code into an XHTML data output stream. Consequently, I will have
little to say about servlets in the remaining lessons in the series, and will
concentrate on helping you to understand how to write the Java code necessary to
take advantage of various SVG features. Typically, the sample programs
will produce output SVG files that can be rendered in graphic form by an SVG
graphics engine such as Firefox 1.5.
What you will learn in this lesson
In this lesson you will learn how to write Java code that uses an SVG
graphics library and the SVG path element to efficiently draw grid
lines, geometric shapes, cubic Bezier curves,
quadratic Bezier curves, and elliptical arcs.
An SVG graphics library
In earlier lessons, I taught you how write your own Java
SVG graphics library to eliminate, or at least alleviate the requirement to
write raw XML code or raw JAXP DOM code. The use of the SVG graphics library
makes it possible for you to produce SVG output simply by making typical Java
method calls. I updated my version of the Java SVG graphics library to
contain several new methods for use in this lesson.
Viewing tip
I recommend that you open another copy of this document in a separate browser
window and use the following links to easily find and view the figures and
listings while you are reading about them.
Figures
- Figure 1. The graphic output from the program.
- Figure 2. Sample cubic Bezier curves.
- Figure 3. Sample d attribute value for a
cubic Bezier curve. - Figure 4. Combinations of large-arc-flag and
sweep-flag. - Figure 5. A portion of the SVG/XML code for the
grid. - Figure 6. A text element.
- Figure 7. XML code for the red cubic Bezier
curve. - Figure 8. XML code for the green cubic Bezier
curve. - Figure 9. XML code for the blue cubic Bezier
curve.
Listings
- Listing 1. The beginning of the class named
Svg12. - Listing 2. Draw a diagonal line.
- Listing 3. Draw the light blue grid lines.
- Listing 4. The makePath method.
- Listing 5. The makeGridString method.
- Listing 6. Label the grid.
- Listing 7. The makeText method.
- Listing 8. Draw the red and blue triangle.
- Listing 9. Draw the red polyline.
- Listing 10. Draw the red cubic Bezier curve.
- Listing 11. Draw the green cubic Bezier curve.
- Listing 12. Draw the blue cubic Bezier curve.
- Listing 13. The quadratic Bezier curve.
- Listing 14. An elliptical arc with no
rotation. - Listing 15. An elliptical arc with a 45-degree
rotation. - Listing 16. Four combinations of
large-arc-flag and sweep-flag. - Listing 17. Program code for Svg012
Supplementary material
I recommend that you also study the other lessons in my extensive collection
of online Java tutorials. You will find a consolidated index at
www.DickBaldwin.com.
General background information
What is a path element?
Paraphrasing the information in the SVG documentation, an SVG path represents
the outline of a shape. The shape can be filled, stroked, used as a
clipping path, or any combination of the three. It can also be used to
cause text to be positioned on curving lines, which will be the topic for a
future lesson.
A path is described using the concept of a current point. In an analogy
with drawing on paper, the current point can be thought of as the current
location of the pen. The position of the pen can be changed, and the outline of
a shape can be traced by dragging the pen in either straight lines or curves.
The shape can be either open or closed.
Paths represent the geometry of the outline of a shape, defined in terms of
the following commands:
- moveto (set a
new current point) - lineto (draw a
straight line) - curve (draw a
curve using a cubic or quadratic Bezier curve or an elliptical arc) - closepath
(close the current shape by drawing a straight line to the last
moveto point)
A path is defined in SVG using the path
element.
A path element can have a large number of optional attributes.
It appears from the documentation that all but two are common attributes that
can be applied to many different elements such as the attributes named
transform, style, etc.
It also appears from the documentation that there are at least two attributes
that are specific to a path element:
- d="path data"
- pathLength = "number"
A required data set
The d attribute is required while the pathLength attribute is
optional. The value for the d
attribute must be a data set that describes the path. In other words, the
data in the data set is the definition of the outline of the shape.
In summary, a path is defined by defining a path
element, which contains a d attribute, where the d-attribute value contains the
moveto, lineto,
curve, and
closepath commands and the
associated coordinate values necessary to define the outline of the shape.
The syntax of the path data set is designed to minimize download time and
associated bandwidth
requirements for downloading and drawing the path. The following rules generally describe the syntax of the
value of the d attribute:
- For download efficiency, all instructions (commands) are expressed
using a single character. For example, a
moveto command is
expressed as either M or m. - Superfluous white space and separators such as commas can be eliminated.
For example, "M 100 100 L 200 200" contains unnecessary spaces and
could be expressed as "M100 100L200 200". - The command letter can be eliminated on subsequent commands if the same
command is used multiple times in a row. For example, you can drop the
second "L" in "M 100 200 L 200 100 L -100 -200" and use "M 100 200
L 200 100 -100 -200" instead. - Relative versions of all commands are available (uppercase means
absolute coordinates, lowercase means relative coordinates). - For the relative versions of the commands, all coordinate values are
relative to the current point at the start of the command. - Alternate forms of the lineto
command are
available to optimize the special cases of horizontal and vertical lines. - Alternate forms of the
curve command are available to optimize the special cases where the
first control point on the current segment can be determined automatically from
the last control point on the previous segment. - The path data syntax is a prefix notation (commands are followed by
parameters). - The only allowable non-numeric characters for numeric parameter values
are the minus ("-") and period (".") characters. No other delimiter characters are allowed
within the parameter value. (For example, the following is
an invalid numeric value in path data: "13,000.56".)
The commands
If I counted correctly, there are twenty single-character commands that fall
in four general categories. The four categories and their single character
commands are given below:
- moveto: M or m
- closepath: Z or z
- lineto
- L or l (general lineto, [note the lowercase L])
- H or h (horizontal lineto)
- V or v (vertical lineto)
- curve:
- Cubic Bezier: C, c, S, and s (will
explain in detail later) - Quadratic Bezier: Q, q, T, and t
(will explain in detail later) - Elliptical arc: A and a
(will explain in detail later)
- Cubic Bezier: C, c, S, and s (will
Recall that the uppercase version of each command indicates that the coordinate values
following the command are to be interpreted as absolute values and the lowercase version
of the command indicates that the coordinate values following the command are to be
interpreted as being
relative to "the current point at the start of the command."
The information in the above list is provided mainly to give you an overview.
I will discuss the different categories and their commands in much more detail later
in this lesson.
The graphic output from the program
Figure 1 shows the graphic output that is produced by the program that I will
present and explain in this lesson.
Figure 1. The graphic output from the program.
|
Figure 1 illustrates a diagonal line, grid lines that give the appearance of
graph paper, a simple triangle composed of straight lines, three different cubic
Bezier curves, one quadratic Bezier curve, and six elliptical arcs. Each
of the shapes in Figure 1 was designed to illustrate one or more
features of the SVG path element. I will refer back to Figure 1 frequently throughout the remainder of this
lesson.
The SVG graphics library
Throughout most of the earlier lessons in this series, I have been using a
Java/SVG
graphics library of my own design to make it easier to generate the detailed
SVG/XML
code required to render SVG graphics in an SVG-capable graphics engine. In several
previous lessons, I
have updated the library to add new functionality, and this lesson is no
exception.
I added the following new methods to my Java SVG graphics library for this lesson.
- makePath
- makeGridString
- makeText
I will present and explain the code
for these methods later. At this point, however, we are still
viewing the graphics process from a somewhat higher level and we don’t need to
know the details of the code.
The moveto command
The moveto commands (M or m)
establish a new current point. The effect is as if the "pen" were lifted from
the paper and moved to a new location where it is then dropped back onto the
paper. A path data segment must begin with a moveto command. Subsequent
moveto commands represent the start of a new sub-path. (Note,
however, that I don’t deal with multiple sub-paths in this lesson.)
|
The moveto command must be followed by a pair of coordinate values that
specify the new current point. Depending on whether the command is an
uppercase M or a lowercase m, the coordinate values may be
either absolute or relative. The moveto command starts a new
sub-path at the location specified by the coordinate values.
Working with straight lines
The lineto command
The various lineto commands draw straight lines from the current point
to a new point that is specified by a pair of coordinate values following the
command.
The L or l lineto command
This command draws a line from the current point to the specified
coordinate which becomes the new current point. As usual, uppercase
L indicates that absolute coordinates will follow.
Lowercase l indicates that relative coordinates will follow.
A number of coordinate pairs may be specified to draw a polyline (not to
be confused with a polyline element). At the end
of the command, the new current point is set to the final set of coordinates
provided.
This is a special case of the lineto command that requires only a
single coordinate value following the command. The single coordinate value
is a new horizontal coordinate. This command draws a horizontal line from
the current point to the new horizontal coordinate at the same vertical
coordinate. (This command was used to draw the horizontal lines in the
blue background grid in Figure 1.)
The V or v lineto command
This is another special case of the lineto command that requires only
a single coordinate value following the command. The single coordinate
value is a new vertical coordinate. This command draws a vertical line
from the current point to the new vertical coordinate at the same horizontal
coordinate. (This command was used to draw the vertical lines in the
blue background grid in Figure 1.)
The closepath command
The closepath command (Z or z)
ends the current sub-path and causes an automatic straight line to be drawn from
the current point to the initial point of the current sub-path.
If a closepath is followed immediately by a moveto, then the
moveto identifies the start point of the next sub-path. If a closepath
is followed immediately by any other command, then the next sub-path starts at
the same initial point as the current sub-path.
The light blue grid pattern
The first thing that I will call your attention to in Figure 1 is the blue
grid of straight horizontal and vertical lines that causes the image to resemble a sheet of graph paper.
Why use the path element to draw straight lines?
You learned about the line and polyline elements in earlier
lessons in this series (see Resources).
The reality is that the use of the path element to draw straight lines is
conceptually more difficult than the use of the line element to draw straight lines. This may cause you to wonder
why you would ever use the path element to draw straight lines. The
reason is that in some cases, (such as the drawing of the background grid in
Figure 1), the use of the path element can significantly reduce the
size of the XML data download and the corresponding bandwidth requirements.
XML data required for a line element
Note the light blue diagonal line in Figure 1. This line was drawn
using a line element, and its only purpose for being in Figure 1 is to
illustrate the improved download efficiency of the path element relative
to the line element in
certain cases.
The XML code required to draw this line and to set its color to light blue
consisted of a total of 74 characters. This total would not
change significantly with changes in the length or the orientation of the line.
Downloading 74 characters to draw a line is not a problem if you are drawing only a few lines. However,
the light blue background grid in Figure 1 contains 45 horizontal lines and 45
vertical lines for a total of 90 lines. If each of those 90 lines had been
drawn and colored using an individual line element, approximately 6660
XML characters would have been required to draw all 90 lines.
Using a path element instead
As you will
see later, I developed a method named makeGridString that uses
special horizontal and
vertical lineto commands of the path element to reduce the
download size and attendant bandwidth requirements for drawings that contain a
large number of horizontal and vertical lines in a uniform grid pattern.
This method returns a string, which is used as the value for the attribute named
d for a path element that draws the lines described by the
contents of the string. Although the code to accomplish this is somewhat
more complicated than would be the case for drawing the grid using simple
line elements, the download size and bandwidth requirements are
significantly reduced with the use of the path element.
A significant reduction in XML data volume
|
If I counted correctly, the XML code required to draw and color
all 90 of the light blue horizontal and vertical grid lines in Figure 1 using a
path element consists of
about 1150 characters. Thus, the use of the path
element to draw the light blue background grid reduced the amount of XML data by
almost a factor of 6 relative to the amount required to draw and color the grid using
individual
line elements.
The red and blue triangle
Next, I want to call your attention to the red triangle with the blue border
in the upper left corner of Figure 1. I used the moveto, lineto,
and closepath commands to draw this triangle. In fact, this program
uses one absolute moveto command, one absolute lineto command, and
one relative lineto command plus the closepath command to draw the triangle. Once the triangle
had been drawn, separate calls to the setAttribute method were made to
fill the triangle with red and to give it a blue border.
The triangle was drawn solely to illustrate the use of the path
element and the commands listed above. In reality, it would probably have
been easier to use simple line elements to draw this triangle than to
draw it using the path element.
Working with text
The use of text in SVG is an extensive topic, one facet of which is to create
a curved path and then to render the text along that path instead of simply
rendering it along a straight line. Text will be the primary topic of a
future lesson.
However, I needed to put numeric labels on the graph shown in Figure 1 to make
it easier to read coordinate values off the graph. As you will see later,
I added a makeText method to my SVG graphics library to make this
relatively easy to
accomplish.
Working with Bezier curves
When you need to draw smooth curves to connect points, the path
element has a lot more to offer than just a reduction in the XML data volume,
although it has that to offer also. For this situation, the use of the
path element also makes it possible to do some things relatively easily that
could be very difficult to do otherwise.
|
Cubic Bezier Curves
A Bezier curve is a smooth curve that is used to connect two points.
SVG supports both quadratic and cubic Bezier curves.
Initially, this
discussion will be centered on cubic Bezier curves. Then it will move
to quadratic Bezier curves.
Bezier curves according to Darrel Plant
While describing cubic Bezier curves, Darrel Plant tells us, "Almost any type of curve that
contains one or two changes in direction can be described by a Bézier equation." (See "What’s a Bézier Curve?" in
Resources)
He
goes on to describe how cubic Bezier curves can also be used to describe loops and
other complex shapes.
Sample cubic Bezier curves
Figure 2 shows four different cubic Bezier curves that I created
interactively using the Bezier Curve Demo in
Resources.
Figure 2. Sample cubic Bezier curves.
In each of the individual images in Figure 2, the curved line is the
actual Bezier curve (or Bezier segment). The straight lines connect
the start point, two control points, and the end point that I will explain
in the next section.
|
Drawing a Bezier segment
The locations of four points in coordinate space must be specified to draw a cubic Bezier segment.
There is a start point, two control points, and an end point. The segment is drawn from
the start point to the end point. The locations of the two control points
determine the shape of the curve as it winds its way from the start point to the
end point. Figure 2 shows some good examples of how the locations of
the two control points, relative to the locations of the start point and the end
point impact the shape of the actual Bezier curve or segment.
Special forms of the path element in SVG
With SVG, cubic Bezier segments are drawn using any one of several special forms of
the path element. There are four single-character commands that can be
used in the specification of the locations of the points: C, c, S, and s.
For example, Figure 3 shows the beginning of the XML path element that
produced the red Bezier curve shown immediately down and to the right of the red
and blue triangle in Figure 1.
Figure 3. Sample d attribute value for a cubic
Bezier curve.
<path d="M100,100 C100,50 175,50 175,100 S250,150 250,100" |
The C-command and the S-command are highlighted in boldface red
in Figure 3 to make them easy to for you to spot. (They would not be red in the
actual XML code.)
Either absolute or relative coordinates
|
Once the start point has been established, the coordinates of the other three
points can be specified either in absolute coordinates or in coordinate values
that are relative to the location of the start point. If the uppercase
command is used, the coordinate values are interpreted to be absolute. If
the lowercase command is used, the coordinate values are interpreted to be
relative.
Poly-Bezier curves
The specifications for multiple Bezier segments can be strung together following a Bezier
command to create a poly-Bezier curve. For example, each of the images
shown in Figure 2 is a single Bezier segment. On the other hand, the red
Bezier curve shown near the upper left in Figure 1 consists of two Bezier
segments concatenated end to end and is therefore a poly-Bezier curve.
For the case of poly-Bezier curves, each group of three
coordinate values following the original Bezier segment is interpreted
to represent the specification of a new Bezier segment. The start point for
the new segment is assumed to be the end point from the previous segment, and hence
serves as the first of the four points that are required to specify a cubic
Bezier segment.
Difference between C and S Bezier
commands
Of the two types of Bezier commands, (C and S), C is the more general
of the two. The C command can be used for a single
Bezier segment or for the construction
of a poly-Bezier curve. However, the S command applies only to the
construction of a poly-Bezier curve.
|
When the S command is used to specify the four required points for the next
segment, only the second control point and the end point are actually specified. As is always the case for a poly-Bezier curve, the start point
for the next segment is assumed to be the end point from the
previous segment. However, when an S command is used, the first control point for the new segment is
assumed to be the reflection of the second control point from the previous
segment, leaving only two points that must actually be specified for the new
segment.
Quadratic Bezier curves
A quadratic Bezier segment is defined by a start point, an end point, and
only one
control point. As with the cubic Bezier segment, there are four commands that are used to specify the locations of the three required points:
Q, q, T and t.
The uppercase commands imply that the coordinate values are absolute while the
lowercase commands imply that the coordinate values are relative to the location
of the start point.
The Q and q commands for the quadratic segment have essentially
the same meaning as the C and c commands for the cubic segment.
The T and t commands for a quadratic segment have essentially the
same meaning as the S and s commands for the cubic segment.
The Bezier curves in Figure 1
Figure 1 contains three cubic Bezier curves colored red, green, and blue, and one quadratic Bezier curve
colored red.
I will have more to say about each of these curves in conjunction with the
explanation of the Java code that produced them.
Working with elliptical arcs
An elliptical arc command is perhaps the most complex of all of the curve
commands. Each elliptical arc command requires seven parameters in the
following order:
- rx specifies the horizontal radius of the ellipse
- ry specifies the vertical radius of the ellipse
- x-axis-rotation specifies the rotation of the ellipse relative to
the current coordinate system - large-arc-flag (will discuss in detail later)
- sweep-flag (will discuss in detail later)
- x specifies the horizontal location of the end point of the arc
- y specifies the vertical location of the end point of the arc
Behavior of the elliptical arc command
The command draws an elliptical arc beginning at the current point and ending
at the location specified by x and y.
The coordinates of the center of the ellipse are calculated automatically to
satisfy the constraints imposed by the other parameters. The
large-arc-flag and sweep-flag parameters contribute to
the automatic calculations and help determine how the arc is drawn.
The large-arc-flag and sweep-flag
parameters
Each of the parameters named large-arc-flag and sweep-flag can
have a value of 0 or 1. This results in four possible combinations of
these two parameters. Thus, there are actually four different arcs (two
different ellipses, each with two different arc sweeps), only one of which
can satisfy the
constraints imposed by a specific combination of these two parameters.
The rules for drawing elliptical arcs
The combination of
large-arc-flag and sweep-flag indicates which one of
the four arcs will be drawn according to the following rules:
- Of the four candidate arcs, two represent an arc sweep of greater than
or equal to 180 degrees. This is a large arc. The other
two candidates
represent an arc sweep of less than or equal to 180 degrees. This is a
small arc.
If the value of large-arc-flag is 1, one of the two
larger arc sweeps will be chosen.
If the value of large-arc-flag is 0, one of the smaller arc
sweeps will be chosen,
- The arc is drawn by evaluating the following equations where cx
and cy are the coordinates of the center of the ellipse:
x = cx + rx*cos(theta)
y =cy + ry*sin(theta)
If the value of sweep-flag is 1, then the equations are
evaluated such that theta starts at an angle corresponding to the current
point and increases positively until the arc reaches the end point.
If the value of sweep-flag is 0, the equations are
evaluated such that theta starts at an angle value corresponding to the
current point and decreases until the arc reaches the end point.
Elliptical arcs in the graphical output
Six elliptical arcs are shown in Figure 1. The yellow arc with the red
border and the red arc with the yellow border illustrate the effect of the
x-axis-rotation parameter on the appearance of the arc. The yellow arc
with the red border was drawn with an x-axis-rotation parameter value of
0. The red arc with the yellow border was drawn with an x-axis-rotation
parameter value of 45 degrees.
In both cases, the start point was at the bottom of the straight-line segment
on the left and the end point was at the top of the straight-line segment.
Also in both cases, the value of
large-arc-flag was 1 and the value of sweep-flag
was 0. Thus, as explained above, one of the two
larger arc sweeps was chosen in each case. Also as explained above, the equations
were evaluated such that theta started at an angle value corresponding to the
current point and decreased until the arc reached the end point.
Combinations of large-arc-flag and
sweep-flag
Each of the other four elliptical arcs at the bottom of Figure 1 represents
one of the four possible combinations of large-arc-flag and
sweep-flag with an x-axis-rotation parameter value
of 0. Note that in each case, the horizontal and vertical radii were
the same, causing the elliptical arc to actually be a circular arc. Also
note that in each case, the start point of the elliptical arc was at the bottom
of the straight-line segment.
Figure 4 shows the different combinations of these two parameters
for each elliptical arc going from left to right across Figure 1.
Hopefully a comparison of these parameter values with the images in Figure 1
will help you to understand the impact of each of the two parameters on the
appearance of the elliptical arc.
Figure 4. Combinations of large-arc-flag and
sweep-flag.
Case 1: Fill color = red, border = blue large-arc-flag = 1 sweep-flag = 0 Case 2: Fill color = green, border = red large-arc-flag = 1 sweep-flag = 1 Case 3: Fill color = blue, border = red large-arc-flag = 0 sweep-flag = 0 Case 4: Fill color = yellow, border = blue large-arc-flag = 0 sweep-flag = 1 |
The large-arc flag
The behavior of the
large-arc-flag
is pretty straightforward and easy to understand. Basically the value of the large-arc-flag
determines whether the large portion or the small portion of the ellipse will be
displayed. If the
large-arc-flag has a value of 1, the large portion of the ellipse will be
displayed. If the
large-arc-flag has a value of 0, the small portion will be displayed.
The sweep-flag
The behavior of the
sweep-flag is not so straightforward, at least not for me anyway.
The value of the sweep-flag
determines which side of the straight-line segment the portion selected by the
large-arc-flag will be
displayed on. However, the difference between right and left in this case
depends on the locations of the start point and the end point.
Although I haven’t worked out the math, I feel reasonably confident in saying
that if you stand at the start point and look toward the end point, the arc will
be displayed on your right if the
sweep-flag
has a value of 0, and will be displayed on your left if the
sweep-flag has a value of 1.
Preview
I will present and explain a program named Svg12 in this lesson.
The primary purpose of this program is to teach you how to use an SVG path
element to draw straight lines, Bezier curves, and elliptical arcs. The program produces a single SVG file as its output. The
graphic output shown in Figure 1 was produced by rendering the SVG output file using
Firefox 1.5.
As is my custom, I will present and explain the program in fragments.
You can view a listing of the entire program in Listing 17 near the end of the
lesson.
Discussion
and sample code
Description of the program
Overall, the program creates a DOM tree
that describes the graphic image shown in Figure 1 in SVG format and writes it out into an
SVG
file named Svg12.svg. The output file produced by this program can be rendered by
loading it into Firefox 1.5.
The path element
The main purpose of this program is to demonstrate the use of a
Java method of my own design named makePath to create the SVG/XML output
code necessary to produce the graphic elements shown in Figure 1. A
secondary purpose is to demonstrate the use of a second Java method of my own
design named makeGridString.
I updated my SVG graphics library during the writing of this program to add several new
methods, including makePath and makeGridString, and to update some of the default values for existing methods
as well.
A diagonal blue line
The program uses a line element to draw a diagonal line from the top
left corner of Figure 1 to the lower right corner. The purpose of this
line was to provide information about the line element that could be used
to calculate the improvement in download
efficiency provided by the path element relative to the line
element for certain cases involving straight lines.
A background grid
|
After taking care of all the preliminary stuff that I have explained in
earlier lessons, the program uses the new method named makeGridString
in
conjunction with the new method named
makePath
to draw a light blue background grid that
resembles graph paper. The
makeGridString
method uses the
moveto
command and
special versions of the horizontal and vertical
lineto
command to reduce the
amount of SVG/XML data needed to draw the grid.
As you can see in Figure 1, there are light blue horizontal
and vertical lines every 10th pixel. There are slightly darker blue lines every
50th pixel. There are even darker blue lines every 100th pixel.
The text labels
The program uses a new Java method of my own
design named makeText to place black text labels on the blue lines that mark every 50th pixel.
A red triangle with a blue border
Then the program uses the moveto,
lineto, and closepath commands to draw
a triangle. The setAttribute method is used to fill the triangle
with solid red color and to give it a blue border.
Three cubic Bezier curves
The program draws three cubic Bezier
curves, one red, one green, and one blue, illustrating the effect that the locations of the control points
have on the
shape of the curve.
One quadratic Bezier curve
Then the program overlays a
red quadratic Bezier curve on the blue cubic Bezier curve, illustrating the
difference in the degree to which the cubic and quadratic Bezier curves fit the
same envelope, where the envelope is defined by the start point, the control point(s), and the end point.
Elliptical arc rotation
The program draws two elliptical arc
curves to illustrate the effect of the x-axis-rotation parameter on the appearance
of the elliptical arcs. The yellow arc with the red border has a rotation
value of zero, while the red arc with the yellow border has a rotation value of
45 degrees. Otherwise, the two elliptical arcs were drawn with the same
parameter values.
Combinations of large-arc-flag and
sweep-flag parameters
Finally, the program draws four more elliptical arc curves,
illustrating the difference in shape that results from all four combinations of
the large-arc-flag and
sweep-flag parameters.
Program testing
The program was tested using J2SE 5.0, Firefox v1.5.0.8, and WinXP.
The beginning of the class named Svg12
The class named Svg12 begins in Listing 1.
Listing 1. The beginning of the class named Svg12.
public class Svg12{ public static void main(String[] args){ int width = 450; int height = 450; //Begin by creating a DOM tree that represents the XML // code that will render to produce the image of // interest. //Get the Document object. Document document = SvgGraphics.getDocument(); //Create the root node named svg and append it to the // document. Specify the parent as null for the // special case where the parent is the document. Element svg = SvgGraphics.makeNode( document, null,//parent "svg",//node type new String[]{"xmlns","http://www.w3.org/2000/svg", "version","1.1", "width",""+ (width + 2), "height","" + (height + 2), "position","absolute", "top","0", "left","0", });//end makeNode method //Create a node named g, which will be the parent for // several graphic elements. Pass null for the // reference to the array object for the special case // where the node has no attributes. Element g = SvgGraphics.makeNode(document, svg,//parent "g", null); |
I have explained the material in Listing 1 in previous lessons, so I won’t
repeat that explanation here.
Draw a diagonal line
Listing 2 calls the makeLine method to draw a diagonal line from the
upper left corner to the bottom right corner in Figure 1, and then calls the
setAttribute method to color it blue.
Listing 2. Draw a diagonal line.
Element diagLine = SvgGraphics.makeLine( document,g,0,0,450,450); diagLine.setAttribute("stroke","#8888ff"); |
I explained both of these methods in earlier lessons, and won’t repeat that
explanation here.
Draw the light blue grid lines
Listing 3 calls the makeGridString method followed by the makePath
method and the setAttribute method to draw the light blue grid lines on a
ten-pixel spacing in Figure 1. This is the first of three sets of grid
lines, followed by a set on 50-pixel spacing and a set on 100-pixel spacing.
Listing 3. Draw the light blue grid lines.
//First draw light blue grid lines every 10 pixels. String gridData = SvgGraphics.makeGridString( width,height,10); //Create the path element. Element temp; temp = SvgGraphics.makePath(document,g,gridData); //Set the color to light blue. temp.setAttribute("stroke","#ccccff"); |
Both the makeGridString method and the makePath method are new
to this lesson, so I will put the discussion of the main method on hold
while I explain these two methods. I will explain them in reverse order
because, in effect, the makeGridString method is a helper method for the
makePath method. Therefore, I will explain the makePath
method first.
The makePath method
The makePath method is shown in its entirety in Listing 4.
Listing 4. The makePath method.
static Element makePath(Document document, Element parent, String d){ Element path = (Element)document.createElement("path"); parent.appendChild(path); path.setAttribute("d",d); path.setAttribute("stroke","black"); path.setAttribute("stroke-width","1"); path.setAttribute("fill","none"); return path; }//end makePath |
Let me begin by reviewing what is going on here. At this point in the
program, I am constructing a DOM tree that represents the graphic image shown in
Figure 1. At this point, the objective is to create nodes in the DOM tree
that will later be transformed into elements in the XML output code produced by
the program.
With the exception of the diagonal line and some polylines, all of the shapes in Figure 1 will
be created using an SVG/XML path element. This includes the
background grid lines. Therefore, at this point, I need to create a node
of type path and append it to the Dom tree.
Old stuff
There is nothing new about the first two parameters to the makePath
method: document and parent. Almost all of the
methods in my SvgGraphics library require these parameters and I have explained
their use in earlier lessons. There is also nothing new about the first
statement or the last four statements in the method. I have also explained
these statements in earlier lessons. That leaves only one interesting
statement, and it is highlighted in boldface in Listing 4.
New stuff
In the earlier section titled The
required d attribute, I explained that an SVG path element must have
an attribute named d. I also explained that the value of the d
attribute must be a data set that describes the path. In other words, the
data in the data set is the definition of the outline of the shape represented
by the path.
In the section titled Syntax of the
path data set I explained the rules for constructing the string attribute
value for the d attribute. Briefly, that string must consist of
uppercase and lowercase alphabetic commands and numeric coordinate values.
Establish the value of the attribute named d
After constructing the path node and appending it to the DOM tree, the
makePath method in Listing 4 calls the setAttribute method on the
path node, passing the third parameter to the makePath method as
the only parameter to the setAttribute method. Therefore, the
string contents of the parameter named d will become the attribute value
for the path element when the DOM tree is transformed to XML. The
makePath method will be called extensively in this program to create all
of the different graphic elements shown in Figure 1. The type of graphic
element that is created depends solely on the string contents of the parameter
named d.
Set default attributes and return the path
Finally, the method named makePath returns a reference to the new
path node that was created.
It is also worth noting that by default, the path will be stroked as a black
line (which may be a curve), one pixel in width. If the path
happens to close, it won’t be filled with anything. You can later set new
values for the stroke, stroke-width, and fill attributes if
you aren’t satisfied with the default values.
Set new attribute values
If you refer back to Listing 3, you will see that the returned reference to
the path node is saved in a local variable named temp. Then
the setAttribute method is called on the reference to the object to reset
the stroke to a new color, which is the lightest shade of blue shown in Figure
1.
So where are we?
We are in the process of constructing a path node that
will eventually be transformed into SVG/XML code that will cause the SVG
rendering engine to draw the light blue grid shown in Figure 1. We know
that a call to the makePath method will return a reference to a path
node, for which the definition of the path is determined by the contents of the
String parameter that is passed as the third parameter to the makePath
method.
Referring once again to Listing 3, we see that a reference to a String
object that is stored in a local variable named gridData is passed as the
third parameter to the makePath method. Therefore, we can expect
that the contents of that String object will describe the grid with a
ten-pixel spacing shown in Figure 1. A reference to that String
object is returned from the earlier call to the makeGridString method.
Therefore, it is time for us to take a look at the method named
makeGridString.
The makeGridString method
This is a highly specialized method that is designed to do one thing and one
thing only. However, that one thing is fairly common, so I decided that it
would be worth encapsulating the code into a method and putting the method into
my SVG graphics library.
The makeGridString method is a utility method that is used to construct a string that describes a grid pattern consisting of horizontal and vertical lines
on a specified pixel spacing for a rectangular area of a specified width and a
specified height in pixels.
The string that is returned is intended solely to be used as the data string for a call to the method named
makePath.
The makeGridString method can be viewed in its entirety in Listing 5.
Listing 5. The makeGridString method.
static String makeGridString( int width,int height,int spacing){ //Construct the data string for the vertical lines. String data = "M0,0 "; for(int cnt = 0;cnt < width;cnt += spacing){ data += "v" + height + " M" + cnt + ",0 "; }//end for loop //Add the final vertical line. data += "v" + height + " n"; //Now add the horizontal lines to the data string. data += "M0,0 "; for(int cnt = 0;cnt < height;cnt += spacing){ data += "h" + width + " M0," + cnt + " "; }//end for loop //Add the final horizontal line. data += "h" + width + "n "; return data; }//end makeGridString |
I’m not going to attempt to explain this code to you in detail. Rather,
I will simply refer you back to the material in the sections titled
The moveto command and
Working with straight lines and let
you work through the details on your own. I will point out, however that
this method makes use of the special horizontal and vertical lineto commands
(h and v) in an attempt to reduce the download size and the bandwidth requirements for a drawing containing a large number of horizontal and vertical lines in the grid pattern.
A portion of the XML code for the small grid
Figure 5 shows a portion of the SVG/XML code that was generated by this
program.
Figure 5. A portion of the SVG/XML code for the grid.
<path d="M0,0 v450 M0,0 v450 M10,0 v450 M20,0 v450 M30,0 v450 ... M430,0 v450 M440,0 v450 M0,0 h450 M0,0 h450 M0,10 h450 M0,20 h450 M0,30 h450 ... M0,430 h450 M0,440 h450 " fill="none" stroke="#ccccff" stroke-width="1"/> |
The steps that produced the code
Note that the code in Figure 5 was the result of performing the following
steps:
- Generating the path data string by calling the makeGridString
method shown in Listing 5, passing 450 for the width and 450 for the height
and passing 10 for the spacing. - Creating the path node by calling the makePath method
shown in Listing 4, passing the data string from step 1 above as the third
parameter to the makePath method. The path node was
appended to the DOM tree. - Calling the setAttribute method on the path node from step
2 above in Listing 3 to change the color from the default black to the light
blue defined by the hexadecimal value #ccccff. - Transforming the DOM tree to XML code and storing it in an output file.
The SVG/XML code shown in Figure 5 was taken from that file.
Drawing the darker grids
If you take a look at the complete program in Listing 17, you will see that
essentially the same thing was done two more times. First, the process was
repeated for a spacing of 50 pixels. This new grid was assigned a stroke
color attribute value of “#aaaaff", which was slightly darker than the first
grid described above. The lines in this new grid were drawn on top of the
original grid, causing the grid lines on a spacing of 50 pixels to be a slightly
darker color of blue than the original grid, as shown in Figure 1. It is
important that these two grids be drawn in this order with the darker one on
top. If the darker grid had been drawn first, it would have been covered
by the lighter grid lines and the darker lines would not have been visible.
Finally, the process was repeated one more time for a spacing of 100 pixels
and a stroke color attribute value of “#8888ff", which is an even darker shade
of blue. This resulted in the background grid in Figure 1 consisting of
very light blue grid lines on 10-pixel spacing, slightly darker grid lines on a
50-pixel spacing, and even darker grid lines on a 100-pixel spacing.
Label the grid
As I mentioned earlier, although the topic of drawing text using SVG is an
extensive topic that I plan to cover in a subsequent lesson, I wanted to be able
to label the grid in this program, so I wrote a new method for my SVG graphics
library named makeText.
Listing 6 makes two calls to the makeText method during each iteration
of a for loop to place a text label on the horizontal lines on the 50-pixel
spacing and to place a text label next to the vertical lines on the 50-pixel spacing.
Listing 6. Label the grid.
for(int cnt = 0;cnt < width;cnt += 50){ SvgGraphics.makeText(document,g,0,cnt,""+cnt); SvgGraphics.makeText(document,g,cnt,height,""+cnt); }//end for loop |
The makeText method
The makeText method is shown in its entirety in Listing 7.
Listing 7. The makeText method.
static Element makeText(Document document, Element parent, int x, int y, String text){ Element textNode = (Element)document.createElement("text"); parent.appendChild(textNode); textNode.setAttribute("x",""+x); textNode.setAttribute("y",""+y); textNode.appendChild(document.createTextNode(text)); return textNode; }//end makePath |
This method begins like most of the others in my SVG graphics library by
creating a new text node of type Element and appending it to the DOM tree.
Then it sets the values for the attributes named x and y, which
specify the location of the text.
Create and append the child node containing the
actual text content
Finally, the method calls the createTextNode method to create a new
Text object that encapsulates the actual text. Whereas the location of
the text is specified by the values of two attributes of an element named
text, the text itself is the content of that element. This means that
it must appear in the DOM tree as a node in its own right and not simply as the
value of an attribute.
The makeText method appends the new Text object to the
Element node named text. This may seem a little strange, but
this is the JAXP mechanism for creating a DOM tree that contains nodes with text
content. In other words, in this case, the actual text is encapsulated in
the node of type Text, the node of type Text is a child of the
Element node named text, and the Element node named text is a child of the
node named g.
The corresponding XML code
When the DOM tree is later transformed to XML code, the actual
text will appear as the content of an element named text as shown in
Figure 6.
Figure 6. A text element.
<text x="200" y="450">200</text> |
The XML code produced by the program contains one element like that shown in
Figure 6 for each of the text labels shown in Figure 1. The element shown
in Figure 6 represents the text label with the value of 200 about half way
across the bottom of Figure 1.
The location of the text
Note that by default, the text characters are drawn above and to the right of the specified
location. Also note that the text in Figure 1 is drawn in the default font
and size. I will have more to say about this in a future lesson that
discusses SVG text in detail.
Draw the red and blue triangle
Listing 8 makes another call to the makePath method to draw the red
triangle with the blue border shown in the upper left of Figure 1. Recall
that the makePath method can be called to draw many different kinds of
paths with the actual path specification being contained in a String
object that is passed as the third parameter to the method.
The specification for the path
If you examine the contents of that String object carefully, you will
see that Listing 8 uses the moveto (M), lineto (L and
l), and closepath
(z) commands to cause the makePath method to draw a triangle.
(The commands are highlighted in red boldface in Listing 8 to make them
easier for you to identify. However, they have no color in the actual
program code.)
Listing 8. Draw the red and blue triangle.
Element pathA = SvgGraphics.makePath( document, g,//owner "M 50 10 L 100 10 l -25 40 z"); pathA.setAttribute("stroke","blue"); pathA.setAttribute("stroke-width","3"); pathA.setAttribute("fill","red"); |
Then
Listing 8 calls the setAttribute method three times in succession to color
the triangle red, and to give it a blue border that is three pixels wide.
The actual path commands
|
For illustration purposes, this example uses one absolute moveto command
(M) to establish the starting point for the path, one absolute lineto command
(L) to draw the
line across the top of the triangle, and one relative lineto command (l) to
draw the line on the lower right side of the triangle. Then it uses one closepath command
(z) to draw the line on the lower left side of the triangle
and to close the triangle.
Draw
three cubic Bezier curves
|
Draw the light red polyline
The next task of the program is to draw the red cubic Bezier curve shown
immediately to the right and down from the red triangle in Figure 1.
First, however, the program draws a light red polyline to show the start points,
the control points, and the end points that will be used to draw the Bezier
curve. I like to think of the polyline as forming an envelope whose shape
will determine the shape of the Bezier curve.
Listing 9. Draw the red polyline.
Element polylineB = SvgGraphics.makePolyline( document, g, new int[] {100,100,100,50,175,50,175,100, 175,150,250,150,250,100}); polylineB.setAttribute("stroke","#FF8888"); polylineB.setAttribute("stroke-width","2"); |
How the polyline describes the start, control, and
end points
The Bezier curve will consist of two Bezier segments. The start point
for the first segment is at the beginning of the polyline on the left. The
end point for the first segment is the location where the polyline crosses the
blue grid line with a y-value of 100. The two control points for the first
segment are at the two corners of the polyline on the blue grid line with a
y-value of 50.
The start point for the second Bezier segment coincides with the end
point from the first segment as described above. The two control points for the second Bezier segment are
at the two lower corners of the polyline on the right. The end point for the second
Bezier segment is at the right end of the polyline.
Listing 9 calls the makePolyline method to draw the polyline. I
explained this method in an earlier lesson and should not need to repeat that
explanation here.
Draw the red cubic Bezier curve
Listing 10 makes another call to the makePath method to draw the red
Bezier curve. Once again, the contents of the String object passed
as the third parameter to the makePath method specify the actual path. Also, once
again the path commands are highlighted in red boldface in Listing 10 to make them easy for
you to identify.
Listing 10. Draw the red cubic Bezier curve.
Element pathB = SvgGraphics.makePath(document,g, "M100,100 C100,50 175,50 175,100 S250,150 250,100"); pathB.setAttribute("stroke","red"); pathB.setAttribute("stroke-width","2"); |
Uses absolute coordinate values and the S command
Note that the path specification in Listing 10 uses absolute (uppercase C and
S) coordinate values only and also uses the S command (see the
earlier section titled
Difference between C and S Bezier commands). Consequently, only
two pairs of coordinate values are provided following the S command.
An explanation of the various coordinate values
As mentioned earlier, the red Bezier curve in Figure 1 consists of two Bezier segments.
The start point for the first segment is established by the moveto
command (M). This is followed by a curve command (C)
followed by three pairs of coordinate values. The first two pairs of
coordinate values specify the control points for the first segment. The third pair specifies the
end point for the first segment.
This is followed by the special curve command (S).
Recall that the start point for the second and subsequent Bezier segments is
always assumed to be the end point for the previous segment. This
eliminates the need for one pair of coordinate values following the S
command.
Also recall that this special curve command (S)
assumes that the first control point is the reflection of the second control
point from the previous segment. This eliminates the need for another pair
of coordinate values following the S. Therefore, the S
command in Listing 10 is followed by only two pairs of coordinate values.
The first pair specifies the second control point for the second Bezier segment.
The second pair specifies the end point for the second Bezier segment.
XML output for the red cubic Bezier curve
Figure 7 shows the actual XML output produced by the program for drawing the
red Bezier curve. This will be useful for comparison with other XML
elements later.
Figure 7. XML code for the red cubic Bezier curve.
<path d= "M100,100 C100,50 175,50 175,100 S250,150 250,100" fill="none" stroke="red" stroke-width="2"/> |
Draw the green cubic Bezier curve
The code in Listing 11 draws the light green polyline and the dark green
cubic Bezier curve shown in Figure 1. This Bezier curve also consists of two Bezier segments.
Listing 11. Draw the green cubic Bezier curve.
Element polylineC = SvgGraphics.makePolyline( document, g, new int[] {300,100,320,10,415,50,375,100, 335,150,430,190,450,100}); polylineC.setAttribute("stroke","#88ff88"); polylineC.setAttribute("stroke-width","2"); //Now draw the two Bezier segments that constitute the // Bezier curve. Element pathC = SvgGraphics.makePath(document,g, "M300,100 C320,10 415,50 375,100 " + "335,150 430,190 450,100"); pathC.setAttribute("stroke","green"); pathC.setAttribute("stroke-width","2"); |
Note that this version of the path specification uses absolute coordinates
only but does not use the S command. Therefore, there are six pairs
of coordinate values following the C command. They are, in order:
- First control point for the first segment
- Second control point for the first segment
- End point for first the segment (also start point for the second
segment) - First control point for the second segment
- Second control point for the second segment
- End point for the second segment
XML output for the green cubic Bezier curve
Figure 8 shows the actual XML output produced by the program for drawing the
green Bezier curve.
Figure 8. XML code for the green cubic Bezier
curve.
<path d= "M300,100 C320,10 415,50 375,100 335,150 430,190 450,100" fill="none" stroke="green" stroke-width="2"/> |
If you compare the number of characters in the XML code in Figure 8 with the
number of characters in Figure 7, you will see that this approach, (which
doesn’t take advantage of the S command), is a little less efficient from
a download and bandwidth viewpoint than the approach shown in Figure 7.
The relative importance of this difference in download efficiency will, of
course, depend on the number of Bezier curves that need to be drawn for a
particular graphic.
Draw the blue cubic Bezier curve
The code in Listing 12 draws the light blue triangular-wave polyline that
begins at an x-value of 50 and a y-value of 200 in Figure 1. Then it draws
the blue cubic Bezier curve shown inside that triangular envelope.
Listing 12. Draw the blue cubic Bezier curve.
Element polylineD = SvgGraphics.makePolyline( document, g, new int[] {50,200,100,150,100,150,150,200, 200,250,200,250,250,200}); polylineD.setAttribute("stroke","#8888FF"); polylineD.setAttribute("stroke-width","2"); Element pathD = SvgGraphics.makePath(document,g, "M50,200 c50,-50 50,-50 100,0 50,50 50,50 100,0"); pathD.setAttribute("stroke","blue"); pathD.setAttribute("stroke-width","2"); |
As before, the blue Bezier curve consists of two Bezier segments.
However, there are at least two things about the Bezier curve in Listing 12 that
distinguish it from the previous two Bezier curves shown in Figure 1.
- The two control points for each segment have the same coordinate values.
In other words, they coincide or overlay one another. - After the start point has been established using an absolute moveto
command (M), all subsequent coordinate values are specified in
relative terms using a relative curve command (c).
(As a reminder, for a Bezier segment, relative coordinate values are
always specified relative to the location of the start point for that segment
and not relative to the start point for the Bezier curve as a whole.)
XML code for the blue cubic Bezier curve
The final XML code used to draw the blue cubic Bezier curve is shown in
Figure 9. Note that this example did not take advantage of the relative
s command.
Figure 9. XML code for the blue cubic Bezier curve.
<path d= "M50,200 c50,-50 50,-50 100,0 50,50 50,50 100,0" fill="none" stroke="blue" stroke-width="2"/> |
Even more efficient from a download viewpoint
If you compare Figure 9 with Figure 8, you will see that the use of relative
coordinate values can also improve download efficiency. This is because
the absolute coordinate values are often large values, each one requiring
several characters to represent. On the other hand, relative values often
represent changes in absolute coordinate values, and the changes are often small
values requiring fewer characters to represent.
Could be made even more efficient
This approach could have been made even more efficient from a download
viewpoint by taking advantage of the relative curve command (s)
similar to the approach illustrated by Figure 7, but using relative coordinate
values instead of absolute coordinate values.
If I did the calculations correctly, the use of the s command would
have eliminated five characters from the specification of the second Bezier
segment in Figure 9, causing it to contain six or seven fewer characters than
Figure 7.
The benefits of using the s command would continue to mount as the
overall Bezier curve becomes longer with additional Bezier segments strung onto
the end of the first segment. An example might be drawing a large contour
map based on a large number of elevation samples.
It seems to me, therefore, that if you need to draw long Bezier curves, the
best approach is probably to use the relative s command for the second
and all subsequent segments if possible.
Draw a quadratic Bezier curve
|
One of the reasons that I drew the blue cubic Bezier curve in Figure 1 is to
make it possible to compare the curve-fitting capabilities of a cubic Bezier
curve with the curve-fitting capabilities of a quadratic Bezier curve.
Superimpose the quadratic curve on the cubic curve
Figure 1 shows a red quadratic Bezier curve overlaid on top of the blue cubic
Bezier curve discussed above. Recall, however, that a quadratic Bezier
segment has only one control point, in addition to the start point and the end
point. This quadratic Bezier curve consists of two Bezier segments.
The locations of the two control
points in Figure 1 are at (100,150) and (200,250). These are the same
locations where the each pair of control points for the blue cubic Bezier curve
were located on top of one another. Therefore, if I were to draw a polyline showing the start
points, the control points, and the end points for the quadratic Bezier curve,
it would exactly overlay the light blue polyline that was drawn for the blue
cubic Bezier curve.
How do the curve fits compare?
The objective here is to be able to compare the curve fitting capabilities of
the cubic and quadratic Bezier curves. As you can see in Figure 1, the
blue
cubic Bezier curve does a much better job of representing, but also smoothing
the triangular polyline than the quadratic Bezier curve. On the other
hand, the cubic curve also requires more download data due to the extra control
point in each segment, and probably requires
more computational resource as well.
The quadratic Bezier curve
Listing 13 shows the call to the makePath method and the two calls to
the setAttribute method that draw the red quadratic Bezier curve in
Figure 1. Note that an absolute T command was used. As a
result, there is only one pair of coordinate values following the T
command.
Listing 13. The quadratic Bezier curve.
Element pathE = SvgGraphics.makePath(document,g, "M50,200 Q100,150 150,200 T250,200"); pathE.setAttribute("stroke","RED"); pathE.setAttribute("stroke-width","2"); |
By now, you should be able to understand the code in Listing 13 with no
further explanation.
Draw six elliptical arcs
An elliptical arc with no rotation
The code in Listing 14 draws the yellow elliptical arc with a red border
shown in Figure 1 to illustrate the appearance of such an arc with no rotation.
Listing 14. An elliptical arc with no rotation.
Element pathF = SvgGraphics.makePath(document,g, "M230,250 a70,30 0 1,0 50,-50 z"); pathF.setAttribute("stroke","red"); pathF.setAttribute("stroke-width","2"); pathF.setAttribute("fill","yellow"); |
Recall that I told you earlier
that an elliptical arc command is perhaps the most complex of all of the curve
commands. Each elliptical arc command requires seven parameters.
Dissecting the path command in Listing 14 gives the following:
- An absolute moveto command (M) followed by a pair
of absolute coordinate values (230,250) that specify establish
the start point of the elliptical arc. - A relative curve command (a).
- Two numeric values (70,30) that specify the horizontal and
vertical radii of the ellipse. - An x-axis-rotation value of 0.
- A large-arc-flag value of 1.
- A sweep-flag value of 0.
- A pair of relative coordinate values (50,-50) that specify
the end point of the elliptical arc. - A closepath command (z) that draws the
straight-line segment of the yellow elliptical arc shape shown in Figure 1.
An elliptical arc with a 45-degree rotation
The code in Listing 15 draws the red elliptical arc with a yellow border
shown in Figure 1 to illustrate the appearance of such an arc with a rotation of 45 degrees.
Listing 15. An elliptical arc with a 45-degree
rotation.
Element pathG = SvgGraphics.makePath(document,g, "M350,250 a70,30 45 1,0 50,-50 z"); pathG.setAttribute("stroke","yellow"); pathG.setAttribute("stroke-width","2"); pathG.setAttribute("fill","red"); |
The specification of the x-axis-rotation value of 45-degrees is
highlighted in red boldface in Listing 15 to make it easy for you to identify.
Other than the rotation, all of the parameter that control the shape of the
elliptical arc in Listing 15 are the same as the elliptical arc in Listing 14.
Four combinations of large-arc-flag and sweep-flag
The two parameters
large-arc-flag and sweep-flag can each take on either of two
values, 0 or 1. Therefore, there are four possible combinations of these
two parameters.
The code in Listing 16 calls the makePath method four times in
succession to draw the four elliptical arcs shown across the bottom of Figure 1.
Listing 16. Four combinations of large-arc-flag and
sweep-flag.
//Case 1: Fill color = red, border = blue // large-arc-flag = 1 // sweep-flag = 0 Element pathH = SvgGraphics.makePath(document,g, "M50,370 a50,50 0 1,0 50,-50 z"); pathH.setAttribute("stroke","blue"); pathH.setAttribute("stroke-width","2"); pathH.setAttribute("fill","red"); //Case 2: Fill color = green, border = red // large-arc-flag = 1 // sweep-flag = 1 Element pathI = SvgGraphics.makePath(document,g, "M225,400 a50,50 0 1,1 50,-50 z"); pathI.setAttribute("stroke","red"); pathI.setAttribute("stroke-width","2"); pathI.setAttribute("fill","green"); //Case 3: Fill color = blue, border = red // large-arc-flag = 0 // sweep-flag = 0 Element pathJ = SvgGraphics.makePath(document,g, "M300,370 a50,50 0 0,0 50,-50 z"); pathJ.setAttribute("stroke","red"); pathJ.setAttribute("stroke-width","2"); pathJ.setAttribute("fill","blue"); //Case 4: Fill color = yellow, border = blue // large-arc-flag = 0 // sweep-flag = 1 Element pathK = SvgGraphics.makePath(document,g, "M375,370 a50,50 0 0,1 50,-50 z"); pathK.setAttribute("stroke","blue"); pathK.setAttribute("stroke-width","2"); pathK.setAttribute("fill","yellow"); |
Other than the values of
large-arc-flag and sweep-flag, all of the parameter that control
the shape of these four elliptical arcs are the same. Therefore, by
comparing the code in Listing 16 with the images of the four elliptical arc
shapes in Figure 1, you should be able to gain an understanding of how these two
parameters impact the appearance of an elliptical arc.
All remaining code is "old stuff"
All of the remaining code in this program is code that I have explained one
or more times in earlier lessons (see Resources).
Therefore, I won’t repeat those explanations in this lesson.
That concludes my explanation of the path element and the program
named Svg12.
Run the program
I encourage you to copy the code from Listing 17 into your text
editor, compile it, and execute it. Load the SVG output file into an
SVG-capable graphics engine such as Firefox 1.5. Experiment with the code, making
changes, and observing the results of your changes.
Above all, enjoy the process. Programming, particularly graphics programming,
can be fun.
Summary
In this lesson, I taught you how to write Java code that uses an SVG graphics
library of my own design and the SVG/XML path element to efficiently draw
grid lines, simple geometric shapes composed of straight lines, cubic Bezier
curves, quadratic Bezier curves, and elliptical arcs.
What’s next?
Future lessons in this series will teach you how to write servlets that:
- Deal with the following graphics elements:
- text
- image (Deal with bit-mapped images in SVG.)
- use (Create and re-use graphics elements.)
- Use SVG symbols.
- Deal with stroke caps in SVG in comparison with similar caps in Java 2D.
- Use the switch element in SVG.
- Deal with other features of SVG, such as animation.
Resources
Java 2D Graphics
300 Java 2D Graphics,
Nested Top-Level Classes and Interfaces
302 Java 2D Graphics,
The Point2D Class
304 Java 2D Graphics,
The Graphics2D Class
306 Java 2D Graphics,
Simple Affine Transforms
308 Java 2D Graphics,
The Shape Interface, Part 1
310 Java 2D Graphics,
The Shape Interface, Part 2
312 Java 2D Graphics,
Solid Color Fill
314 Java 2D Graphics,
Gradient Color Fill
316 Java 2D Graphics,
Texture Fill
318 Java 2D Graphics,
The Stroke Interface
320 Java 2D Graphics,
The Composite Interface and Transparency
322 Java 2D Graphics,
The Composite Interface, GradientPaint, and Transparency
324 Java 2D Graphics,
The Color Constructors and Transparency
Java 2D API
Specification
Java 2D API
Java API for XML Processing (JAXP)
2200 Java
API for XML Processing (JAXP), Getting Started
2202 Getting
Started with Java JAXP and XSL Transformations (XSLT)
2204 Java
JAXP, Exposing a DOM Tree
2206 Java
JAXP, Implementing Default XSLT Behavior in Java
2208 Java
JAXP, Writing Java Code to Emulate an XSLT Transformation
2210 Java
JAXP, Transforming XML to XHTML
Links to numerous XML tutorials
by Richard G. Baldwin
Scalable Vector Graphics (SVG)
2212 Java JAXP, Creating graphics using Java and SVG
2214 An improved approach for creating SVG/XML code and SVG/XML DOM nodes
using Java
2216 Using Java to produce SVG code in XHTML data
2218 Writing Java servlets to produce XHTML code that references external SVG
files
Scalable Vector Graphics (SVG) 1.1
Specification
Adobe SVG Viewer plug-in
Create vector graphics in the browser with SVG by Uche Ogbuji
SVG Tutorial
SVG Basics
SVG in HTML
pages
Bézier Curves
What’s a
Bézier Curve?
Wikipedia, Bézier curve
Bézier Curve
Demo
Miscellaneous
W3C Markup Validation
Service
XMLvalidation.com
Reflection in
a Line
An Intuitive
Notion of Line Reflections
Complete program listing
A complete listing of the program that I presented and explained in this lesson
is shown in Listing 17 below.
Listing 17. Program code for Svg012
/*File Svg12.java Copyright 2007 R.G.Baldwin THE path ELEMENT The purpose of this program is to demonstrate the use of an SVG graphics method named makePath. The SVG graphics library in the class named SvgClass was updated during the writing of this program to add several new methods and to update some of the default values for existing methods. A BACKGROUND GRID The program begins by using a new method named makeGridString in conjunction with a new method named makePath to draw a background grid that resembles graph paper. The makeGridString method uses the moveto command and special versions of the horizontal and vertical lineto command to reduce the amount of XML SVG data needed to draw the grid. There are light blue horizontal and vertical lines every 10th pixel. There are slightly darker blue lines every 50th pixel. There are even darker blue lines every 100th pixel A RED AND BLUE TRIANGLE Then the program uses the moveto, lineto, and closepath commands to draw a triangle. THREE CUBIC BEZIER CURVES Then the program draws three cubic Bezier curves, illustrating the effect of the locations of the control points on the shape of the curve. ONE QUADRATIC BEZIER CURVE Then the program overlays a quadratic Bezier curve on the third cubic Bezier curve, illustrating the difference in the degree to which the cubic and quadratic Bezier curves fit the same envelope. ELLIPTICAL ARC ROTATION Then the program draws two elliptical arc curves illustrating the effect of the x-axis-rotation parameter on the appearance of the elliptical arcs. COMBINATIONS OF large-arc-flag and sweep-flag PARAMETERS Finally, the program draws four more elliptical arc curves, illustrating the difference in shape that results from all four combinations of the large-arc-flag and sweep-flag parameters. The program creates a DOM tree describing a specific graphic image in SVG format and writes it out into an XML file named Svg12.svg. The output file produced by this program can be rendered by loading it into Firefox 1.5. Tested using J2SE 5.0, Firefox v1.5.0.8, and WinXP. *********************************************************/ import javax.xml.parsers.*; import org.w3c.dom.*; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; public class Svg12 { public static void main(String[] args){ int width = 450; int height = 450; //Begin by creating a DOM tree that represents the XML // code that will render to produce the image of // interest. //Get the Document object. Document document = SvgGraphics.getDocument(); //Create the root node named svg and append it to the // document. Specify the parent as null for the // special case where the parent is the document. Element svg = SvgGraphics.makeNode( document, null,//parent "svg",//node type new String[]{"xmlns","http://www.w3.org/2000/svg", "version","1.1", "width",""+ (width + 2), "height","" + (height + 2), "position","absolute", "top","0", "left","0", });//end makeNode method //Create a node named g, which will be the parent for // several graphic elements. Pass null for the // reference to the array object for the special case // where the node has no attributes. Element g = SvgGraphics.makeNode(document, svg,//parent "g", null); //**DIAGONAL LINE** //Draw a blue diagonal line that will be used to // illustrate the effect of the path element in // reducing the size of the download and the attendant // bandwidth requirements. Element diagLine = SvgGraphics.makeLine( document,g,0,0,450,450); diagLine.setAttribute("stroke","#8888ff"); //** GRID LINES ** //Use the moveto command and the special horizontal // and vertical lineto commands to draw three sets of // grid lines that cause the output image to resemble // a sheet of graph paper. //First draw light blue grid lines every 10 pixels. String gridData = SvgGraphics.makeGridString( width,height,10); //Create the path element. Element temp; temp = SvgGraphics.makePath(document,g,gridData); //Set the color to light blue. temp.setAttribute("stroke","#ccccff"); //Now draw darker grid lines every 50 pixels gridData = SvgGraphics.makeGridString( width,height,50); temp = SvgGraphics.makePath(document,g,gridData); //Set the color to a slightly darker blue. temp.setAttribute("stroke","#aaaaff"); //Now draw even darker grid lines every 100 pixels. gridData = SvgGraphics.makeGridString( width,height,100); temp = SvgGraphics.makePath(document,g,gridData); //Set the color to an even darker blue. temp.setAttribute("stroke","#8888ff"); //** LABEL THE GRID ** for(int cnt = 0;cnt < width;cnt += 50){ SvgGraphics.makeText(document,g,0,cnt,""+cnt); SvgGraphics.makeText(document,g,cnt,height,""+cnt); }//end for loop //** RED AND BLUE TRIANGLE ** //Use the moveto, lineto, and closepath commands to // draw a red triangle with a blue border. This // example uses one absolute moveto command, one // absolute lineto command, and one relative lineto // command. (Don't confuse the lower-case L (1) // between the numeric values of 10 and -25 with the // numeric character for one (1).) Element pathA = SvgGraphics.makePath( document, g,//owner "M 50 10 L 100 10 l -25 40 z"); pathA.setAttribute("stroke","blue"); pathA.setAttribute("stroke-width","3"); pathA.setAttribute("fill","red"); //** THREE CUBIC BEZIER CURVES ** //Draw a red cubic Bézier curve. This curve consists // of two Bezier segments. First draw a light red // polyline whose nodes coincide with the start // points, end points, and control points for the two // segments. The purpose of the polyline is solely to // show the locations of those points. In has no // functional purpose in the drawing of the curve. // In this case, the three start and end points fall // on a horizontal line with a y-coordinate value // of 100. Element polylineB = SvgGraphics.makePolyline( document, g, new int[] {100,100,100,50,175,50,175,100, 175,150,250,150,250,100}); polylineB.setAttribute("stroke","#FF8888"); polylineB.setAttribute("stroke-width","2"); //Draw the cubic curve. Note the use of absolute // coordinate values only and also the use of the S // command. Element pathB = SvgGraphics.makePath(document,g, "M100,100 C100,50 175,50 175,100 S250,150 250,100"); pathB.setAttribute("stroke","red"); pathB.setAttribute("stroke-width","2"); //Draw a green cubic Bézier curve. This curve also // consists of two Bezier segments. First draw a light // green polyline whose nodes coincide with the start // points, end points, and control points for the two // segments. Element polylineC = SvgGraphics.makePolyline( document, g, new int[] {300,100,320,10,415,50,375,100, 335,150,430,190,450,100}); polylineC.setAttribute("stroke","#88ff88"); polylineC.setAttribute("stroke-width","2"); //Now draw the two Bezier segments that constitute the // Bezier curve. Note that this version uses // absolute coordinates but doesn't use the S command. Element pathC = SvgGraphics.makePath(document,g, "M300,100 C320,10 415,50 375,100 " + "335,150 430,190 450,100"); pathC.setAttribute("stroke","green"); pathC.setAttribute("stroke-width","2"); //Fit a pair of cubic Bezier segments into a set of // start points, end points, and control points where // the two control points on each side of the // horizontal axis coincide with one another so as to // form a triangular envelope. Note that this curve is // defined using relative coordinate values and does // not use the S command. It is colored blue. Also // note that a red quadratic Bezier curve will be // drawn later so as to coincide with the start // points, end points, and control points of this // curve. //As a reminder, for a Bezier segment, relative // coordinate values are relative to the location of // the start point for each segment. Element polylineD = SvgGraphics.makePolyline( document, g, new int[] {50,200,100,150,100,150,150,200, 200,250,200,250,250,200}); polylineD.setAttribute("stroke","#8888FF"); polylineD.setAttribute("stroke-width","2"); Element pathD = SvgGraphics.makePath(document,g, "M50,200 c50,-50 50,-50 100,0 50,50 50,50 100,0"); pathD.setAttribute("stroke","blue"); pathD.setAttribute("stroke-width","2"); //** ONE QUADRATIC BEZIER CURVE ** //Now fit a pair of quadratic Bezier segments into // the same triangular envelope as the above cubic // Bezier segments. Recall that for a quadratic // segment, there is a start point, an end point, // and only one control point. In this case, the // control points for the two quadratic Bezier // segments coincide with the overlaid control points // for the cubic Bezier segments discussed above. //This quadratic Bezier curve is colored red. This // curve uses absolute coordinate values and also // uses the T command to join the second segment to // the first segment. Note that no polyline was // created for this example because the same polyline // that was used for the cubic Bezier curve discussed // above also shows the locations of the start points, // end points, and control points for this quadratic // curve. Element pathE = SvgGraphics.makePath(document,g, "M50,200 Q100,150 150,200 T250,200"); pathE.setAttribute("stroke","RED"); pathE.setAttribute("stroke-width","2"); //** ROTATION OF AN ELLIPTICAL ARC ** //Draw a yellow elliptical arc with a red border to // illustrate the appearance of such an arc with no // rotation. Element pathF = SvgGraphics.makePath(document,g, "M230,250 a70,30 0 1,0 50,-50 z"); pathF.setAttribute("stroke","red"); pathF.setAttribute("stroke-width","2"); pathF.setAttribute("fill","yellow"); //Draw a red elliptical arc with a yellow border to // illustrate the appearance of such an arc with a // rotation of 45 degrees. Element pathG = SvgGraphics.makePath(document,g, "M350,250 a70,30 45 1,0 50,-50 z"); pathG.setAttribute("stroke","yellow"); pathG.setAttribute("stroke-width","2"); pathG.setAttribute("fill","red"); //** PARAMETERS large-arc-flag AND sweep-flag ** //Demonstrate four combinations of large-arc-flag and // sweep-flag //Case 1: Fill color = red, border = blue // large-arc-flag = 1 // sweep-flag = 0 Element pathH = SvgGraphics.makePath(document,g, "M50,370 a50,50 0 1,0 50,-50 z"); pathH.setAttribute("stroke","blue"); pathH.setAttribute("stroke-width","2"); pathH.setAttribute("fill","red"); //Case 2: Fill color = green, border = red // large-arc-flag = 1 // sweep-flag = 1 Element pathI = SvgGraphics.makePath(document,g, "M225,400 a50,50 0 1,1 50,-50 z"); pathI.setAttribute("stroke","red"); pathI.setAttribute("stroke-width","2"); pathI.setAttribute("fill","green"); //Case 3: Fill color = blue, border = red // large-arc-flag = 0 // sweep-flag = 0 Element pathJ = SvgGraphics.makePath(document,g, "M300,370 a50,50 0 0,0 50,-50 z"); pathJ.setAttribute("stroke","red"); pathJ.setAttribute("stroke-width","2"); pathJ.setAttribute("fill","blue"); //Case 4: Fill color = yellow, border = blue // large-arc-flag = 0 // sweep-flag = 1 Element pathK = SvgGraphics.makePath(document,g, "M375,370 a50,50 0 0,1 50,-50 z"); pathK.setAttribute("stroke","blue"); pathK.setAttribute("stroke-width","2"); pathK.setAttribute("fill","yellow"); //** OUTLINE ON GRAPHIC DISPLAY ** Element outline = SvgGraphics.makeNode( document, g,//parent "rect",//type new String[]{"x","1", "y","1", "width",""+ width, "height",""+ height, "fill","none", "stroke","black", "stroke-width","1", });//end makeNode method //** END OF GRAPHICS ** //Transform the DOM and write the output file. SvgGraphics.transformTheDom(document,"Svg12.svg"); }// end main() //----------------------------------------------------// }// class Svg12 //======================================================// //This class was updated on 01/06/07 to add several new // methods. //This is a proof-of-concept graphics class that // provides method calls for the creation of several // different DOM tree nodes. //Each method receives a reference to the overall document // along with a reference to the parent for the new node. //When the method returns, the new node has been appended // to the parent node. //The class also contains some utility methods that are // useful for the creation of graphic output using SVG. class SvgGraphics{ //----------------------------------------------------// //This method creates a linear gradient node to which // stop elements must be appended. static Element makeLinearGradient(Document document, Element parent, String id){ Element gradient = (Element)document.createElement("linearGradient"); parent.appendChild(gradient); gradient.setAttribute("id",id); return gradient; }//End makeLinearGradient //----------------------------------------------------// //This method creates a radial gradient node to which // stop elements must be appended. Note that numeric // attributes are set as type String. static Element makeRadialGradient(Document document, Element parent, String id, String gradientUnits, int cx, int cy, int r){ Element gradient = (Element)document.createElement("radialGradient"); parent.appendChild(gradient); gradient.setAttribute("id",id); gradient.setAttribute("gradientUnits",gradientUnits); gradient.setAttribute("cx",""+cx); gradient.setAttribute("cy",""+cy); gradient.setAttribute("r",""+r); return gradient; }//End makeRadialGradient //----------------------------------------------------// //This method creates a gradient stop node to be // appended to a linear gradient node or a radial // gradient node. static Element makeGradientStop(Document document, Element parent, String location, String color){ Element stopElement = (Element)document.createElement("stop"); parent.appendChild(stopElement); stopElement.setAttribute("offset",location); stopElement.setAttribute("stop-color",color); return stopElement; }//End makeGradientStop //----------------------------------------------------// //This method returns a reference to an ellipse. The // xCoor and yCoor parameters specify the center of the // ellipse. The xRadius and yRadius parameters specify // the width and height of the ellipse respectively // while it is in the horizontal plane before being // rotated. Numeric attributes are set at type String. // By default, the ellipse is drawn with a black stroke // one pixel wide and a fill of none. static Element makeEllipse(Document document, Element parent, int xCoor, int yCoor, int xRadius, int yRadius){ Element ellipse = (Element)document.createElement("ellipse"); parent.appendChild(ellipse); ellipse.setAttribute("cx",""+xCoor); ellipse.setAttribute("cy",""+yCoor); ellipse.setAttribute("rx",""+xRadius); ellipse.setAttribute("ry",""+yRadius); ellipse.setAttribute("stroke","black"); ellipse.setAttribute("stroke-width","1"); ellipse.setAttribute("fill","none"); return ellipse; }//end makeEllipse //----------------------------------------------------// //This method returns a reference to a circle. The // xCoor and yCoor parameters specify the center of the // circle. The radius parameter specifies the radus of // the circle. Numeric attributes are set as type // String. By default, the stroke is black one pixel // wide and the fill is none. static Element makeCircle(Document document, Element parent, int xCoor, int yCoor, int radius){ Element circle = (Element)document.createElement("circle"); parent.appendChild(circle); circle.setAttribute("cx",""+xCoor); circle.setAttribute("cy",""+yCoor); circle.setAttribute("r",""+radius); circle.setAttribute("stroke","black"); circle.setAttribute("stroke-width","1"); circle.setAttribute("fill","none"); return circle; }//end makeCircle //----------------------------------------------------// //This method returns a reference to a rectangle. The // xCoor and yCoor parameters specify the location of // the upper left corner. The width and height // parameters specify the width and the height while // the rectangle is in the horizontal plane before // being rotated. Numeric attributes are set as type // String. By default, the stroke is set to black, one // pixel wide and the fill is set to none. static Element makeRect(Document document, Element parent, int xCoor, int yCoor, int width, int height){ Element rect = (Element)document.createElement("rect"); parent.appendChild(rect); rect.setAttribute("x",""+xCoor); rect.setAttribute("y",""+yCoor); rect.setAttribute("width",""+width); rect.setAttribute("height",""+height); rect.setAttribute("stroke","black"); rect.setAttribute("stroke-width","1"); rect.setAttribute("fill","none"); return rect; }//end makeRect //----------------------------------------------------// //This method returns a reference to a line. x1 and y1 // specify the starting point of the line before it is // rotated. x2 and y2 specify the end point. By // default, the stroke is set to black one pixel wide. // This can be overridden to speciy other colors and // other widths if you need to do so. static Element makeLine(Document document, Element parent, int x1, int y1, int x2, int y2){ Element line = (Element)document.createElement("line"); parent.appendChild(line); line.setAttribute("x1",""+x1); line.setAttribute("y1",""+y1); line.setAttribute("x2",""+x2); line.setAttribute("y2",""+y2); line.setAttribute("stroke","black"); line.setAttribute("stroke-width","1"); return line; }//end makeLine //----------------------------------------------------// //This method returns a reference to a polyline. The // array of type int[] must contain an even number of // values for things to work correctly. //The values are extracted from the array and treated // as coordinate values x1,y1, x2,y2, x3,y3 ... etc. // By default, the stroke is set to black one pixel // wide with no fill. This can be overridden to other // colors and other widths if you need to do so. static Element makePolyline(Document document, Element parent, int[] points){ Element polyline = (Element)document.createElement("polyline"); parent.appendChild(polyline); String dataPoints = ""; for(int cnt=0;cnt<points.length;cnt++){ dataPoints += "" + points[cnt] + ","; }//end for loop polyline.setAttribute("points",dataPoints); polyline.setAttribute("stroke","black"); polyline.setAttribute("stroke-width","1"); polyline.setAttribute("fill","none"); return polyline; }//end makePolyline //----------------------------------------------------// //This method returns a reference to a polygon. The // array of type int[] must contain an even number of // values for things to work correctly. //The values are extracted from the array and treated // as coordinate values x1,y1, x2,y2, x3,y3 ... etc. // By default, the stroke is set to black, one pixel // wide with no fill. This can be overridden to other // colors and other widths if you need to do so. //The major difference between a polygon and a polyline // is that a polyline leaves the last point dangling. // However, a polygon automatically draws a line from // the last point back to the first point to close // the polygon. static Element makePolygon(Document document, Element parent, int[] points){ Element polygon = (Element)document.createElement("polygon"); parent.appendChild(polygon); String dataPoints = ""; for(int cnt=0;cnt<points.length;cnt++){ dataPoints += "" + points[cnt] + ","; }//end for loop polygon.setAttribute("points",dataPoints); polygon.setAttribute("stroke","black"); polygon.setAttribute("stroke-width","1"); polygon.setAttribute("fill","none"); return polygon; }//end makePolygon //----------------------------------------------------// /* One of the most frustrating things about using Java to create elements in XML, XHTML, or HTML is having to deal with the escape characters for the many required quotation marks. This method constructs an element, which may or may not have attributes. Also, the element may or may not be empty. The user of this method does not have to deal with the required quotation marks surrounding attribute values and the corresponding escape characters The first incoming parameter must be true if the element is empty and false if the element is not empty. If the first parameter is true, the element is sealed off in the required manner for an empty element. If the first parameter is false, the method returns the complete start tag for the element but does not return a complete element. It is the responsibility of the calling method to provide the content and the end tag for the element. The second parameter to the method must be a String that specifies the name of the element. The third parameter to the method must be a reference to an array object of type String. This array must contain an even number of elements. Each pair of elements constitutes the name and the value of an attribute, in the order name, value, name, value, etc. If the reference to the array object is null and the first parameter is false, the method returns the start tag for an element that has no attributes and is not empty. If the reference is null and the first parameter is true, the method returns a complete empty element with no attributes (which probably doesn't make any sense). An example of the recommended usage of the method follows: String newElement = SvgGraphics.makeElement( true/false, name, new String[]{"name","value", "name","value", "name","value", "name","value", "name","value", "name","value", "name","value", "name","value", "name","value" });//end call to makeElement */ static String makeElement( boolean empty,String elementName,String[] data){ //Begin constructing the start tag. String element = "<" + elementName + " "; //Deal with elements that have no attributes. if((empty==false) && (data == null)){ //Return a complete start tag. return element + ">"; }else if((empty==true) && (data == null)){ //Return a complete empty element. return element + "/>"; }//end if for(int cnt=0;cnt<data.length;cnt+=2){ String name = data[cnt]; String value = data[cnt+1]; element += name + "=" + """ + value + "" "; }//end for loop if(empty){ //Terminate the element appropriately for an // empty element. A complete empty element will // be returned. element += "/>"; }else{ //End the start tag for an element that is not // empty. In this case, only the start tag will // be returned. The calling program must provide // the content for the element as well as the end // tag for the element. element += ">"; }//end else return element; }//end makeElement //----------------------------------------------------// /* The purpose of this method is to create a general node having any name, and any number of attributes with any attribute names and any String values for the attributes, or no attributes at all. The first parameter is a reference to the document to which the new node belongs. The second parameter is a reference to the parent node to which this node is to be appended so as to become a child of that node. If this parameter is null, the new node is appended to the document. Otherwise, it is appended to the specified parent node. The third parameter is a String that specifies the type of node. The fourth parameter to the method must be a reference to an array object of type String. This array must contain an even number of elements. Each pair of elements constitutes the name and the value of an attribute, in the order name, value, name, value, etc. An example of the recommended usage of the method follows: Element abc = SvgGraphics.makeNode( document, def,//parent could be null "ghi",//node type new String[]{"name","value", "name","value", "name","value", "name","value", "name","value", "name","value", "name","value", "name","value", "name","value" });//end call to makeNode */ static Element makeNode(Document document, Element parent, String nodeType, String[] data){ Element element = (Element)document.createElement(nodeType); if(parent == null){ //For the special case of parent equal to null, // append the new node to the document. document.appendChild(element); }else{ //Otherwise, append the new node to the specified // parent. parent.appendChild(element); }//end else //Deal with elements that have no attributes. if(data == null){ return element; }//end if for(int cnt=0;cnt<data.length;cnt+=2){ String name = data[cnt]; String value = data[cnt+1]; element.setAttribute(name,value); }//end for loop return element; }//end makeNode //----------------------------------------------------// //This method returns a reference to a path. By // default, the stroke is set to black one pixel wide, // and the fill is set to none. //See the method named makeGridString for a utility // method designed to create data strings for this // method for the special case of drawing grids that // resemble graph paper. static Element makePath(Document document, Element parent, String d){ Element path = (Element)document.createElement("path"); parent.appendChild(path); path.setAttribute("d",d); path.setAttribute("stroke","black"); path.setAttribute("stroke-width","1"); path.setAttribute("fill","none"); return path; }//end makePath //----------------------------------------------------// //This is a utility method that is used to construct a // string that describes a grid pattern consisting of // horizontal and vertical lines at a specified pixel // spacing for a rectangular area of a specified width // and height in pixels. The string is intended to be // used as the data string for a call to the method // named makePath. //Note that this method makes use of the special // horizontal and vertical lineto commands in an attempt // to reduce the download size and the bandwidth // requirements for a drawing containing a large number // of horizontal and vertical lines in the grid pattern. static String makeGridString( int width,int height,int spacing){ //Construct the data string for the vertical lines. String data = "M0,0 "; for(int cnt = 0;cnt < width;cnt += spacing){ data += "v" + height + " M" + cnt + ",0 "; }//end for loop //Add the final vertical line. data += "v" + height + " n"; //Now add the horizontal lines to the data string. data += "M0,0 "; for(int cnt = 0;cnt < height;cnt += spacing){ data += "h" + width + " M0," + cnt + " "; }//end for loop //Add the final horizontal line. data += "h" + width + "n "; return data; }//end makeGridString //----------------------------------------------------// //This method returns a reference to a text element // node. static Element makeText(Document document, Element parent, int x, int y, String text){ Element textNode = (Element)document.createElement("text"); parent.appendChild(textNode); textNode.setAttribute("x",""+x); textNode.setAttribute("y",""+y); textNode.appendChild(document.createTextNode(text)); return textNode; }//end makePath //----------------------------------------------------// //This is a utility method that is used to execute code // that is the same regardless of the graphic image // being produced. static Document getDocument(){ Document document = null; try{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.newDocument(); }catch(Exception e){ e.printStackTrace(System.err); System.exit(0); }//end catch return document; }//end getDocument //----------------------------------------------------// //This is a utility method that is used to execute code // that is the same regardless of the graphic image // being produced. This method transforms the DOM into // raw XML code and writes that code into the output. static void transformTheDom(Document document, String filename){ try{ //Get a TransformerFactory object. TransformerFactory xformFactory = TransformerFactory.newInstance(); //Get an XSL Transformer object. Transformer transformer = xformFactory.newTransformer(); //Get a DOMSource object that represents the // Document object DOMSource source = new DOMSource(document); //Get an output stream for the output file. PrintWriter outStream = new PrintWriter(filename); //Get a StreamResult object that points to the // output file. Then transform the DOM sending XML // code to the file StreamResult fileResult = new StreamResult(outStream); transformer.transform(source,fileResult); }//end try block catch(Exception e){ e.printStackTrace(System.err); }//end catch }//end transformTheDom //----------------------------------------------------// }//end class SvgGraphics |
Copyright
Copyright 2007, Richard G. Baldwin. Reproduction in whole or in part in any
form or medium without express written permission from Richard Baldwin is
prohibited.
About the author
Richard Baldwin is a
college professor (at Austin Community College in Austin, TX) and private
consultant whose primary focus is a combination of Java, C#, and XML. In
addition to the many platform and/or language independent benefits of Java and
C# applications, he believes that a combination of Java, C#, and XML will become
the primary driving force in the delivery of structured information on the Web.
Richard has participated in numerous consulting projects and he
frequently provides onsite training at the high-tech companies located in and
around Austin, Texas. He is the author of Baldwin’s Programming
Tutorials, which have gained a
worldwide following among experienced and aspiring programmers. He has also
published articles in JavaPro magazine.
In addition to his programming expertise, Richard has many years of
practical experience in Digital Signal Processing (DSP). His first job after he
earned his Bachelor’s degree was doing DSP in the Seismic Research Department of
Texas Instruments. (TI is still a world leader in DSP.) In the following
years, he applied his programming and DSP expertise to other interesting areas
including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has
many years of experience in the application of computer technology to real-world
problems.