Java Programming Notes # 2216
- Preface
- General
background information - Preview
- Discussion and
sample code - Run the programs
- Summary
- What’s next?
- Complete program
listings - Copyright
- Resources
- About the author
Preface
General
This is the third lesson in a series designed to teach you how to write
servlets to produce SVG code that will be rendered in graphic form by an
SVG-compatible browser. In this lesson you will learn how to write Java code to produce XHTML files containing in-line SVG/XML
code. You will also learn how to write servlets that produce XHTML output containing
in-line SVG/XML code.
An SVG graphics library
In the previous two lessons titled "Java JAXP, Creating graphics using Java
and SVG" and "An improved approach for creating SVG/XML code and SVG/XML DOM
nodes using Java" (see Resources),
I taught you how write your own 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.
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. Graphic output from Svg03 and Svg04
programs.
Listings
- Listing 1. Beginning of Svg03 program class.
- Listing 2. Create the svg node.
- Listing 3. Show the outline of the canvas.
- Listing 4. Create the defs node.
- Listing 5. Create the first linearGradient
node. - Listing 6. The makeLinearGradient method.
- Listing 7. Create three stop nodes.
- Listing 8. Create the other linearGradient
node. - Listing 9. Create the radialGradient node.
- Listing 10. Create the node of type g.
- Listing 11. Create the basic ellipse.
- Listing 12. Set the appearance of the ellipse.
- Listing 13. Rotate the ellipse.
- Listing 14. Create the circle.
- Listing 15. Set the appearance and rotate the
circle. - Listing 16. Create and fill the rectangle.
- Listing 17. Round the corners of the
rectangle. - Listing 18. Create a polyline and rotate it.
- Listing 19. Create a polygon and dress it up.
- Listing 20. Create a wide, green, partially
transparent line. - Listing 21. Get an output stream to write the
XHTML file. - Listing 22. Process the DOM tree.
- Listing 23. The processDocumentNode method.
- Listing 24. Beginning of the recursive processNode
method. - Listing 25. Process nodes of type
ELEMENT_NODE. - Listing 26. The recursive processChildNodes method.
- Listing 27. The DOCUMENT_NODE case.
- Listing 28. First section of different code.
- Listing 29. Last section of different code.
- Listing 30. Program code for Svg03.
- Listing 31. Program code for Svg04.
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
SVG is a language for describing two-dimensional graphics in XML. SVG allows
for three types of graphic objects:
- vector graphic shapes
- images
- text
The Scalable Vector Graphics (SVG) 1.1
Specification is extensive, but it is not easy reading, particularly if your
knowledge of XML is weak.
While I don’t claim to be an expert in XML, I do know enough about the
topic that I can usually make sense of the material provided in the SVG
specification. One of my objectives for this series is to help Java programmers
who lack a strong background in XML to take advantage of the capabilities of
SVG.
Great graphics
in the browser
|
You can write
Java programs that will produce XML output, which, when loaded into an SVG
rendering engine, will result in the display of rich graphic material. When
combined with the use of servlets, this makes it possible to display graphic
material in the client area of a web browser that competes favorably with the
graphic material that can be displayed using the Java 2D API
(see
Resources)
in desktop applications.
Preview
|
Two different programs
In this lesson, I will present and explain two different programs. The
first program named Svg03 produces an output file containing SVG data
embedded in XHTML data (see the sidebar). The file can be rendered by loading it directly
into a Firefox 1.5 browser.
The second program named Svg04 is a servlet program. When the servlet is
deployed and then accessed
by a Firefox 1.5 browser, the servlet returns SVG data embedded in XHTML data
(see the sidebar),
which is rendered in graphic form by a Firefox 1.5 browser.
The graphic output
Both programs produce the same graphic output, which is shown in Figure 1.
Figure 1. Graphic output from Svg03 and Svg04
programs.
|
The programs illustrate a large number of important SVG
capabilities
The graphic output shown in Figure 1 illustrates a large number of important
capabilities of SVG including the following:
- An svg element that contains other SVG elements and serves as a
canvas upon which SVG
graphics
elements are drawn. The available graphics
elements are listed below. The subset of graphics elements shown
in boldface in the following list are known as the - path
- text
- rect
- circle
- ellipse
- line
- polyline
- polyline
- image
- use
- A rect element that produces a black border (stroke) on the canvas
with a thickness of one pixel. - A defs element, which is a
container for elements that are referenced by other elements in the
SVG code. In this case, the defs element contains two
linearGradient elements, one radialGradient element, and several
stop elements associated with the gradient elements. - A linearGradient element that produces a left-to-right color
gradient that begins with yellow, changes to red, and finishes with blue. This gradient is
referenced to fill the ellipse. - A second linearGradient element that produces a color gradient
that begins with green and finishes with blue. This gradient is
referenced to fill the circle. - A radialGradient element, which produces a color gradient where
the color changes going outward from a center point. The rounded
rectangle in Figure 1 is filled with this radial gradient where the color
starts as yellow at the center, changes to red, and finishes with blue at
the outer extremity of the gradient. - Several stop elements, which determine where and how the changes in
color gradients occur. - A g element, which is a
container element for grouping together related graphics elements.
This g element is the container for all the graphics elements in Figure 1
other than the rectangle that forms the black border on the
canvas. - An ellipse, with a blue border (stroke) two pixels thick
rotated by fifteen degrees clockwise around its center and
filled with a three-color linear gradient. - A circle rotated by fifteen degrees clockwise around its center and filled with a two-color linear gradient.
- A rectangle with rounded corners filled with a three-color radial
gradient. - A polyline described by four points and rotated by ten degrees
clockwise around the first point on the left. - A polygon described by four points with a red border (stroke)
three pixels thick and filled with green. - A green line with a width of twelve pixels and an opacity of 0.6.
This line is drawn on top of the other graphics elements from the upper left
to the bottom right of the canvas. Note that the other graphics
elements can be seen showing through the green line because its opacity is less
than 1.0.
Basic Shapes. Each
of the basic shapes is illustrated in Figure 1. The other graphics
elements will be illustrated in future lessons.
As I discuss the code for the two programs in the next section, remember that
both of the programs produce the graphic output shown in Figure 1.
Discussion
and sample code
The program named Svg03
Description of the program
The main purpose of this program is to illustrate the inclusion of SVG
graphics elements in an XHTML file that can be rendered in Firefox 1.5.
This program uses an SVG graphic library of my own design that has been significantly
upgraded relative to the version used in the earlier lessons of this series
(see Resources). The upgraded version of the library supports the following basic shapes plus linear and radial gradients
and dozens of attributes and transforms on each shape.
- rect
- circle
- ellipse
- line
- polyline
- polygon
The shapes in the above list are all of the basic shapes provided by SVG.
Convenience methods
The library also contains some convenience methods for creating elements,
nodes, and blocks of code. These convenience methods are designed to
reduce the labor required to write programs such as this one.
Create and transform a DOM tree
This program creates a DOM tree describing the SVG code for the image shown
in Figure 1. Then it transforms the DOM tree into SVG code that is
embedded
in a file that would otherwise be a valid XHTML file. As mentioned
earlier, inclusion of the SVG code prevents the code from being valid XHTML
code because the SVG element names are not recognized by XHTML validator programs
|
A precursor to writing a servlet program
The capability demonstrated by this program is a precursor to being able to
create inline SVG code in a servlet and to cause that SVG code to be rendered in
a Firefox browser that accesses the servlet.
The output file produced by this program can be rendered by loading it
directly into Firefox 1.5.
The program was tested using J2SE 5.0, Firefox v1.5.0.9, and WinXP.
Will discuss in fragments
I will discuss and explain this program in fragments. You can view the
entire program in Listing 30 near the end of the lesson. Listing 1 shows
the beginning of the program class for the program named Svg03.
Listing 1. Beginning of Svg03 program class.
public class Svg03{ public static void main(String argv[]){ //The following data values will be used to create // SVG graphics. int ellipseCenterX = 110; int ellipseCenterY = 100; int ellipseWidth = 100; int ellipseHeight = 40; int ellipseRotate = 15;//degrees int circleCenterX = 110; int circleCenterY = 100; int circleRadius = 30; int circleRotate = 15; int rectCenterX = 110; int rectCenterY = 200; int rectWidth = 110; int rectHeight = 80; int rectRoundX = 26; int rectRoundY = 25; int rotatePolyline = 10; try{ //Create a DOM tree that describes a particular // graphic image. //Begin by creating a Document object and a root // node named svg. All of the graphic content will // be contained in the svg element. This code was // explained in the earlier program named // Xslt01.java that was explained in lesson 2202 // named "Getting Started with Java JAXP and XSL // Transformations (XSLT)". //At this point, the program starts using the // SVG graphics library encapsulated in the class // named SvgGraphics. Document document = SvgGraphics.getDocument(); |
Listing 1 begins by declaring and initializing some variables that will be
used later by the program
Then the program calls the getDocument method of the SvgGraphics class
to get a reference to the Document object that represents the entire XML
document. I explained this method in an earlier lesson and won’t repeat
that explanation here.
Create the svg node
Listing 2 calls the makeNode method to create the root node named svg and append it to the document
node.
Listing 2 also sets some attributes on the svg node that are required for proper rendering.
Listing 2. Create the svg node.
Element svg = SvgGraphics.makeNode( document, null,//parent "svg",//node type new String[]{"xmlns","http://www.w3.org/2000/svg", "version","1.1", "width","220px", "height","440px", "position","static", "top","0", "left","0" });//end call to makeNode |
As I mentioned earlier, the svg node is used to create the canvas upon
which the Basic Shape graphics elements shown in Figure 1 are drawn.
The makeNode method
I explained the usage of the method named makeNode in the earlier
lesson titled "An improved approach for creating SVG/XML code and SVG/XML DOM
nodes using Java" (see Resources) and won’t
repeat that explanation here.
|
Attributes of the svg node
With the possible exception of the attributes named xmlns and
position, the purpose of the attributes that are set on the svg node
in Listing 2 should be fairly obvious on the basis of their names.
Show the outline of the canvas
Listing 3 calls the makeNode method to create a node of type rect
to draw the black rectangular outline on the canvas in Figure 1.
Listing 3. Show the outline of the canvas.
Element outline = SvgGraphics.makeNode( document, svg,//parent could be null "rect",//node type new String[]{"x","0", "y","0", "width","220", "height","440", "fill","none", "stroke","black", "stroke-width","1" });//end call to makeNode |
The stroke and stroke-width attributes
The stroke attribute value in Listing 3 specifies that the rectangle
should be drawn (or stroked) with a black line. The stroke-width
attribute in Listing 3 specifies that the black line should be one pixel in
thickness.
The x, y, width, and height attributes
If you compare the x, y, width, and height
attribute values in Listing 3 with the left, top, width,
and height attribute values in Listing 2, you will see that the outline
of the black rectangle matches the outline of the canvas that was created by
Listing 2. You can’t see the canvas in Figure 1. However, you can
see its outline that is highlighted by the black rectangle.
Create the defs node
Listing 4 calls the makeNode method to create a node of type defs.
Listing 4. Create the defs node.
Element defs = SvgGraphics.makeNode(document, svg,//parent "defs", null); |
The defs node will be the parent (container) for three gradient nodes. As
explained earlier, the defs element will contain two linear gradient
elements and one radial gradient element.
In this case, a value of null is passed as the fourth parameter (which is
normally a reference to an array object containing attribute name/value pairs)
to indicate that the defs node has no attributes.
Create the first linearGradient node
Listing 5 is the beginning of a section of code that creates nodes that
define three different gradient coloring schemes. These nodes will be
child nodes of the defs node.
Listing 5. Create the first linearGradient node.
Element gradientA = SvgGraphics.makeLinearGradient( document, //this document defs, //parent "gradientA");//id |
A container for referenced elements
|
Recall that a defs element in the
final SVG code is a container for elements that are
referenced by other elements in the SVG code. The three gradient nodes
will be identified as gradientA, gradientB, and gradientC.
The other elements in the SVG code will use these identifiers to reference the
gradient nodes.
The gradient elements will be referenced later to specify the fill colors for
an ellipse, a circle, and a rounded rectangle.
Call the makeLinearGradient method
The code in Listing 5 calls the makeLinearGradient method to create
the node of type linearGradient for gradientA. Although it
isn’t obvious yet, the gradient element that results from transforming this node
into raw XML code will provide a linear gradient that begins with yellow, changes to red, and ends
with blue going from left to right.
Did not call the makeNode method
|
It is important to note that Listing 5 did not call the general makeNode
method to create this node. Rather, it called the more specialized method
named makeLinearGradient.
Specialized methods in my SVG graphics library
My SvgGraphics library contains a
number of specialized methods that are used to create nodes with most of the
attributes having default values. Only the attribute values that are most
likely to change from one call to the next are passed as parameters to these
methods. They are provided as a convenience
because they can be called with a little less effort and
a little less
thought than the makeNode
method for those cases where they are applicable.
The makeLinearGradient method
It will probably be
useful to take a look at the code in the makeLinearGradient method as an
example of the more specialized methods in the library. The method is shown in its entirety in Listing 6.
Listing 6. The makeLinearGradient method.
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 and returns a reference to a node of type
linearGradient with an id attribute whose value is set to the value
of the third parameter. Before returning, the new
node is appended as a child to the node whose reference is received as the
second parameter.
This method is straightforward and shouldn’t require any further explanation.
|
Create three stop nodes
Now, returning to the main thread of the discussion, a linearGradient
element alone isn’t very useful. To be useful, the linearGradient
element must be supplemented by stop elements that control how and where
the colors change. One stop element is required for each color
change. The stop elements must be children of the linearGradient element.
According to the
W3C, "The ramp of colors to use on a gradient is defined by the stop
elements that are child elements to either the linearGradient element or
the radialGradient element."
Call the makeGradientStop method
Listing 7 calls the makeGradientStop method three times in succession to
establish the "ramp of colors" to be used on the linearGradient
node that was created in Listing 6.
Listing 7. Create three stop nodes.
SvgGraphics.makeGradientStop(document, gradientA,//parent "2%", //start here "yellow");//color SvgGraphics.makeGradientStop(document, gradientA, "50%", "red"); SvgGraphics.makeGradientStop(document, gradientA, "98%", "blue"); |
You can view the makeLinearGradient method in its entirety in Listing
30, but you don’t need to know much about the code in the method to be able to
use it. The important thing is to understand the meaning of the four
parameters to the method.
The four parameters to the makeGradientStop method
The first parameter is a reference to the document to which the element
belongs, and the second parameter is a reference to the node that will be the
parent of the stop node.
The gradient vector
The third parameter is perhaps the most complicated of the four. This
parameter specifies the location, (proceeding from the beginning to the end along a
linearGradient vector or from the center point outward for a radialGradient
vector) at which the color starts changing.
|
This value can be
specified as a percentage (0% to 100%) or a number (0.0 to 1.0)
representing a fraction of the total length of the vector to which the gradient
will be applied, (such as the width of ellipse in Figure 1).
For this
example, the color starts as pure yellow at the left end of the ellipse and
starts changing to red two-percent of the way to the right end of the ellipse.
The specified colors
The fourth parameter simply specifies the color at the point specified by the
third parameter.
Thus the combination of the first two stop nodes in Listing 7 specify
that the color will be yellow at and to the left of the two-percent point and
will be red at the fifty-percent point. The color will change in a linear
gradient fashion from yellow to red along the portion of the gradient vector that connects those two
points.
Similarly, the combination of the last two stop nodes in Listing 7
specify that the color will be red at the fifty-percent point and will be blue
at the ninety-eight-percent point and beyond. The color will change in a
linear gradient fashion from red to blue along the portion of the gradient vector that connects those
two points.
Create the other linearGradient node
Listing 8 creates the linearGradient node and two child nodes of type
stop that result in the green-to-blue gradient in the circle in Figure 1.
Listing 8. Create the other linearGradient node.
Element gradientB =SvgGraphics.makeLinearGradient( document, //this document defs, //parent "gradientB");//id SvgGraphics.makeGradientStop(document, gradientB,//parent "0%", //start here "green"); //color SvgGraphics.makeGradientStop(document, gradientB, "100%", "blue"); |
Create the radialGradient node
Listing 9 calls the makeRadialGradient method once and then calls the
makeGradientStop method three times in succession to create the radial
gradient node that results in the color gradient shown in the rounded rectangle
in Figure 1. This gradient starts with yellow at the center, changes to
red at a distance of fifty-percent of the distance to the outer edge and ends up
with blue at the outer edge of the rounded rectangle.
Listing 9. Create the radialGradient node.
Element gradientC = SvgGraphics.makeRadialGradient( document, //this document defs, //parent "gradientC", //ID "userSpaceOnUse", rectCenterX, //cx rectCenterY, //cy rectWidth/2); //r SvgGraphics.makeGradientStop(document, gradientC, "0%", "yellow"); SvgGraphics.makeGradientStop(document, gradientC, "50%", "red"); SvgGraphics.makeGradientStop(document, gradientC, "100%", "blue"); |
The parameters to the makeRadialGradient method are similar to, but
not identical to the parameters to the makeLinearGradient method in
Listing 5 and Listing 8. For the radial gradient, the first three
parameters have the same meaning as in Listing 5.
The attribute value of "userSpaceOnUse"
I’m not even going to try to explain the meaning of the fourth parameter with
the value "userSpaceOnUse." For this parameter, you can either
continue to use the same value, or you can go to the SVG specifications (see
Resources) to learn more about this parameter.
The center point and radius of the radialGradient
The fifth and sixth parameters, cx and cy, specify the point
from which the gradient will radiate.
The seventh parameter, r
specifies the distance from that center point that represents 100% insofar as
the stop element is concerned. Stated differently, this distance
represents the length of the gradient vector discussed earlier.
The fill color results
Thus, when this radial gradient is used to fill the rounded rectangle shown
in Figure 1, the
color at the center of the rectangle will be yellow. The color half way
between the center and the outer edge on the horizontal axis will be red.
The color at the outer edge on the horizontal axis will be blue. The
colors will change in a linear gradient fashion along any vector that extends
from the center to a point that is at a distance from the center equal to
one-half the width of the rectangle. (As you can see in Figure 1, because the height of
the rectangle is less than the width, the color never quite reaches the blue
state at the top and the bottom of the rectangle.)
The center point can be anywhere
By the way, there is no requirement for the center point of the radial
gradient to be at the center of the rectangle. In fact, there is no
requirement for the center point of the radial gradient to even be inside the
rectangle. When the radial gradient is used to fill the rectangle, the
effect is as if an invisible version of the radial gradient is created at a
specified location with a specified radius on the canvas but only
that portion that overlaps the rectangle actually becomes visible.
Create the node of type g
Listing 10 calls the makeNode method to create a group node named g.
As described earlier, a g element is a
container element for grouping together related graphics elements. This
g node will result in an element that will be the container for an ellipse,
a circle, a rectangle, a polyline, a polygon, and a line.
Listing 10. Create the node of type g.
Element g = SvgGraphics.makeNode(document, svg,//parent "g", null); |
Because this node doesn’t have any attributes, a value of null is passed as
the fourth parameter to the method.
Create the basic ellipse
Listing 11 shows the beginning of a section of code that creates the ellipse
shown at the top of Figure 1. Eventually, this ellipse will be:
- Given a blue border that is two pixels thick.
- Filled with gradientA, which is a linear gradient that begins
with yellow, changes to red, and finishes with blue. - Rotated by fifteen degrees clockwise around its center.
|
However, the code in Listing 11 simply calls the makeEllipse method to
create the basic ellipse on the basis of the specified coordinates for the
center of the ellipse and the specified values for the width and the height.
Listing 11. Create the basic ellipse.
Element theEllipse = SvgGraphics.makeEllipse( document, g,//Owner ellipseCenterX, ellipseCenterY, ellipseWidth, ellipseHeight); |
Note that the ellipse node that is created in Listing 11 is appended as a
child of the g node.
Set or update the appearance of the ellipse
After the basic ellipse node has been created by the code in Listing 11, Listing 12 calls the
setAttribute method on the reference to the ellipse node three times in
succession to:
- Fill the ellipse with the linear gradient element that was created and
identified as gradientA earlier. (This is what I meant when
I discussed the possibility of SVG elements
referencing elements that are created within the defs element.) - Set the border color to blue.
- Set the border thickness to two pixels.
Listing 12. Set the appearance of the ellipse.
theEllipse.setAttribute("fill","url(#gradientA)"); theEllipse.setAttribute("stroke","blue"); theEllipse.setAttribute("stroke-width","2"); |
|
The transform attribute
The transform
attribute can be set repeatedly on a graphics element to perform a series of sequential transforms on the
element.
Available transforms include:
- translate
- scale
- rotate
- skewX
- skewY
Rotate the ellipse (the hard way)
In this case, I was interested only in rotating the ellipse about its center.
It should be possible to rotate a shape around any location specified
by x and y coordinate values. However, I was unable to identify the proper
syntax for doing that, so I used a work around that consisted of three
sequential steps to accomplish the desired rotation.
The three steps
The three steps were:
- Translate the origin so that the origin was at the
center of the ellipse. - Rotate the ellipse by
fifteen degrees clockwise around the origin. - Translate the origin so that the center of the now-rotated ellipse was back in
its original location relative to the origin.
The code to accomplish the three steps is shown in Listing 13.
Listing 13. Rotate the ellipse.
theEllipse.setAttribute("transform", "translate(" + ellipseCenterX + "," + ellipseCenterY + ") " + "rotate(" + ellipseRotate + ") " + "translate(" + (-ellipseCenterX) + "," + (-ellipseCenterY) + ") "); |
If Listing 13 seems confusing, rewrite the statement in Listing 13,
substituting the numeric values for the variables in place of the variable
names. It may make more sense if you look at it that way.
Create the circle
Listing 14 calls the makeCircle method to position a basic circle so that it appears to be inside the ellipse
in Figure 1. However, it isn’t really inside the ellipse in a containment
sense. Rather, the circle is simply positioned on top of the ellipse.
The default opacity value for the circle is 1.0. Therefore, the ellipse
doesn’t show through the circle.
As you can see, the parameter list for the makeCircle method in
Listing 14 is a
little simpler than the parameter list for the makeEllipse method in
Listing 11.
This is because it is necessary to specify both a width and a height for an
ellipse, but it is only necessary to specify a radius for a circle.
Listing 14. Create the circle.
Element theCircle = SvgGraphics.makeCircle( document, g,//Owner circleCenterX, circleCenterY, circleRadius); |
Once the code in Listing 14 has finished executing, the basic circle has been
created and positioned, but it hasn’t been "dressed up" in the manner shown in
Figure 1.
Set the appearance and rotate the circle
Listing 15 calls the setAttribute method twice in succession to:
- Fill the circle with the green-to-blue linear gradient, created and
identified earlier as gradientB, as shown in the
circle in Figure 1. - Rotate the circle by fifteen degrees clockwise around its center.
Listing 15. Set the appearance and rotate the
circle.
theCircle.setAttribute("fill","url(#gradientB)"); theCircle.setAttribute("transform", "translate(" + circleCenterX + "," + circleCenterY + ") " + "rotate(" + circleRotate + ") " + "translate(" + (-circleCenterX) + "," + (-circleCenterY) + ") "); |
Why rotate the circle?
You might be wondering what is to be gained by rotating a circle by fifteen
degrees around its center. If the circle had been filled with a solid
color, rotation would not change the appearance of the circle. However,
because the circle was filled with a linear gradient such that it is green on
the left and blue on the right, rotating the circle about its center does change
its appearance.
Create and fill the rectangle
Listing 16 calls the makeRect method to create a basic rectangle.
The coordinates of the upper-left corner of the rectangle are specified by the
values of the third and fourth parameters. The width and height of the
rectangle are specified by the values of the fifth and sixth parameters.
Listing 16. Create and fill the rectangle.
Element theRect = SvgGraphics.makeRect( document, g,//Owner rectCenterX - rectWidth/2,//x rectCenterY - rectHeight/2,//y rectWidth, rectHeight); theRect.setAttribute("fill","url(#gradientC)"); |
Then Listing 16 calls the setAttribute method to fill the rectangle
with the radial gradient created and identified earlier as gradientC.
Note that at this point, the rectangle still has square corners. It is
not yet a "rounded rectangle."
Round the corners of the rectangle
Listing 17 calls the setAttribute method twice in succession to set
the values for the attributes named rx and ry. The purpose
of these two attributes is to control the amount of rounding that is applied to
the corners.
Listing 17. Round the corners of the rectangle.
//Round the corners. theRect.setAttribute("rx",""+ rectRoundX); theRect.setAttribute("ry",""+ rectRoundY); |
Although I can’t explain exactly how these attribute values are used to round
the corners from a geometry viewpoint (I would like to see some explanatory
pictures that describe this process), I can tell you generally how they behave. If you set either
attribute value to zero, all four corners will be square.
If you set rx to half the width of the rectangle and set ry to
half the height of the rectangle, the corners will be rounded so much that the
rectangle will take on the appearance of an ellipse.
Values in between those two extremes result in rounded corners as shown in
Figure 1.
What is a polyline?
A polyline is a shape that results from "connecting the dots"
between each consecutive pair of points in a set of points. Each point is
connected to only the point that follows it in the specification of the
locations of the points.
There is no requirement for the shape to be closed, although it can be closed if the coordinates for the last point
are the same as the coordinates for the
first point. (The polyline in Figure 1 is not closed.)
There is also nothing to prohibit a line drawn between two points from
crossing a previously drawn line between two other points as shown in Figure 1.
There are no gaps or open spaces along the line. You can always start at the beginning
specified by the first point and follow the line to the end that is specified by
the last point.
Create a polyline and rotate it
Figure 1 shows a black polyline that was drawn using coordinate values
for the points as shown in Listing 18.
Listing 18 begins by creating and populating an array object containing eight values of
type int. Each consecutive pair of int values placed in the
array object represents the coordinates of a single point. Thus, the array
object contains the coordinate values for four points.
Call the makePolyline method
Then Listing 18 calls the makePolyline method to construct and return
a reference to a node that describes a polyline element representing the
four points whose coordinate values were used to populate the array object.
Listing 18. Create a polyline and rotate it.
//Draw a polyline with four points. int[] polylinePoints = {10,235,210,235,110,275,110,225}; Element polyline = SvgGraphics.makePolyline( document, g,//owner polylinePoints); //Rotate the polyline by 10 degrees around the first // point. polyline.setAttribute("transform", "translate(" + polylinePoints[0] + "," + polylinePoints[1] + ")" + "rotate(" + rotatePolyline + ")" + "translate(" + (-polylinePoints[0]) + "," + (-polylinePoints[1]) + ")"); |
Rotate the polyline around the first point
When the makePolyline method returns the reference to the polyline
node, the code in Listing 18 calls the setAttribute method to rotate the
entire polyline shape by ten degrees clockwise around the first point.
Thus, the first line drawn for the polyline in Figure 1 has a general downward
slope going from left to right.
What is a polygon?
The big difference between a polyline and a polygon is that a
polygon must always be a closed shape. As a result, once the
polygon points have been connected with straight lines in the same fashion
as a polyline, the SVG rendering engine draws one additional line for a
polygon that connects the first and last points. This additional
line always closes the shape.
Create a polygon and dress it up
Listing 19 calls the makePolygon method to create a polygon
node using a set of points similar to those used earlier to create the
polyline node. However, the polygon is not subsequently
rotated as was the case with the polyline. Rather, the setAttribute method is called on the reference
to the polygon node three times in succession to give the polygon
a red border that is three pixels thick, and to fill the polygon with the
solid color green.
Listing 19. Create a polygon and dress it up.
//Draw a polygon with four points. Give it a red // border and fill it with green. int[] polygonPoints = {10,335,210,335,110,375,110,325}; Element polygon = SvgGraphics.makePolygon( document, g,//parent polygonPoints); polygon.setAttribute("fill","green"); polygon.setAttribute("stroke","red"); polygon.setAttribute("stroke-width","3"); |
The green and red polygon is shown at the bottom of Figure 1.
Create a wide, green, partially transparent line
Listing 20 calls the makeLine method to create a line node that
extends from the upper-left corner of the canvas to the lower-right corner of
the canvas. By default, this would be a black line one pixel wide.
Listing 20. Create a wide, green, partially
transparent line.
Element line = SvgGraphics.makeLine(document, g, //owner 0, //x1 0, //y1 220, //x2 440);//y2 line.setAttribute("stroke","green"); line.setAttribute("stroke-width","12"); line.setAttribute("stroke-opacity","0.6"); |
Then Listing 20 calls the setAttribute method three times in
succession to:
- Set the color of the line to green.
- Set the width of the line to twelve pixels.
- Set the opacity of the line to 0.6 or 60-percent. (You could
also say that the line is 40-percent transparent.)
Because the line is the last shape to be drawn on the canvas in Figure 1,
it is drawn on top of the other shapes. However, because the opacity of
the line is 0.6 (as opposed to 1.0), it is partially transparent and the
other shapes that are behind the line show through the line.
Wrap the SVG code in XHTML data
At this point, a DOM tree that represents the SVG code for the graphic image
shown in Figure 1 has been constructed. The remaining code in the class is used to transform the DOM tree into SVG code and to wrap
the SVG code in a document that would otherwise be a valid XHTML document.
This process begins in Listing 21 and becomes recursive later on.
Listing 21 begins by instantiating an object of the class. Then it gets
an output stream that will be used to write the XHTML data and the SVG data into
an output file named junk.xhtml.
Listing 21. Get an output stream to write the XHTML
file.
//Instantiate an object of this class Svg03 thisObj = new Svg03(); //Get an output stream for the output produced by // the program code. PrintWriter out = new PrintWriter( new FileOutputStream("junk.xhtml")); |
Process the DOM tree
The last executable statement in the main method is shown in Listing
22. This statement calls the method
named processDocumentNode passing a reference to the output stream and a
reference to the Document node of the DOM tree as parameters.
Listing 22. Process the DOM tree.
thisObj.processDocumentNode(out,document); }catch(Exception e){ //Note that no effort was made to provide meaningful // information in the event of an exception or // error. e.printStackTrace(System.err); }//end catch }// end main() |
An XHTML output file is required
If you have studied the two previous lessons in this series (see
Resources), you will recall that the sample
programs in those lessons produced SVG files as the main output from the
program. Those programs didn’t attempt to create XHTML
files. You will also recall that the programming task was relatively easy
at this point in the program because JAXP methods already exist to do transform
the DOM tree into raw XML code. Unfortunately, things
aren’t quite so easy when the program is required to produce an XHTML file as
its output.
There is a lot of programming that you are responsible for providing in this
case.
XHTML text is required
There is quite a lot of XML text that is required to cause a file to qualify
as a valid XHTML file. I won’t go into the reasons for that text because
that is beyond the scope of this tutorial series. However, I will show you
how to write the code to create the text.
The processDocumentNode method
The processDocumentNode method is used to produce the XML text that is required in the output at the document level,
(such as the XML declaration). The method also produces the top level element tags.
The processDocumentNode method is shown in its entirety in Listing 23.
Listing 23. The processDocumentNode method.
void processDocumentNode(PrintWriter out,Node node){ //Create the beginning of the XHTML document. out.println("<?xml version="1.0" " + "encoding="UTF-8"?>"); out.println( "<!DOCTYPE html PUBLIC "-//W3C//DTD " + "XHTML 1.0 Transitional//EN" " + ""http://www.w3.org/TR/xhtml1/" + "DTD/xhtml1-transitional.dtd">"); out.println(SvgGraphics.makeElement(false,"html", new String[]{"xmlns","http://www.w3.org/1999/xhtml", "xml:lang","en", "name","lang" })//end call to makeElement );//end println out.println("<head>"); out.println(SvgGraphics.makeElement( true, "meta", new String[]{"http-equiv","content-type", "content", "image/svg+xml; charset=UTF-8" })//end call to makeElement );//end println out.println("<title>Generated XHTML file</title>"); out.println("</head>"); out.println(SvgGraphics.makeElement( false, "body", new String[]{"id","body", "style","position:absolute;" + "z-index:0;" + "border:1px solid black;" + "left:0;" + "top:0;" + "width:220px;" + "height:440px;" })//end call to makeElement );//end println //Go process the root (document) node. This method // call triggers a recursive process that will //process the entire DOM tree. processNode(out,node); //The entire DOM tree has been processed when control // returns to this point. //Now finish the output document and flush the output // buffer. out.println("</body></html>"); out.flush(); }//end processDocumentNode |
Behavior of the processDocumentNode method
|
The behavior of the processDocumentNode method is pretty
straightforward and shouldn’t require a lot of explanation beyond the embedded
comments.
Down to the point where the method makes a recursive call to the
method named processNode, the code in the method consists simply of print statements
and calls to the makeElement method.
The call to the processNode method
The call to the processNode method near the end of the processDocumentNode
method is where the recursive processing of the DOM tree begins in earnest.
(The processNode method is passed a reference to the output stream
along with a reference to the root node of the DOM tree at this point.)
When control returns to the processDocumentNode method from the call
to the processNode method in Listing 23, the entire DOM tree will have been processed
recursively. At that point, one more print statement is executed to put
the final string of XML text into the output file. Then the output buffer
is flushed, the processDocumentNode method terminates, and the program
terminates leaving an XHTML file named junk.xhtml in the current
directory.
The processNode method
A general DOM tree could contain any of the following seven types of nodes:
- root or document
- element
- attribute
- text
- comment
- processing instruction
- namespace
A general purpose DOM tree processor would have to be capable of handling any
combination of the seven types of nodes in the above list. However, because the nodes in
this DOM tree are produced by the program, it is known in advance which types of
nodes will be encountered. Therefore, the processNode method only
handles element nodes, attribute nodes, and document nodes.
Beginning of the recursive processNode method
The recursive processNode method begins in Listing 24. The code in Listing
24 is straightforward.
Listing 24. Beginning of the recursive processNode method.
void processNode(PrintWriter out,Node node){ try{ if (node == null){ System.out.println("Nothing to do, node is null"); return; }//end if |
Process nodes according to their type
Each time the processNode method is called, it receives an incoming
parameter that specifies the node from the DOM tree that is to be processed
recursively.
Each node is processed according to its type. A switch statement is
used to select among the different types.
Process nodes of type ELEMENT_NODE
Listing 25 shows the code that is executed to process nodes of type
ELEMENT_NODE.
Listing 25. Process nodes of type ELEMENT_NODE.
int type = node.getNodeType(); switch(type){ case Node.ELEMENT_NODE:{ //Start creating the start tag. String nodeName = node.getNodeName(); out.print("<" + nodeName + " "); //Now add each attribute to the start tag. NamedNodeMap theList = node.getAttributes(); int length = theList.getLength(); for(int cnt = 0;cnt < length;cnt++){ Node item = theList.item(cnt); out.print(item.getNodeName() + "="" + item.getNodeValue() + "" "); }//end for loop out.println(">");//close the start tag. //Process all XML child nodes recursively. processChildNodes(out,node); //The element and all of its children have been // processed when control returns to this point. //Create the end tag for the element. out.println("</" + nodeName + ">"); break; }//end case ELEMENT_NODE |
Create the beginning of the element start tag
The code in Listing 25 begins by creating the XML text that constitutes the
beginning of the start tag for an XML element consisting of "<nodeName ".
Process each attribute
Then Listing 25 uses a for loop to:
- Get each attribute belonging to the node
- Construct an
XML text string in the proper syntax for the name and value of the attribute - Concatenate the newly-constructed text string onto the start tag
Finish the start tag
When all attributes belonging to the node have been processed, the for
loop terminates and an angle
bracket is appended to the element start tag. At this point, a start tag looking
something like the following has been sent to the output stream:
<elementName attrName="attrValue" attrName="attrValue">
Process the child nodes
Before creating the element’s end tag, it is necessary to process any child
nodes belonging to the node that is being processed. This is accomplished
by making a call to the recursive method named processChildNodes.
Construct the end tag
When
control returns from the recursive processChildNodes method, the entire DOM
sub-tree below the current node will have been processed. The code in Listing 25:
- Constructs the end tag for the element
- Sends it to the output stream
- Breaks out of the switch statement to return control to the method from which
the processNode method was called
Put switch statement on the back burner
At this point, I am going to put the switch statement in Listing 25 on
the back burner, and continue explaining the processing of nodes of
type ELEMENT_NODE.
As mentioned above, before creating the text for
the element’s end tag and sending it to the output, the code in Listing 25 calls
the recursive method named processChildNodes, passing a reference to the node
currently being processed, along with a reference to the output stream as
parameters. The purpose is to recursively process all of the child nodes
belonging to the node that is currently being processed.
The recursive processChildNodes method
The processChildNodes method is shown in its entirety in Listing 26.
Listing 26. The recursive processChildNodes method.
void processChildNodes(PrintWriter out,Node node){ NodeList children = node.getChildNodes(); if (children != null){ int len = children.getLength(); //Iterate on NodeList of child nodes. for(int i = 0; i < len; i++){ //This is a recursive call. processNode(out,children.item(i)); }//end for loop }//end if children != null }//end processChildNodes |
|
Get a list of child nodes
The code in Listing 26 begins by getting a list of all the child nodes
belonging to the node specified by the second incoming parameter.
Process each child node
Then it
executes a for loop, calling the processNode method during each
iteration of the loop and passing a child node’s reference to the
processNode method. (This causes the specified child node and all
of its children to be processed recursively.)
When the for loop in Listing 26 terminates, all of the
child nodes belonging to the node that was specified by the second incoming
parameter will have been recursively processed, and the processChildNodes
method returns control to the method from which it was called.
The DOCUMENT_NODE case
That completes
the explanation of the processing of nodes of type ELEMENT_NODE that
began with the switch statement in Listing 25. There is another
case in the switch statement, however, that hasn’t been explained yet.
That case is shown in Listing 27.
Listing 27. The DOCUMENT_NODE case.
case Node.DOCUMENT_NODE:{ //No action is required other than to process // the child nodes recursively. processChildNodes(out,node); break; }//end case DOCUMENT_NODE }//end switch }catch(Exception e){ e.printStackTrace(System.err); }//end catch }//end processNode(Node) |
Listing 27 shows the code that processes the incoming node to the
processNode method for the case where the type of the node is
DOCMENT_NODE.
Will occur only once
This case will occur only once at the beginning of the processing of the DOM
tree because the DOM tree has only one document node. As indicated in the comments in Listing 27, no action is required
for this case other than to assure that all of the child nodes of the document
node are processed
recursively. Therefore, the code in Listing 27 simply makes a call to the
processChildNodes method passing a reference to the document node as a
parameter.
The entire DOM tree will have been processed
When control returns from the call to processChildNodes in the
DOCUMENT_NODE case in Listing 27, the entire DOM tree will have been
processed. At that point:
- Control breaks out of the switch statement
- The processNode method returns to the method from which it was
called - Control progresses up the call stack to the bottom of
Listing 23 causing the final XML text to be written into the output stream and
causing the output buffer to be flushed
The program terminates
Control then returns to the
main method in Listing 22. Since there is nothing more for the
program to do in the main method, the program terminates at that point,
leaving a new file named junk.xhtml in the current directory.
End of explanation for Svg03
And that ends the explanation of the program named Svg03. As
indicated earlier, you will find a complete listing of all of the code discussed
above, plus the code for my SVG graphics library class named SvgGraphics
in Listing 30 near the end of the lesson.
The program named Svg04
Description of the program
This program demonstrates the rendering of SVG graphic content in a Firefox 1.5
client browser that is produced by a servlet that is accessed by the browser.
Very similar to the previous program
Except for the fact that this program is a servlet that generates XHTML code
as its output, and the previous program named Svg03 is a desktop
application that produces an output file containing XHTML code, the two programs
are identical.
This program defines a servlet that first creates a DOM tree describing the
graphic image in Figure 1, and then transforms that tree into corresponding SVG XML
code for transmission to the client.
Not valid XHTML
The program wraps the SVG graphic
code in code that would otherwise be valid XHTML code. However the inclusion of the
SVG code prevents the code produced by the servlet from being valid XHTML because the SVG
element names are not recognized by XHTML validator programs, such as the
program at the following URL:
http://validator.w3.org/file-upload.html
Program testing
For testing, the servlet must be deployed on a
servlet-compatible server and then accessed using an SVG-compatible browser,
such as Firefox 1.5. On my system, the servlet can be tested by first copying
it into the following directory on my Tomcat server:
C:jakarta-tomcat-5.0.27webappsROOTWEB-INFclasses
Having copied the servlet onto the server and having started the server
running, the servlet can be accessed and the SVG code can be rendered by
accessing the servlet in Firefox using the following URL:
http://localhost/servlet/Svg04
What about Internet Explorer
As I mentioned earlier, I have not installed the SVG plug-in for IE6. Accessing the servlet using IE6 (without an SVG plug-in installed)
simply causes the browser to request permission to save the output produced by
the server in a local file. The file name that is recommended by IE6 is
simply Svg04 with no extension.
Therefore, the program was tested using J2SE 5.0, Firefox 1.5.09, and
jakarta-tomcat-5.0.27 running as a localhost server under WinXP.
Will discuss in fragments
As usual, I will discuss this program in servlets. However, because the
code in this program is almost identical to the code in the earlier program
named Svg03, I will only discuss the code that is different between the
two. You can view the program named Svg04 in its entirety in
Listing 31 near the end of the lesson.
As you will see, the difference between the two programs is confined to two
small sections of code.
First section of different code
The first section of code that differs between the programs named Svg03
and Svg04 is shown in Listing 28.
Listing 28. First section of different code.
import javax.servlet.*; import javax.servlet.http.*; public class Svg04 extends HttpServlet{ public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{ //Note: This statement is critical to causing // Firefox to recognize and render the SVG // code produced by the servlet. res.setContentType("image/svg+xml"); |
Additional import directives are required
First, because Svg04 is a servlet program, it must declare the two
import directives shown in Listing 28 (or fully qualify the classes contained
in those two packages when they are referenced in the program code.).
Extend HttpServlet class
Second, because Svg04 is a servlet program, the class must extend the
HttpServlet class.
Define the doGet method
Third, because Svg04 is a servlet program and not a desktop
application, the main method in
the desktop application named Svg03 must be replaced by the method named doGet
shown in Listing 28.
Set the content type
Fourth, the program must set the content
type as shown in the last statement in Listing 28.
Last section of different code
The last section of code that differs between the two programs is the single
statement shown in
Listing 29.
Listing 29. Last section of different code.
//Get an output stream for the output produced by // the program code. PrintWriter out = res.getWriter(); |
At this point in the program, the desktop application named Svg03 instantiates a
PrintWriter output stream object based on a FileOutputStream object.
That causes the output produced by the program to be written into a file named
junk.svg.
As you can see in Listing 29, the code required to get an appropriate output
stream for a servlet is somewhat simpler. Output that is placed in the
output stream by the servlet ends up at the client browser for rendering and
display.
Those are the only differences between the two
programs
Unless I missed something during my comparison of the two programs, the only
differences between the two programs are shown in Listing 28 and Listing 29.
It is fortunate that the differences between the two programs are so few in
number and so insignificant in content. Testing and debugging servlets can
be a difficult task. However, in this case at least, the program can be
developed, tested, and debugged as a stand alone Java application, and then
converted to a servlet by making the small number of changes shown in Listing 28
and Listing 29.
That ends the discussion and explanation of the program named Svg04.
Once again, you can view the program in its entirety in Listing 31 near the end
of the lesson.
Run the programs
I encourage you to copy the code from Listing 30 and Listing 31 into your text
editor, compile it, and execute it. Experiment with it, making
changes, and observing the results of your changes. Then view the results in
Firefox 1.5, or some other suitable SVG rendering engine. 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 to produce XHTML files
containing in-line SVG/XML code. I also taught you how to write servlets that
produce XHTML output containing in-line SVG/XML code.
An SVG program was developed, tested, and debugged as a stand-alone desktop
application that produced an output XHTML file to produce the image shown in
Figure 1.
Then that desktop application was converted to a servlet program by making
the following five changes to the code in the desktop application:
- Declare the following import directives:
import javax.servlet.*; import javax.servlet.http.*;
- Define the main class to extend the HttpServlet class.
- Replace the signature for the main method in the desktop
application with the signature for the doGet
method in the servlet program:public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
- Execute the following statement to set the content type:
res.setContentType("image/svg+");
- Execute the following statement to get an output stream:
PrintWriter out = res.getWriter();
What’s next?
The next lesson in this series will teach you how to write servlets that
produce XHTML output containing references to external SVG files. Those
SVG files may be created on-the-fly during the execution of the servlet.
Future lessons will teach you how to write servlets that:
- Deal with the following graphics elements:
- path
- 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.
Complete program listings
Complete listings of the programs discussed in this lesson are shown in Listing
30 and Listing 31 below.
Listing 30. Program code for Svg03
/*File Svg03.java Copyright 2006 R.G.Baldwin The main purpose of this program is to illustrate the inclusion of SVG graphics in an XHTML file that can be rendered in Firefox 1.5. This program uses an SVG graphic library that has been significantly improved over the one used in the earlier program named Svg02. The library now supports the following basic shapes plus linear and radial gradients and dozens of attributes and transforms on each shape. rect circle ellipse line polyline polygon These are all of the basic shapes provided by SVG. The library also contains some convenience methods for creating elements, nodes, and blocks of code. These convenience methods are designed to reduce the labor required to write programs such as this one. This program creates a DOM tree describing the SVG code for a particular graphic image. Then it transforms the DOM tree into SVG code that is wrapped in a file that would otherwise be a valid XHTML file. Inclusion of the SVG code, however, prevents the code from being valid XHTML because the SVG element names are not recognized by XHTML validator programs, such as the program at the following URL: http://validator.w3.org/file-upload.html The capability demonstrated in this program is a precursor to being able to create inline SVG code in a servlet and to cause that SVG code to be rendered in a Firefox browser that accesses the servlet. (As you will see, creating an XHTML file is a good bit more difficult than creating an SVG file as was done in the earlier program named Svg02.java.) The output file produced by this program can be rendered by loading it directly 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 java.io.*; public class Svg03{ public static void main(String argv[]){ //The following data values will be used to create // SVG graphics. int ellipseCenterX = 110; int ellipseCenterY = 100; int ellipseWidth = 100; int ellipseHeight = 40; int ellipseRotate = 15;//degrees int circleCenterX = 110; int circleCenterY = 100; int circleRadius = 30; int circleRotate = 15; int rectCenterX = 110; int rectCenterY = 200; int rectWidth = 110; int rectHeight = 80; int rectRoundX = 26; int rectRoundY = 25; int rotatePolyline = 10; try{ //Create a DOM tree that describes a particular // graphic image. //Begin by creating a Document object and a root // node named svg. All of the graphic content will // be contained in the svg element. This code was // explained in the earlier program named // Xslt01.java that was explained in lesson 2202 // named "Getting Started with Java JAXP and XSL // Transformations (XSLT)". //At this point, the program starts using the // SVG graphics library encapsulated in the class // named SvgGraphics. Document document = SvgGraphics.getDocument(); //Create the root node named svg and append it to // the document. //Set some attributes on the root node that are // required for proper rendering. Element svg = SvgGraphics.makeNode( document, null,//parent could be null "svg",//node type new String[]{"xmlns","http://www.w3.org/2000/svg", "version","1.1", "width","220px", "height","440px", "position","static", "top","0", "left","0" });//end call to makeNode //Show outline of canvas using 'rect' element Element outline = SvgGraphics.makeNode( document, svg,//parent could be null "rect",//node type new String[]{"x","0", "y","0", "width","220", "height","440", "fill","none", "stroke","black", "stroke-width","1" });//end call to makeNode //Create a node named defs, which will be the parent // for three gradient definitions. There will be // two linear gradients and one radial gradient. // Pass null for the reference to the object for the // special case where the node has no attributes. Element defs = SvgGraphics.makeNode(document, svg,//parent "defs", null); //Create nodes that define three different gradient // coloring schemes. //The definitions are identified as gradientA, // gradientB, and gradientC. They will be referred // to later to specify the fill colors for an // ellipse, a circle, and a rounded rectangle. //Define gradientA, which provides a linear // gradient from yellow to red to blue going from // left to right. Element gradientA = SvgGraphics.makeLinearGradient( document, //this document defs, //parent "gradientA");//id //Create three stop nodes that identify the colors // used to produce the gradient and specify where // the colors begin and end, SvgGraphics.makeGradientStop(document, gradientA,//parent "2%", //start here "yellow");//color SvgGraphics.makeGradientStop(document, gradientA, "50%", "red"); SvgGraphics.makeGradientStop(document, gradientA, "98%", "blue"); //Define gradientB, which provides a linear // gradient having two stops from green to blue // going from left to right. Element gradientB =SvgGraphics.makeLinearGradient( document, //this document defs, //parent "gradientB");//id SvgGraphics.makeGradientStop(document, gradientB,//parent "0%", //start here "green"); //color SvgGraphics.makeGradientStop(document, gradientB, "100%", "blue"); //Define gradientC, which provides a radial // gradient from yellow to red to blue going from // the center to the outer edge. Element gradientC = SvgGraphics.makeRadialGradient( document, //this document defs, //parent "gradientC", //ID "userSpaceOnUse", rectCenterX, //cx rectCenterY, //cy rectWidth/2); //r SvgGraphics.makeGradientStop(document, gradientC, "0%", "yellow"); SvgGraphics.makeGradientStop(document, gradientC, "50%", "red"); SvgGraphics.makeGradientStop(document, gradientC, "100%", "blue"); //Create a node named g, which will be the parent // for an ellipse,a circle, a rectangle, a line, // a polyline, and a polygon. Pass null for the // reference to the object for the special case // where the node has no attributes. Element g = SvgGraphics.makeNode(document, svg,//parent "g", null); //Create an ellipse with a blue border that is two // pixels wide. Fill it with the yellow-red-blue // gradient defined by gradientA. Element theEllipse = SvgGraphics.makeEllipse( document, g,//Owner ellipseCenterX, ellipseCenterY, ellipseWidth, ellipseHeight); //Set the appearance of the ellipse. theEllipse.setAttribute("fill","url(#gradientA)"); theEllipse.setAttribute("stroke","blue"); theEllipse.setAttribute("stroke-width","2"); //Rotate the ellipse by 15-degrees clockwise about // its center. theEllipse.setAttribute("transform", "translate(" + ellipseCenterX + "," + ellipseCenterY + ") " + "rotate(" + ellipseRotate + ") " + "translate(" + (-ellipseCenterX) + "," + (-ellipseCenterY) + ") "); //Position a circle so that it appears to be inside // the ellipse. Fill it with the green-blue // gradient defined by gradientB. Element theCircle = SvgGraphics.makeCircle( document, g,//Owner circleCenterX, circleCenterY, circleRadius); //Set the appearance of the circle and rotate it by // 15-degrees clockwise about its center. theCircle.setAttribute("fill","url(#gradientB)"); theCircle.setAttribute("transform", "translate(" + circleCenterX + "," + circleCenterY + ") " + "rotate(" + circleRotate + ") " + "translate(" + (-circleCenterX) + "," + (-circleCenterY) + ") "); //Make a rounded rectangle and fill it with // gradientC Element theRect = SvgGraphics.makeRect( document, g,//Owner rectCenterX - rectWidth/2,//x rectCenterY - rectHeight/2,//y rectWidth, rectHeight); theRect.setAttribute("fill","url(#gradientC)"); //Round the corners. theRect.setAttribute("rx",""+ rectRoundX); theRect.setAttribute("ry",""+ rectRoundY); //Draw a polyline with four points. int[] polylinePoints = {10,235,210,235,110,275,110,225}; Element polyline = SvgGraphics.makePolyline( document, g,//owner polylinePoints); //Rotate the polyline by 10 degrees around the first // point. polyline.setAttribute("transform", "translate(" + polylinePoints[0] + "," + polylinePoints[1] + ")" + "rotate(" + rotatePolyline + ")" + "translate(" + (-polylinePoints[0]) + "," + (-polylinePoints[1]) + ")"); //Draw a polygon with four points. Give it a red // border and fill it with green. int[] polygonPoints = {10,335,210,335,110,375,110,325}; Element polygon = SvgGraphics.makePolygon( document, g,//parent polygonPoints); polygon.setAttribute("fill","green"); polygon.setAttribute("stroke","red"); polygon.setAttribute("stroke-width","3"); //Draw a green line 12 pixels wide. Make the line // 60% opaque, or 40% transparent, whichever you // prefer. Element line = SvgGraphics.makeLine(document, g, //owner 0, //x1 0, //y1 220, //x2 440);//y2 line.setAttribute("stroke","green"); line.setAttribute("stroke-width","12"); line.setAttribute("stroke-opacity","0.6"); //The remaining code in this class is used to // transform the DOM tree into SVG code and to wrap // that code in a document that would otherwise be // a valid XHTML document. //Instantiate an object of this class Svg03 thisObj = new Svg03(); //Get an output stream for the output produced by // the program code. PrintWriter out = new PrintWriter( new FileOutputStream("junk.xhtml")); //Process the DOM tree, beginning with the Document // node to produce the output. //The invocation of processDocumentNode starts a // recursive process that will process the entire // DOM tree. thisObj.processDocumentNode(out,document); }catch(Exception e){ //Note that no effort was made to provide meaningful // information in the event of an exception or // error. e.printStackTrace(System.err); }//end catch }// end main() //----------------------------------------------------// //This method is used to produce the text required in // the output at the document level, such as the // XML declaration. It also produces the top level // element tags. void processDocumentNode(PrintWriter out,Node node){ //Create the beginning of the XHTML document. out.println("<?xml version="1.0" " + "encoding="UTF-8"?>"); out.println( "<!DOCTYPE html PUBLIC "-//W3C//DTD " + "XHTML 1.0 Transitional//EN" " + ""http://www.w3.org/TR/xhtml1/" + "DTD/xhtml1-transitional.dtd">"); out.println(SvgGraphics.makeElement(false,"html", new String[]{"xmlns","http://www.w3.org/1999/xhtml", "xml:lang","en", "name","lang" })//end call to makeElement );//end println out.println("<head>"); out.println(SvgGraphics.makeElement( true, "meta", new String[]{"http-equiv","content-type", "content", "image/svg+xml; charset=UTF-8" })//end call to makeElement );//end println out.println("<title>Generated XHTML file</title>"); out.println("</head>"); out.println(SvgGraphics.makeElement( false, "body", new String[]{"id","body", "style","position:absolute;" + "z-index:0;" + "border:1px solid black;" + "left:0;" + "top:0;" + "width:220px;" + "height:440px;" })//end call to makeElement );//end println //Go process the root (document) node. This method // call triggers a recursive process that will //process the entire DOM tree. processNode(out,node); //The entire DOM tree has been processed when control // returns to this point. //Now finish the output document and flush the output // buffer. out.println("</body></html>"); out.flush(); }//end processDocumentNode //----------------------------------------------------// //There are seven kinds of nodes and most of them were // handled in the earlier program from which this // program was derived: // root or document // element // attribute // text // comment // processing instruction // namespace //However, because the nodes in this program are // produced by the program and it is known in advance // the types of nodes that will be encountered, this // program is less general than the earlier program. // Therefore, this method only handles element nodes // and document nodes. void processNode(PrintWriter out,Node node){ try{ if (node == null){ System.out.println("Nothing to do, node is null"); return; }//end if //Process the incoming node based on its type. int type = node.getNodeType(); switch(type){ case Node.ELEMENT_NODE:{ //Start creating the start tag. String nodeName = node.getNodeName(); out.print("<" + nodeName + " "); //Now add each attribute to the start tag. NamedNodeMap theList = node.getAttributes(); int length = theList.getLength(); for(int cnt = 0;cnt < length;cnt++){ Node item = theList.item(cnt); out.print(item.getNodeName() + "="" + item.getNodeValue() + "" "); }//end for loop out.println(">");//close the start tag. //Process all XML child nodes recursively. processChildNodes(out,node); //The element and all of its children have been // processed when control returns to this point. //Create the end tag for the element. out.println("</" + nodeName + ">"); break; }//end case ELEMENT_NODE case Node.DOCUMENT_NODE:{ //No action is required other than to process // the child nodes recursively. processChildNodes(out,node); break; }//end case DOCUMENT_NODE }//end switch }catch(Exception e){ e.printStackTrace(System.err); }//end catch }//end processNode(Node) //----------------------------------------------------// //This method is called to recursively process // the child nodes belonging to a specified // node. void processChildNodes(PrintWriter out,Node node){ NodeList children = node.getChildNodes(); if (children != null){ int len = children.getLength(); //Iterate on NodeList of child nodes. for(int i = 0; i < len; i++){ //This is a recursive call. processNode(out,children.item(i)); }//end for loop }//end if children != null }//end processChildNodes //----------------------------------------------------// }//End class Svg03 //======================================================// //This is a proof-of-concept graphics class that // provides method calls for the creation of the following // DOM tree nodes: // A general node of any type // A linear gradient element. // A radial gradient element. // An ellipse. // A circle. // A rectangle. // A line. // A polyline. // A polygon. //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. 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. 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); 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. 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); 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. 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); 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 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(); document.setXmlStandalone(false); }catch(Exception e){ e.printStackTrace(System.err); System.exit(0); }//end catch return document; }//end getDocument //----------------------------------------------------// }//end class SvgGraphics |
Listing 31. Program code for Svg04.
/*File Svg04.java, |
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.
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
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
Miscellaneous
W3C Markup Validation
Service.
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.