You are on page 1of 199

On the Topology of Celtic Knot Designs

Gwen Fisher
Mathematics Department
California Polytechnic State University
San Luis Obispo, CA 93407
glfisher@calpoly.edu

Blake Mellor
Mathematics Department
Loyola Marymount University
Los Angeles, CA 90045-2659
bmellor@lmu.edu

Abstract
We derive formulas for counting the number of strands in a variety of knotwork designs inspired by
traditional Celtic designs, including rectangular panels, circular borders, rectangular borders, and half frames.
We include graphic examples for each of these types of designs.

1. Introduction

There is a long tradition of abstract geometric designs in the art of the Celtic peoples of ancient
Britain, Scotland and Ireland, including spirals, key patterns and, in the Christian era, knots and
interlacings [1]. Complex knotwork patterns were used profusely in the Celtic illuminated manuscripts,
such as in the Books of Durrow (early 5th to early 6th century), Kells (middle 6th to early 8th century),
Lindesfarne (late 7th century), and Grimbald of St. Bertin (early 11th century). In these manuscripts,
interlacing designs (both purely geometric, as in Figure 1, and incorporating animal figures) fill areas and
are used as borders for text and illustrations. Françoise Henry called these designs a “sacred riddle” [7],
and their symbolic meaning is a fascinating and unresolved issue in Celtic art history. James Trilling [10]
has theorized that knotwork designs were, like the crosses that they commonly accompanied, protections
against evil: the complex designs would trap and confuse the “evil eye”.

Figure 1: A knotwork design, from [5]

Whatever their meaning, the complexity of Celtic knotwork designs is evidence of substantial
mathematical sophistication [4], and their design and analysis lead to many mathematical questions.
Several authors [2, 9] have studied methods by which the Celtic artists may have constructed their
designs. Peter Cromwell [3] studied the symmetries of Celtic knotwork as strip patterns. In this paper we
look at a deceptively simple question: How many different components (closed loops) are there in a given
knotwork design? This is the first step towards measuring the “complexity” of a design, and perhaps (if
Trilling is correct) its protective powers. It is also a question particularly appropriate for Celtic art.
While knotwork and interlacing designs appear in many traditions, the designs are not always “closed off”
into a finite number of loops. In Islamic art, for example, interlace patterns are often part of an infinite
plane, in which some strands never close into loops at all [6].
In addition to assisting in the analysis of Celtic art, methods for determining the number of
components in a design can assist artists in creating new work. Some writers have claimed that the ancient
Celts used designs with a single component to represent continuity and eternity (though there seems to be
no clear evidence one way or the other) [9]. Modern artists may do the same or, conversely, prefer more
than one component so that the design may be colored with multiple colors.
We will not give a complete answer to the problem of counting components. Celtic knotwork designs
can easily become immensely complex – the design in Figure 1 is a relatively simple example, yet its
structure still exceeds our methods of analysis– and it will take much more work to develop a general
approach. We will only examine a few fundamental designs: the basic knotwork panels and their
extensions to rectangular and circular border designs. Our method is to study how the braiding in the
designs changes the order of the strands, so each design corresponds to some permutation of the strands.
With this viewpoint, to compute the number of components in the design, we count the number of cycles
in the permutation, which is a relatively easy mathematical problem. Along with the details of our results
we will provide several examples and illustrations.

2. Counting Components in Knotwork Designs

2.1 Knotwork Panels. The history of the development of Celtic knotwork designs is still a subject of
debate. One popular theory, proposed by J. Romilly Allen [1], is that Celtic knotwork (as well as other
interlace traditions) developed from plaitwork panels used in Roman decoration. The basic plaitwork
panel can be described using a square grid with p rows and q columns. The process is illustrated in
Figure 2. We begin with the grid, and in each square draw the inscribed square whose vertices are the
midpoints of the original square. Where two of the inscribed “diamonds” meet, we insert a crossing: if
the diamonds are meeting on a vertical line of the original grid, the strand connecting the bottom left to
the top right will be on top; if they are meeting on a horizontal line, the strand connecting the top left to
the bottom right will be on top. Finally, we erase the original square grid. (We could, of course, reverse
all the crossings; but this would not affect the number of components in the design.) To get the
traditional “ribbon” designs, as in Figure 3, we simply thicken the knot into a ribbon.

Figure 2: Constructing a basic 2 × 3 knotwork panel

More complicated knotwork designs are formed from the basic plaitwork panel by making breaks in
the pattern – erasing a crossing and rejoining the four strands in one of the other two possible ways (top to
top and bottom to bottom, or right to right and left to left). This leads to designs such as Figure 1.
However, in this paper we will only be considering the basic plaitwork pattern – as we will see, even this
is not a trivial problem!
The construction that we have given of the plaitwork pattern is more convenient for the
mathematician than for the artist – for a practical method for creating these panels (and many other
designs) see Bain [2] and Meehan [9]. Notice that these knots and links are alternating - as we travel
along one strand, the crossings alternate between over- and under-crossings. A component of a design is a
closed loop in the design. If the design has only one component, we will call it a knot; otherwise, we will
call it a link. Our first problem is to count the number of components in the basic panel design. It is well
known that a p × q panel will have one component when p and q have no common factors, and that a p ×
p square panel has p components [2, 3]. We will generalize these facts to give a formula for the number
of components in any panel. We will prove our formula in two ways – one that is more intuitive, and one
that introduces the mathematical machinery of permutations that we will use in the rest of the paper.

p=4 q=6 p = 12 q = 15
Figure 3: Examples of knotwork panels

Theorem 1: The number of components in a p × q knotwork panel is given by the greatest common
divisor gcd(p, q).

Figure 3 shows two examples of Theorem 1. In particular, the 4 × 6 panel has 2 = gcd(4, 6) strands,
and the 12 × 15 panel has 3 = gcd(12, 15) strands. We will first approach this result from an intuitive,
topological point of view. Since our construction of the knotwork panel begins with a square grid, every
line in the design has slope 1 or -1. Imagine an ant traveling along one strand in the panel, beginning at
the left side of the panel. Eventually the ant will traverse the entire component and begin to retrace its
steps – how long will this take? Since the strands only change direction at the boundaries of the grid, to
return to its starting point (facing in the same direction that it started) the ant will need to follow the
strand across the grid and back some number of times both horizontally and vertically. So the ant will
travel a distance 2pj vertically and a distance 2qk horizontally (for some integers j and k). (The factor of
2 is needed because the ant travels across the grid and back.) Since the ant is always traveling one unit
vertically for each unit horizontally (and vice-versa), this means that 2pj = 2qk, where j and k are integers.
For each complete circuit of the component, j (or k) represents the number of times the ant travels across
the panel and back in the vertical (or horizontal) direction, respectively. Our goal is to find the smallest
values of j and k for which this occurs (to find the first time the ant returns to its starting point). This
means that pj = qk = lcm(p, q), where lcm(p, q) is the least common multiple of p and q. Since pq =
p q
lcm(p, q)·gcd(p, q), we can conclude that k = and j = .
gcd( p, q ) gcd( p, q )
Since k is the number of times the ant walked across the full width of the panel in the horizontal
direction, it also gives the number of times the ant turned around at the left side of the panel. This
number did not depend upon which strand the ant started, so every strand must meet the left side the same
number of times. Since the total number of times all strands meet the left side of the panel is p, the
number of different strands is kp = gcd( p, q ) , as desired (we could make the same argument using q and j
and the top of the panel).
A nice consequence of this argument is that, since each component of the design meets the left (and
p q
right) side of the panel k = times and the top (and bottom) of the panel j = times:
gcd( p, q ) gcd( p, q )
Each component is, on its own, a k × j knotwork panel.
While this approach to the problem is very natural, it is difficult to extend it to other designs. We will
describe another approach, based on the mathematics of permutations, which extends very nicely to
several other designs. To begin with, imagine a vertical strip of a panel. A p × q panel has 2p strings
running horizontally across it, intertwining with each other (several, or all, of these strings may belong to
the same component). Starting at the left side, number the strings 1 to 2p from top to bottom (so strings 1
and 2 will be parts of the same component, as will 3 and 4, and so forth). As we follow the strings to the
right, their positions change. We will look at how these positions change as we move to the right across
one column of the original square grid; we denote this permutation σp. Figure 4 shows an example with p
= 4.

1 1 1 1
2
2 1 2 2
2 1 2
3 3 2 4
3 3 2 4
3 1
4 4 6 3 1
4
4 4 4 6
5 5 5 3
6 8 5 5 5 3
6 6 7 5 6 7
7 8 7 6 6 7 5
7
7 7
8 8

σ4 γ 3.5

Figure 4: Permutations of strings.

Permutations are often represented using cycle notation. For example, the permutation of the set {1,
2, 3} that sends 1 to 2, 2 to 3 and 3 to 1 is represented by the 3-cycle (123). The permutation of the set
{1, 2, 3, 4, 5} which sends 1 to 2 and 2 to 1, 3 to 5 and 5 to 3 and leaves 4 fixed would be written
(12)(35)(4). We can visualize these cycles as shown in Figure 5.

1 3
1

3 2
2 5

(123) (12)(35)(4)

Figure 5: Cycles in permutations

We can “multiply” permutations by performing one after the other and looking at the result. Products
of permutations are traditionally read from right to left. For example, the product (123)(12)(35)(4) of the
permutations in Figure 5 sends 1 to 2, and then 2 to 3, so the product sends 1 to 3. Similarly, we can see
that the product sends 3 to 5, and 5 to 1, so we have a cycle (135). Finally, the product fixes 2 and 4. In
cycle notation, we write that (123)(12)(35)(4) = (135)(2)(4).
Returning to our knotwork panel: In cycle notation, the permutation σ4 in Figure 4 is (12468753). In
general, the permutation is σp = (124...2p 2p-1 2p-3...53), which is always a 2p-cycle. When the strings
hit the left or right sides of the panel, they "bounce" with a permutation δ p = (12)(34)(56)...(2p-1 2p)
because neighboring strings switch places.
So if we follow the strings from the left side of the panel to the right and back again, bouncing off
each end, when we return to our original position (moving once again to the right), the total permutation
is given by the product w = δ p (σ p ) − q δ p (σ p ) q . We want to simplify this permutation and split it into
disjoint cycles (so no number appears in more than one cycle). At this point, each cycle will correspond
to a component of the panel. In fact, each component of the design corresponds to two of these cycles: If
we start at any point on the component, one cycle describes our path around the component moving in
one direction, and the other cycle describes the path if we move in the other direction.
First note that (σ p ) −1δ p (σ p ) −1 = δ p (this is easy to check and is left as an exercise). So
(σ p ) −1δ p = δ pσ p , which means that we can rewrite w as (δ p ) 2 (σ p ) 2 q = (σ p ) 2 q (since δp has order 2).
Now we use a fact about permutations: if we multiply a single cycle of length n by itself m times, the
product will consist of gcd(n, m) disjoint cycles. Since σ p is a single 2p-cycle, the number of disjoint
cycles in w is gcd(2p, 2q) = 2gcd(p, q). Since each component of the panel corresponds to two of these
cycles (traversing the component in both directions), the number of components is once again gcd(p, q).
In the following sections, we will extend this approach to several other basic designs.

2.2 Circular Borders. Circular motifs are also common in Celtic knotwork designs, often appearing as
elements in larger designs, such as stone carvings or illuminated manuscripts [2, 8]. They are also found
in modern designs influenced by the Celtic tradition.

Figure 6: The 2.5 × 12 circular border has 1 component and the 2 × 12 circular border has 4
components. The 1.5 × 3 circular border (the Borromean link) has three components.

The simplest circular knotwork design is made by bending a p × q knotwork panel into a circle, and
joining the left and right sides to get a p × q circular border. Three such designs are shown in Figure 6.
Cromwell [3] noted that knotwork panels must have an even number of horizontal strings in order to close
up (a p × q panel has 2p strings). In circular designs, however, there can be an odd number of strings, so
p can be a half-integer as well as an integer. Figure 6 shows examples of each type. There is, once again,
a simple formula for the number of components in the design. The simplest circular borders are well
known to knot theorists, although by other names – the 1 × 3 circular border is the trefoil knot, and the 1.5
× 3 circular border is the Borromean link. Figure 6 shows the Borromean link; a tiny trefoil knot can be
seen in the center of the design.
Theorem 2: The number of components in a p × q circular border is gcd(2p, q).

Notice that when p is a half-integer, 2p is odd, so the design will have an odd number of components.
As an example, in Figure 6, the figure on the left has gcd(5, 12) = 1 component, the figure in the middle
has gcd(4, 12) = 4 components, and the Borromean link has gcd(3, 3) = 3 components. We will prove
Theorem 2 using the permutation technique we developed in Section 2.1. We first consider the case when
p is an integer, so the permutation across one “diamond” is once again σp. In the circular case, the
permutation of the strings as we travel once around the design is w = (σ p ) q . So, w splits into gcd(2p, q)
disjoint cycles, each of which corresponds to a different component of the design. Unlike the case in
Section 2.1, we are only following the strings in one direction, so we do not need to divide by 2. So the
number of components is gcd(2p, q).
On the other hand, if p = k + 1/2 for some integer k, then we need to consider a slightly different
permutation. In this case, there are 2p = 2k + 1 strings winding around the design. The permutation of
the strings as we move one unit clockwise around the design (i.e. one "qth" of the way around) is given by
γp = (1 2 4...2p-1 2p 2p-2...5 3), a 2p-cycle. The right illustration in Figure 4 shows the case when p =
3.5. So the permutation as we make a complete circuit of the design is w = (γ p ) q . Since γp is a 2p-cycle,
the number of disjoint cycles in w is once again gcd(2p,q), as desired.

2.3 Rectangular Borders. Our methods are easily adapted to looking at rectangular borders, or frames of
constant width. In general, frames have three parameters: the height p, the breadth q and the width (of
the band) n. We will refer to such a frame as a p × q frame of width n. Note that n, as for circles, may be
either an integer or a half-integer, such as 1.5. Figure 7 shows several examples.

Figure 7: Examples of frames: 5 by 5 of width 2, 5 by 5 of width 1.5, 5 by 6 of width 2, and 5 by 7 of


width 2

The number of components in a frame design is given by the following theorem:

Theorem 3: The number of components in a p × q frame of width n is equal to 2gcd(|p – q|, n) (if n is an
integer) or to gcd(|p – q|, 2n) (if n is a half-integer k + 1/2, with k an integer). Here we require 2n < min
(p, q).

Notice that, as with circles, if the width of the frame is an integer then there is an even number of
components, and if the width is a half-integer then there are an odd number of components. If we apply
this result to the examples in Figure 7, we see that the designs (from left to right) have 2gcd(5 – 5, 2) = 4
strands (0 is divisible by anything), gcd(5 – 5, 3) = 3 strands, 2gcd(6 – 5, 2) = 2 strands and 2gcd(7 – 5, 2)
= 4 strands. Once again, we will use permutations – as with the circular borders, the arguments are
simpler and more natural than with the panels, since we do not need to worry about “bouncing” off the
sides. The permutation of the strings of the design as we go along the sides is given by σ n or γ n,
depending on whether n is an integer or a half-integer. In addition, we need to determine what happens as
we "turn a corner" in the design. We will denote the permutation of the strings around a corner by αn if n
is an integer, and by βn if n is a half-integer. Figure 8 illustrates α4 and β3.5.
In general, we can see that αn = (12)(34)(56)...(2n-1 2n); in fact, αn is the same as δn from Section 2.1.
Similarly, βn = (12)(34)(56)...(2n-2 2n-1)(2n) (note that in this case 2n is odd). Recall from Section 2.1
that (σ n ) −1α n = α nσ n . It is also easy to show that (γ n ) −1 β n = β nγ n .

1 2 3 4 5 6 7 8 1 2 3 4 5 6 7
1 1 2 1 2
1
2 2 1 2 1
2
3 3 4 3 4
3
4 3 4 3
4
5 5 6 4
5 6
6 5 5
6 6 5
7 8
7 6 7 7
8 7 7
8

α4 β 3.5
Figure 8: Permutation of strings around corners.

If n is an integer, the permutation of the strings around the frame is given by the product of the four
sides and four corners:
w = (σ n ) q − 2 n α n (σ n ) p − 2 n α n (σ n ) q − 2 n α n (σ n ) p − 2 n α n

( )
2
= (σ n ) q − 2 n α n (σ n ) p − 2 n α n

= ((σ ) (α ) ) = ((σ ) )
2 2
q − 2n
n (σ n ) 2 n − p n
2
n
q− p
= (σ n ) 2( q − p )
The number of disjoint cycles in this permutation (and hence the number of strands in the design) is
therefore gcd(|2(q – p)|, 2n) = 2gcd(|p – q|, n).
If n = k + 1/2, the permutation of the strings around the frame is given by
w = (γ n ) q − 2 n β n (γ n ) p − 2 n β n (γ n ) q − 2 n β n (γ n ) p − 2 n β n
= (γ n ) 2( q − p )
The number of disjoint cycles is therefore gcd(|2(q – p)|, 2n) = gcd(2|q – p|, 2k + 1). Since 2k + 1 is
odd, the greatest common divisor must be odd, so we can ignore the factor of 2 in the first term. So the
number of strands of the design is equal to gcd(|p – q|, 2n).

2.4 Half-frames or L shapes. We can apply the techniques we have developed to a wide range of
designs involving a strip of constant width that makes right angle turns. A simple example of such a
design is half of a frame, or an L shape. A p × q L of width n has height p, base length q and is made
from a strip of width n. As with panels, n must be an integer. Examples of L shapes are shown in Figure
9.

Theorem 4: The number of components (strands) in a p × q L shape of width n is gcd(|p – q|, n).
Figure 9: Examples of L shapes:4 by 5 of width 2, 5 by 5 of width 2, 6 by 6 of width 2.

We can check this formula on the examples in Figure 9. The design on the left has gcd(5 – 4, 2) = 1
strand, the design in the middle has gcd(5 – 5, 2) = 2 strands, and the design on the right has gcd(6 – 6, 2)
= 2 strands. We prove the formula using the same methods as before. Using the permutations we have
defined in the earlier sections, the permutation of the strings in the L is obtained by beginning at the top,
traveling to the bottom, turning a corner, traveling to the end, "bouncing" and eventually returning to our
original position (after turning the corner and bouncing off the top to face down again). This permutation
is w = δn (σ n ) −( p − n )α n (σ n ) −( q − n )δn (σ n ) q − n α n (σ n ) p − n . Using the relations we developed in the earlier
sections, we can reduce this to
w = (σ n ) p − n (σ n ) −( q − n ) (σ n ) −( q − n ) (σ n ) p − n = (σ n ) 2( p − q ) .
So the number of disjoint cycles is gcd(2|p – q|, 2n) = 2gcd(|p – q|, n). As with knotwork panels, each
component of the design is counted by two of these cycles, so the number of components is gcd(|p-q|, n).

3. Conclusion

Obviously, there is much more work to be done. Another common design is the Celtic Cross, and many
other designs do not involve strips of constant width, but have strips of different widths joining with each
other – these will require new techniques to analyze. And, of course, we have not even begun to analyze
the effects of “breaking” the basic plaitwork pattern. We have presented a few basic ideas and results, but
these are only a first step.

4. References

[1] Allen, J.R., Celtic Art in Pagan and Christian Times, Methuen, London, 1904
[2] Bain, G., Celtic Art: The Method of Construction, Dover, New York, 1973
[3] Cromwell, P.R., “Celtic Knotwork: Mathematical Art”, Mathematical Intelligencer, Vol. 15, No. 1,
1993, pp. 36-47
[4] Doran, B., “Mathematical Sophistication of the Insular Celts – Spirals, Symmetries, and Knots as a
Window onto their World View”, Proceedings of the Harvard Celtic Colloquium, Vol. 15, 1995, pp.
258-289
[5] Gillman, P., http://www.boulder.net/~gillman/prism/drawing.html
[6] Grunbaum, B. and Shephard, G.C., “Interlace Patterns in Islamic and Moorish Art”, Leonardo,, Vol.
25, No. 3/4, 1992, pp. 331-339
[7] Henry, F., Irish Art in the Early Christian Period, Cornell University Press, Ithaca N.Y., 1965
[8] Laing, L. and J., Art of the Celts, Thames and Hudson, London, 1992
[9] Meehan, A., Celtic Design: Knotwork, Thames and Hudson, New York, 1991
[10] Trilling, J., “Medieval Interlace Ornament: the Making of a Cross-Cultural Idiom”, Arte Medievale,
Vol. 9, 1995, pp. 59-86
ACTIVITY
LEARN TO DRAW SHEET

ALCUIN AND THE CAROLINGIAN RENAISSANCE


A TEACHING RESOURCE
CELTIC KNOTWORK 9

Alcuin and Charlemagne believed it On the right is an example of Celtic


was important to preserve and knotwork from the Lindisfarne
disseminate ancient learning. They Gospels. A whole page of decoration
encouraged monks to copy many like this is known as a ‘carpet page’.
manuscripts. One of the most Alcuin would probably have been
important monasteries where this familiar with this type of decoration.
took place was St Gall, in modern-
day Switzerland. Today St Gall Although this example is quite
houses one of the most important complicated, it is not very difficult to
medieval libraries in Europe. The draw simple knotwork patterns. You
monastery of St Gall was established can learn how to do this by using
by Irish monks, and the manuscripts templates 1, 2 and 3 on the NCEM
they produced were often decorated website and by following the
by artwork influenced by Celtic instructions below, step by step.
styles. Carpet page from the Lindisfarne Gospels,
British Library, reproduced with
permission.
STEP 1 STEP 3
Begin by joining the lines in the top left- When you have finished, your know should
hand corner of each square to the lines look like example 4.
at the bottom right-hand corner. Try to
keep the pairs of lines the same distance If you wish, you can go around the lines
apart, and the curves as smooth as you have drawn in black ink. Now colour
possible. Example 1 shows a few lines your knot. Start by colouring the loop,
Example 1
drawn in red to show you what to do. and then do the background. When it is
At this stage, join only the lines on the finished, your knot will look something like
top left to the lines on the bottom right. this.
Work your way through square by
square. Draw the lines in pencil so that if
you make a mistake you can rub it out
and draw it again.

Check that you have joined the lines in


Example 2
each square. When you have finished,
your knotwork should look like example
2.

STEP 2
Now you need to join the lines in the
top right-hand corner of each square Example 3
to the lines at the bottom left-hand
corner. Every time you are about to In this design, the knot makes one
cross a line that you have already continuous loop. This is not always
drawn, stop and start again on the the case. Different designs produce
other side of the line. This will make it different numbers of loops. Template
look as if the line goes underneath the 2 contains more than one loop.
one you have already drawn, as in Complete the design and colour it in
example 3. to see how many loops it makes.
Example 4

Page 1
ACTIVITY
LEARN TO DRAW SHEET

ALCUIN AND THE CAROLINGIAN RENAISSANCE


A TEACHING RESOURCE
CELTIC KNOTWORK 9

Now use template 3 and try designing your own knot. There are 531,441 different
knots you can make with this template, so it is very likely that your knot will be unique.

There are four rules to follow:

RULE 1
Where the squares meet, the pairs of lines must both be vertical or horizontal or
diagonal:

or or

RULE 2
At the top and bottom of the knot, the lines must all be horizontal

RULE 3
The diagonal lines must look like this: And not like this:

RULE 4
When joining the lines, always begin by drawing all the lines from top left to bottom
right before drawing the lines from top right to bottom left.

Thanks to Andy Sloss for help in the production of this activity sheet. For more information on Celtic
knotwork, see Sloss, A., How to Draw Keltic Knotwork, a practical handbook, 1996, Brockhampton Press.,
London.

Page 2
Basic Celtic Knotwork Drawing
Interlaced Rectangle
http://www.illuminatedspaces.com/ithreads/ by Kathy Powe

This is the interlaced rectangle Celtic knotwork (woven looking) drawing that we are going to
make. We will also learn a few steps that we can use to design and draw other knot
configurations based on this basic rectangle.

MATERIALS:
- Pencil – I prefer a mechanical pencil so that I’m not always sharpening it.
- Ruler or straight edge – If doing measurements, you need a ruler. If just drawing, a
straight edge can help. If you are brave, you can draw your lines without either one.
- Eraser – Inexpensive white erasers can be your friends.
- Paper – I prefer graph paper at least for the design phase because it allows me to skip
fiddly time consuming measurement steps. Different sizes of graph squares are readily
available in stores and various software can also create the desired graph squares to make
the size of knotwork drawing that you want. For this tutorial, we are going to wind up
with a 4 x 3 inch rectangle. We will use graph paper with ¼ inch squares. That can give
the final line width of about 3/8 inch shown in the example above. Note that the drawing
illustrations in this tutorial are not to scale.
- Transfer materials – If you are planning to transfer your design for embroidery, quilt
design, or incorporation into a different artwork, use appropriate transfer materials and
method.

START:

1. Decide what size drawing that you want. In this tutorial, our size is given at 3 x 4 inches.
Near the top of the page, lightly draw a box 4 inches (16 squares) wide and 3 inches (12
squares) tall. The sides of the box are called walls. This is because the lines must stop or go
around a wall for a knot.
Basic Celtic Knotwork Drawing – Interlaced Rectangle Page 2

Inside the box, lightly make dots as shown. Note that the outside edge of the dots are all ½
inch (2 squares) from the walls and 4 squares (1 inch) horizontally and vertically from each
other.

Figure 1a

2. Begin drawing lines.

Some people like to draw all of the lines in one direction and then all the lines in the other
direction. Some like to start in one place and follow that one line all the way until the
pattern is complete – kind of like doodling. Either way works, although the doodling type
method requires keeping more steps in your head at one time. A combination of the two
methods can work well in creating new designs or filling unusually shaped spaces.
Doodling until a pleasing design is established can then be followed by polishing and
finalizing with the more regimented method. We already know our design, so this tutorial
will address the more regimented method of drawing an interlaced rectangular Celtic knot.

Draw all your lines in one direction, making sure they cross half way (2 squares) between
the outside dots (Figure 2a). You can skip a space half way in line with the next vertical dot
or erase that space when done drawing the lines (Figure 2b).

OR
Figure 2a Figure 2b
Basic Celtic Knotwork Drawing – Interlaced Rectangle Page 3

3. Draw all your lines in the opposite direction making sure they also cross half way (2
squares) between the outside dots. Skip a space where they cross the other solid lines. Note
that the skipped spaces with the solid lines drawn through them causes the drawing to begin
to look woven or interlaced.

Figure 3a

4. Draw points on the corners and round or square off where the lines if continued would bump
into a wall (the outside box).

Figure 4a

If you are satisfied with this line design for your purposes, you can erase the dots and the box
and transfer to other media for your intended use of the drawn Celtic knotwork.

OR

You can use this line design as the basis for other designs. To make the wider line or ribbon
width drawing at the start of the lesson:
Basic Celtic Knotwork Drawing – Interlaced Rectangle Page 4

5. Start with your dots in the square the same as you did in steps 1 and 2.

Figure 1a (again)

6. Similar to step 3, draw all your lines in one direction, except make sure that your lines cross
1 square after and 1 square before the outside dots. Plan on erasing the spaces at the
intersections that show the weaving when done drawing the lines.

Figure 6a

7. Repeat step 6 in the other direction.

Figure 7a
Basic Celtic Knotwork Drawing – Interlaced Rectangle Page 5

8. Similar to step 4, draw points on the corners and round or square off where the lines if
continued would bump into a wall (the outside box). Remember that you are representing a
wide line or ribbon instead of the single line so both sides of that ribbon need to be drawn.

Figure 8a

9. Starting inside the ribbon near the top, follow the ribbon all the way through the design and
erase every other pair of lines as you encounter an intersection. If done correctly, the woven
interlacing of over and under, over and under, should become apparent. This effect can be
enhanced with shadowing. When done, erase the dots and the box.

Figure 9a (8a with dots and lines erased) Figure 9b (same thing with shadows)

10 More walls can be inserted to add more interest. Walls must be perpendicular and/or
horizontal, never diagonal. The same rules that we used for the outside box apply wherever
a wall occurs – draw points in corners and round or square off where the lines/ribbons bump
into a wall. The other steps remain the same.
Basic Celtic Knotwork Drawing – Interlaced Rectangle Page 6

Figure 10a (erased vertical wall in center)

11 Rectangles can be joined for other shapes:

Figure 11a (vertical and horizontal walls in center and dots kept on purpose)
Basic Celtic Knotwork Drawing – Interlaced Rectangle Page 7

Figure 11b (could be one corner of a border)

12.Further enhancements can be done by turning the single ribbon into more than one. You can
make designs in the ribbons. You can color the background. Or you can take this wherever
YOUR imagination takes you.

Figure 12a Figure 12b

There are many methods used for drawing Celtic knotwork, including software specifically
designed for the purpose. Tutorials are available on the internet when “drawing Celtic
knotwork” is searched. There are also many books with a variety of instructions. This is the
method that I have found that works best for me.
Drawing Celtic Knots
This method, like most, begins with drawing the grid. Of course you could use
graph paper, but that would limit the size of your finished pattern to the sizes of
graph paper that you happen to have available. What size should you make the
grid? Well that depends on what size you want your final pattern to be. If you are
making a pattern for leatherwork, for instance, you would want to draw the
pattern to be the size of the final project; say - wallet size. How many squares
should you make? That is a tougher question. The number of squares depends
on the pattern that you are going to make. The problem is that we often don't
know what sort of pattern we want at this stage. So what do we do? Guess? Pick
out a pattern that someone else created and copy it? We just have to pick a
number of squares that fits into our final size and forge ahead hoping that the
resulting pattern will look good and that we aren't wasting our efforts. This grid is
4 squares tall and 6 squares wide.

The second step is to draw in the diagonals. These lines will be the path followed
by most of the knotwork. The diagonal lines must be drawn starting from the
middle of the sides of the grid squares. In other words, don't draw the diagonals
from the points of the grid squares or you'll end up with loose ends sticking out at
all four corners.

Next draw in the connecting curves that will become the edges of the knotwork. I
make these curves tangent to the diagonals. In other words they connect
smoothly to the diagonal lines.
This is the step that most people have the most trouble with: deciding where to
"Break the Grid". The thing that makes Celtic knotwork unique from the plaiting
(or weaving) seen in many other cultures is the introduction of "BREAKS" into the
weave. I've always found it to be rather fascinating: that Celtic knotwork is
defined more by where the pattern ISN'T than where the pattern IS. It is a design
form made of empty spaces within an otherwise solid object.

So, where do we put these spaces to make an aesthetically pleasing design? A


person with extraordinary visualization skills could probably imagine what a knot
will look like, but for the average person it is a "try-it-and-see" sort of operation.
Here we find the biggest single problem with ALL of the traditional Celtic knot
work construction methods: you have to know what your knot is going to look like
BEFORE you design it!

1). Copy what I did here (or someone else's designs from a book).

Or

2). Make some breaks by trial and error and see what happens. If you are lucky it
will look good. If you aren't lucky you will waste a bunch of time drawing an ugly
knot and have to erase it all and start over, or get disgusted and quit. The red
lines on our grid show where I am going to make my breaks.

Follow the diagonals until you come to a red line. When you get to a red line, add
a curve so that your line continues into the other line that is broken at the same
place. Adding these curves completes the path of the knotwork. Darken this line
so that we can see the path clearly for the next step.
OK, now that you spent all of that time drawing the grid, the diagonals and the
breaks, erase all of it except the knotwork path that we darkened in the last step.
Here is what it should look like.

For this step a computer graphics program comes in real handy. We need to
make a new outline all around the entire knotwork path. You'll have to eyeball it.
Draw a line all the way around the knotwork path and inside all of the little holes
in the pattern. The new line should be offset from the original path by 1/2 of the
desired width of the finished knotwork cord.

When you are done with doubling the line, erase the original knotwork path so
that all that is left is the outline.

We're almost there! Hang in there just a little while longer and we will have a
knot!

All that is left is to figure out the "Over / Under Thing". You can start anywhere on
the path of the knot and make 2 little lines at each intersection. Be sure to
alternate one way and then the other, over and under, until all of the intersections
are done or until you run into an area where you find that you went OVER-OVER,
or UNDER-UNDER. In which case you erase lines until you've eliminated the
ones that are wrong and do it over. Look at the next illustration and you can see
what I mean. Of course if you had used the Celtic Knot Font you wouldn't have to
worry about the "Over / Under Thing" because that has been done for you
already.

So, there you have it, a completed Celtic knot! It looks pretty good. Of course I'm
a professional graphic artist who has done this sort of thing for a living for over a
decade, so your results may vary. Because I am fairly proficient at this, it only
took me about 2 hours to make this knot for this tutorial.

From:

Isdell, Daniel L. “2 Ways to Draw Celtic Knotwork: A Side-by-side Tutorial Comparing


Two Different Methods!” Clan Badge. 2011. Web. 6 June 2013.

The second way to do this would be to go to Clan Badge and download the Celtic Knot
font and just type your knot. - http://www.clanbadge.com/tutorial.htm
Digital illumination

Dr. Alun Moon


School of Informatics
University of Northumbria
Newcastle upon Tyne, UK
alun.moon@unn.ac.uk

Abstract
Donald Knuth has given us Digital Typography, and through METAFONT, Digital
Calligraphy; this paper explores how these tools can be used for Digital Illumi-
nation. It follows from my interest as an amateur calligrapher in Celtic artwork.
Two examples of my work are in figures 1 and 2. Compare with a sketch of
an element from the Lindesfarne Gospels in 3 (Bain, 1989, pg. 67), which is the
destination I’m reaching for. This has been a very good exercise in learning to
write macros for METAPOST.

Figure 1: A cartouche with spiral inserts.

Some background
Figure 2: A brooch inspired by elements of the
Celtic artwork in Britain covers the period from the Tara brooch.
7th century BC through the 7th century AD. Dur-
ing that time examples can be found in stone carv-
ing, intricate metalwork, and, towards the end of
the period, in illuminated manuscripts. The Celtic
monastic scribes produced such masterpieces as The
Book of Kells and The Lindesfarne Gospels. Lindes- curves be described and then a METAFONT algo-
farne itself is about 60 miles north of Newcastle; the rithm used to split them up to generate the under–
Gospels are thought to have been produced at Jar- over–under–. . . pattern?
row on the south bank of the Tyne. These show a A keypattern does have a base form that is then
highly developed artistic style, with very fine, intri- tiled to form the page. The base pattern does have
cate detail. There are three main styles considered a simple sequence of numbers that define it. A se-
here: knots, keypatterns, and spirals. quence such as (1, 1, 2, 2, 7, 2, 2, 1, 1) gives a pattern
Knots and keypatterns can be drawn from block such as This pattern can then be tiled .
elements treated as characters, and large carpet Can these simple sequences be used to program
pages built from these standard elements. However, METAFONT to generate larger patterns?
the Celtic scribes show a high degree of geometry Similarly, spiral patterns can be constructed us-
and geometrical construction in their work. ing a pair of compasses. How can METAFONT’s ge-
A knot can be described as one or more strands ometrical programming be used as a digital pair of
that loop, cross and re-cross many times. Can the compasses to create these beautiful patterns?

18 TUGboat, Volume 24 (2003), No. 1 — Proceedings of the 2003 Annual Meeting


Digital illumination

vardef crossings@#(text others) =


save lastpt, tmp;
p@#t[0]:=0;
p@#t#:=0;
forsuffixes $=others:
numeric lastpt;
lastpt := epsilon;
forever:
numeric tmp;
(tmp,whatever)=
Figure 3: Sketch of a cartouche in the subpath (lastpt,length(p@#)-epsilon)
Lindesfarne Gospels (2 in long). of p@#
intersectiontimes p$;
exitif (tmp<=0);
Knotwork p@#t[incr p@#t#] := if(tmp<1):
tmp[lastpt,ceil(lastpt)]
Interlacing patterns of weaving cords is possibly the else:
best known and most widely recognised form of Celt- floor(lastpt)+tmp
ic artwork. fi;
Given a set of curves, once the intersections are lastpt := p@#t[p@#t#]+epsilon;
known and sorted along the paths, drawing the in- endfor
tersections is easy. For an array of paths p[], two endfor;
numeric arrays are needed. One holds the times of sort.p@#t;
the intersections p[]t[], the other a count of inter- enddef;
sections p[]t#. This way p1 t3 is the 3rd intersec-
tion on the 1st curve, and p3 t# is the number of in- Figure 4: crossings function.
tersections on the 3rd curve. The intersections can
be found with the intersectiontimes operator in
METAPOST and METAFONT. Drawing the crossings The crossings are drawn
A function crossings takes a suffix parameter using the erase draw technique, as described in The
and a text parameter. The suffix is the path for METAFONTbook (pg. 113). The erasing segment is
which the crossings are to be found, and the text is drawn between the midpoints of the sections on ei-
a list of paths to test for (see figure 4). ther side of the crossing point, the line is then drawn
slightly longer. This avoids gaps in the lines where
Intersection times The intersectiontimes op- the erasing began.
erator tends to generate points at the beginning of
Examples With the crossing macros, any knot-
the path. To iterate along the path a series of sub-
work pattern can be drawn, as long as there are the
paths are used. Each one starts just past the last
paths p[] defined. There is one caveat, each path
intersection (time plus epsilon), up to the end of
must start so that its first crossing is over the path
the path.
it crosses. This makes all the crossing times the odd
There is a small problem using subpaths with
numbers in the array of times.
the intersectiontimes operator; the time returned
is the time for the subpath. The path z1 ..z2 ..z3 ..z4 The trefoil The trefoil is a simple knot using four
has length 4, while the subpath [.75, 1.25] has length paths (figure 6). Some people claim it symbolises
2. Points have been added to the beginning and end the Holy Trinity, or wholeness (I like it because it is
where there are not points of the original path. The the motif used for my wedding).
intersection time on the subpath (ts ) can be con-
verted to a time on the full path as follows: Border A common theme is a knotwork border,
a simple example is shown in 7 after (Bain, 1989,
• if ts < 1, use ts to interpolate between the be- pg. 29). Once the paths are specified, application
ginning of the subpath (a) and the next point of the crossings and drawcrossings macros gen-
on the curve (ceiling of a). erate the knots.
• if ts ≥ 1, add it to the last point on the curve Better knots Because a circular pen is used for
before the subpath (floor a). both erasing and drawing the lines, there is a limit
For a simple knot the global intersection-times for to how wide the line can be before the ends of the
one of the paths is shown in figure 5. strokes become visible.

TUGboat, Volume 24 (2003), No. 1 — Proceedings of the 2003 Annual Meeting 19


Dr. Alun Moon

Bain (1989) uses a numeric notation to describe the


core patterns, the inspiration for one approach.
S-spiral generation A sequence of numbers such
0.51149 as (1, 2, 3, 4, 9, 4, 3, 2, 1) defines a curve; each num-
ber is the length of the segment. Each new segment
0.71165 is drawn at right-angles to the last. Turning anti-
clockwise in the first half, where the lengths are in-
creasing. Clockwise in the second, where the lengths
decrease.
The macro is shown in figure 8. It maintains a
1.28835 copy of the maximum length drawn, to test whether
1.48863 to turn clockwise or anti-clockwise.
def keySspiral(text tail) :=
Figure 5: Times for intersection points. begingroup
save direct,lastpoint,maxlength;
pair direct,lastpoint;
direct := up rotated -90;
lastpoint := origin;
maxlength := 0;
origin
for p=tail: --
begingroup
direct := direct
rotated if (maxlength<=p):
begingroup maxlength := p;
90 endgroup
else:
-90
fi;
lastpoint := lastpoint + direct*p;
lastlength := p;
Figure 6: The trefoil.
lastpoint
endgroup
endfor
A better method would be to generate the
endgroup
points that form the end of each stroke. This can be
enddef;
combined with the penpos and penstroke macros.
This requires a little more mathematics. Once the
Figure 8: S-spiral generator macro.
intersection is known, a time on the path is needed
to give a point a given distance from the intersec-
tion. Tessellated curves The sequence (1, 2, 3, 4, 8, 4, 3,
2, 1) gives a curve that tessellates to fill a region
Keypatterns
(figure 9). Typically a blank border surrounds the
Keypatterns are a common border or filling element. keypattern; the clip operator in METAPOST serves
Usually the base element is a C or S spiral, which well to form the border.
is then repeated to fill the space. The edges use a Programmed variation A curve that can tessel-
separate pattern that fills in around the basic shape. late in such a way that it interlocks with itself (fig-
ure 10) can be generated by the sequence (1, 2, 3, 4,
9, 4, 3, 2, 1).
With METAFONT we have a powerful program-
ming tool that can vary the pattern as it is drawn,
in ways that the seventh century scribes could not
easily do. Figure 12 shows the border from figure
10 varying in intensity; figure 11 shows the width of
Figure 7: A knotwork border. the line varying.

20 TUGboat, Volume 24 (2003), No. 1 — Proceedings of the 2003 Annual Meeting


Digital illumination

def spiral(expr a,b,$)(expr turns) =


$
.. $ rotatedaround(a, 90)
.. $ rotatedaround(a, 180)
if( turns>1 ):
& spiral(b,a,
$ rotatedaround(a, 180))
(turns-1)
fi
enddef;
Figure 9: Space-filling keypattern.
Figure 13: Spiral macro.

Figure 10: Interlocking keypattern.

Figure 12 is also interesting as the scale of the


lengths (and pen width) is 1 pt. It looks fine on a
monitor, but may test the limits of a printer. META-
FONT allows even finer detail, limited only by the
resolution of the printer.
Figure 14: Cartouche inspired by figure 3.
Spirals
Spirals are another signature element of Celtic art-
work. Meehan (1993b) shows how the spiral ele- find the points on the two curves giving the common
ments can be drawn using two, three or four offset tangent.
centres and a pair of compasses. METAFONT draws
three point paths (z0 ..z1 ..z2 ) as close to a circular Some resources
arc as possible (Knuth, 2000, pg. 128). Over the years I have found several books to be of
Given an initial point, a pair of centres, and a use. George Bain (Bain, 1989) is often cited as the
number of turns, the spiral macro is a very simple key work. He has collected a wide range of mate-
recursive function (figure 13). Although it could be rial from the Gospels, Book of Kells, jewelry, arti-
just as simple with a loop, swapping the centres over facts and stone carvings. It doesn’t go into many
is easier to do with the recursive call. of the construction techniques, but is a very good
Figure 14 shows a cartouche inspired by fig- source of inspiration. Aidan Meehan has produced
ure 3. Each pair of spirals is joined by a path con- a series of books (Meehan, 1993a; Meehan, 1993b),
necting the outer points. This path really should which give a step-by-step approach and give some
follow a common tangent to the two curves forming good ideas as to the construction of the geometry.
the outer end of the spiral. A macro is needed to Sheila Sturrock’s book (Sturrock, 2001) has a differ-
ent approach to building the keypatterns and clearly
shows how the borders develop.

Figure 11: Keypattern varying with line width.

Figure 12: Keypattern varying with colour. Figure 15: Joined spirals.

TUGboat, Volume 24 (2003), No. 1 — Proceedings of the 2003 Annual Meeting 21


Dr. Alun Moon

Andy Sloss has two books with a radically dif- References


ferent technique (Sloss, 1997a; Sloss, 1997b). He
Bain, George. Celtic Art: The Methods of Con-
enumerates all possible combinations of crossings,
struction. Constable and Company Ltd, 1989.
characterised by the four entry and exit directions
ISBN 0 09 461830 5.
of the two strands. These can then be laid out on a
grid. This would be eminently suitable for conver- Knuth, Donald. The METAFONTbook. Addison
Wesley, 2000.
sion to a font (assuming enough characters). Per-
haps a task for the long winter evenings. Meehan, Aidan. A Beginner’S manual. Celtic
Design. Thames and Hudson, 1993a.
Finally ISBN 0 500 27629 3.

What new Illumination can be produced by a tool as Meehan, Aidan. Spiral Patterns. Celtic Design.
highly versatile as METAFONT? Can the transfor- Thames and Hudson, 1993b. ISBN 0 500 27705 2.
mations in Metafont implement conformal mapping Sloss, Andy. How to Draw Celtic Key Patterns.
and be applied to patterns generated as above? Pre- Blandford, 1997a. ISBN 0 7137 2652 0.
liminary experiments suggest it can, but the trick is Sloss, Andy. How to Draw Celtic Knotwork. Bland-
finding the mapping to use. If the patterns can be ford, 1997b. ISBN 0 7137 2492 7.
described in a parametric form, can an Escher-like Sturrock, Sheila. Celtic Spirals and Other Designs.
tiling be achieved where the pattern changes across Guild of Master Craftsman Publications Ltd,
the page? Again yes, but finding a shape that scales 2001. ISBN 1 86108 159 6.
and still fits together is difficult.
The so-called “Dark Ages” produced a flower-
ing of the work of Celtic scribes, culminating in the
“Golden Age” of the Scribes’ art. Knuth has given
us tools to usher in a Golden Age of Typesetting
and Digital Illumination.

22 TUGboat, Volume 24 (2003), No. 1 — Proceedings of the 2003 Annual Meeting


Creating Celtic Knot Work From User Parameters

Christopher Bailey

Bachelor of Science in Computer Science with Honours


The University of Bath
April 2008
This dissertation may be made available for consultation within the Uni-
versity Library and may be photocopied or lent to other libraries for the
purposes of consultation.

Signed:
Creating Celtic Knot Work From User Parameters

Submitted by: Christopher Bailey

COPYRIGHT
Attention is drawn to the fact that copyright of this dissertation rests with its author. The
Intellectual Property Rights of the products produced as part of the project belong to the
University of Bath (see http://www.bath.ac.uk/ordinances/#intelprop).
This copy of the dissertation has been supplied on condition that anyone who consults it
is understood to recognise that its copyright rests with its author and that no quotation
from the dissertation and no information derived from it may be published without the
prior written consent of the author.

Declaration
This dissertation is submitted to the University of Bath in accordance with the requirements
of the degree of Batchelor of Science in the Department of Computer Science. No portion of
the work in this dissertation has been submitted in support of an application for any other
degree or qualification of this or any other university or institution of learning. Except
where specifcally acknowledged, it is the work of the author.

Signed:
Abstract

This project aimed to produce a software package capable of generating Celtic knot work
from user parameters. The package developed and presented here generates Celtic knot
work based on an undirected graph entered by the user. The software does not use prede-
termined sections of knot work in the generation process instead the user’s graph gives the
base structure for the knot to be built around. The software fills a gap in knot generating
software as it is platform independent and generates Celtic knot work based on user input.
Contents

1 Introduction 1
1.1 Project Aim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 This Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

2 Literature Review 3
2.1 Celtic Artwork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1 Insular Pre-Roman Artwork . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Celtic Art In Roman Britain . . . . . . . . . . . . . . . . . . . . . . 5
2.1.3 Dark Age Celtic Art . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.4 Christian Celtic Art & The Illustrated Manuscripts . . . . . . . . . . 8
2.2 Celtic Knot Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.1 Why Knot Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.2 Rules & Principles of Celtic Knot Work . . . . . . . . . . . . . . . . 11
2.2.3 The 8 Basic Knots . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 Mathematical Knot Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3.1 Origins of Knot Theory . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3.2 The Study Of Knot Theory . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.3 Uses Of Knot Theory . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3.4 Celtic Knot Work & Knot Theory . . . . . . . . . . . . . . . . . . . 17
2.4 Computer Generated Celtic Knot Work . . . . . . . . . . . . . . . . . . . . 18
2.4.1 The Tile Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4.2 Algorithmic Approach . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

i
CONTENTS ii

3 Problem Description & Requirements Analysis 25


3.1 Problem Description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2 Non-Functional Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3 Functional Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.4 Summary Of Key Requirements . . . . . . . . . . . . . . . . . . . . . . . . . 28

4 Design 30
4.1 The Celtic Knot Creation Process . . . . . . . . . . . . . . . . . . . . . . . 30
4.2 The Knot Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2.1 Representation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2.2 Knot Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2.3 The Celtic Knot Class . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.3 Graph Production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3.1 Graph Area . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3.2 Storage & Representation Of Vertices & Edges . . . . . . . . . . . . 38
4.3.3 Saving & Loading Of Graphs . . . . . . . . . . . . . . . . . . . . . . 38
4.3.4 Software Generated Improvements . . . . . . . . . . . . . . . . . . . 39
4.4 Knot Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4.1 Calculate Crossing Components . . . . . . . . . . . . . . . . . . . . . 40
4.4.2 Calculate Straight Line Components . . . . . . . . . . . . . . . . . . 44
4.4.3 Calculate Curved Line Components . . . . . . . . . . . . . . . . . . 46
4.4.4 Calculate Pointed Return Components . . . . . . . . . . . . . . . . . 48
4.4.5 Calculate Control Points For Curves . . . . . . . . . . . . . . . . . . 54
4.5 Linking The Knot Components . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.6 Drawing & Displaying The Knot . . . . . . . . . . . . . . . . . . . . . . . . 62
4.7 Knot Preferences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.7.1 Colours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.7.2 Line Thicknesses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

5 Implementation 64
5.1 The Knot Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
CONTENTS iii

5.2 The Celtic Knot Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65


5.3 The Graph Area . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.4 Generating The Knot Components . . . . . . . . . . . . . . . . . . . . . . . 66
5.4.1 Calculating The Crossing Components . . . . . . . . . . . . . . . . . 66
5.4.2 Calculating The Straight Line Components . . . . . . . . . . . . . . 66
5.4.3 Calculating The Curved Components . . . . . . . . . . . . . . . . . 67
5.4.4 Calculating The Pointed Returns . . . . . . . . . . . . . . . . . . . . 69
5.5 Linking The Knot Components . . . . . . . . . . . . . . . . . . . . . . . . . 69
5.6 Drawing The Knot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

6 Testing & Evaluation 71


6.1 Mercat’s Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.2 The Knot Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.3 The Effect Of Different Graphs . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.3.1 Symmetry Of Knots . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.4 Celtic Knot Work In Borders . . . . . . . . . . . . . . . . . . . . . . . . . . 78
6.5 More Examples Of Celtic Knot Work . . . . . . . . . . . . . . . . . . . . . . 78
6.6 Testing Against Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

7 Conclusion 82
7.1 Background & Literature . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
7.2 The Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.3 Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.3.1 The Software Generated Improvements . . . . . . . . . . . . . . . . 84
7.3.2 Knot Tutor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
7.3.3 Other Knot Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
7.4 Overall Project Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

A Sample Celtic Knots 88

B Source Code 91
CONTENTS iv

B.1 Package: knot.gui . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91


B.1.1 File: ButtonPanelColours.java . . . . . . . . . . . . . . . . . . . . . 91
B.1.2 File: ButtonPanelGraphCreation.java . . . . . . . . . . . . . . . . . 101
B.1.3 File: ButtonPanelKnotCreated.java . . . . . . . . . . . . . . . . . . 104
B.1.4 File: GridGraphView.java . . . . . . . . . . . . . . . . . . . . . . . . 105
B.1.5 File: KnotFileFilter.java . . . . . . . . . . . . . . . . . . . . . . . . . 115
B.1.6 File: KnotFileReaderWriter.java . . . . . . . . . . . . . . . . . . . . 116
B.1.7 File: MainWindow.java . . . . . . . . . . . . . . . . . . . . . . . . . 120
B.1.8 File: PictureFileFilter.java . . . . . . . . . . . . . . . . . . . . . . . 122
B.2 Package: knot.structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
B.2.1 File: CelticKnot.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
B.2.2 File: Crossing.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
B.2.3 File: CurvedLine.java . . . . . . . . . . . . . . . . . . . . . . . . . . 133
B.2.4 File: KnotComponent.java . . . . . . . . . . . . . . . . . . . . . . . 135
B.2.5 File: PointedReturn.java . . . . . . . . . . . . . . . . . . . . . . . . . 136
B.2.6 File: StraightLine.java . . . . . . . . . . . . . . . . . . . . . . . . . . 138
B.3 Package: knot.generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
B.3.1 File: Generator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
List of Figures

2.1 Celtic Shields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5


2.2 Stone Female Head . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Mosaics Using Celtic Knot Work . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 Celtic Brooch Found in Bettystown, Ireland . . . . . . . . . . . . . . . . . . 8
2.5 Carpet Page From The Book Of Durrow . . . . . . . . . . . . . . . . . . . . 9
2.6 Stylised First Letter In The Book Of Durrow . . . . . . . . . . . . . . . . . 10
2.7 Carpet Page From The Book Of Kells . . . . . . . . . . . . . . . . . . . . . 12
2.8 Stylised First Letter In The Book Of Kells . . . . . . . . . . . . . . . . . . . 13
2.9 The Eight Basic Celtic Knots . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.10 Example Tile of Knot Work . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.11 Knot Produced Using Tile Method . . . . . . . . . . . . . . . . . . . . . . . 19
2.12 The Marc Wallace Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.13 The Christian Mercat Method . . . . . . . . . . . . . . . . . . . . . . . . . . 24

4.1 Class Diagram For Knot Structure . . . . . . . . . . . . . . . . . . . . . . . 33


4.2 The Four Knot Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.3 Example Edge & It’s Crossing . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.4 Calculating The Offsets Required To Position Crossing Points . . . . . . . . 43
4.5 Crossing Lines On The Same Infinite Line . . . . . . . . . . . . . . . . . . . 45
4.6 Finding The Crossing Points For Curved Components . . . . . . . . . . . . 48
4.7 Connecting Crossings In Pointed Returns . . . . . . . . . . . . . . . . . . . 51
4.8 Calculating X & Y Offsets For Straight Lines In Pointed Returns . . . . . . 52
4.9 Calculating The Common Return Point . . . . . . . . . . . . . . . . . . . . 54

v
LIST OF FIGURES vi

4.10 Overlapping Curve Control Points . . . . . . . . . . . . . . . . . . . . . . . 56


4.11 Knots With Different Number Of Cords . . . . . . . . . . . . . . . . . . . . 59
4.12 Knots With Different Number Of Cords: Annotated . . . . . . . . . . . . . 60

5.1 Knots From The Same Graph With & Without Additional Constraint . . . 68

6.1 Software Implementation Of Mercat’s Method . . . . . . . . . . . . . . . . . 72


6.2 A Complete Celtic Knot Generated By The Software . . . . . . . . . . . . . 73
6.3 Overlapping Components Using Thick Cord . . . . . . . . . . . . . . . . . . 74
6.4 Highlighted Knot Components Generated By The Software . . . . . . . . . 75
6.5 Transforming One Knot To Another: Starting & Ending Knots . . . . . . . 76
6.6 Transforming One Knot To Another: Key Steps . . . . . . . . . . . . . . . . 77
6.7 Breaking Symmetry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
6.8 Border Knot Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

A.1 Square Knot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88


A.2 Nested Squares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
A.3 Basic Cross . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
A.4 Rectangle Intersected By Line . . . . . . . . . . . . . . . . . . . . . . . . . . 90
A.5 Hexagon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Acknowledgements

I would like to thank Dr Claire Willis for the initial idea and for her continuing support
and advice throughout the project. Special thanks to Sarah Affleck for the motivation
and encouragement she has provided throughout the project. I would also like to thank
my parents for their thoughts, ideas and opinions during some of the most frustrating and
difficult times within this project.

vii
Chapter 1

Introduction

Celtic knot work has fascinated people ever since it originated, right the way through to
the modern era. Celtic knot work is immediately recognisable to many people due to the
‘over and under’ pattern of the knot cord and the knots distinctive overall form. It is often
the over and under pattern the cord takes and the apparent simplicity of Celtic knot work,
which gives the knots their style and beauty.
The motifs found in Celtic knot work have been used in a variety of areas since appearing,
although their use has changed over time. Between the 7th and 9th centuries Celtic knot
work was heavily used; some of the best and most elaborate examples can be found in the
religious manuscripts produced during this time. After the 9th century the use of Celtic
knot began to decline in Britain, but remained a strong influence in Ireland; many of the
designs that we view as Irish in origin have Celtic knot work within them. Although the use
of Celtic knot work decreased in Britain it never actually died out and there are examples of
its use and influence right up to the modern era. During the 20th century Celtic knot work
went through a revival and its use increased, although not to the same level as it reached in
earlier periods of history. The modern revival moved from decorating manuscripts with the
Celtic patterns to creating jewellery, clothing and art work inspired by or directly featuring
knot work.
There are several key designs which feature prominently within Celtic knot work; these
designs have changed very little from the 7th century to the present. This has lead to
well established procedures and processes for creating Celtic knot work; the majority of
these processes are for creating knot work by hand with pen and paper. There are several
computer programs available to generate Celtic knot work, implementing a range of different
methods, however there is a gap in the available software. Currently there appears to be no
inexpensive cross platform software which can create Celtic knot work based on parameters
entered by the user. Software exists that meets one or two of these conditions but not all
three.

1
CHAPTER 1. INTRODUCTION 2

1.1 Project Aim

The aim of this project is to produce a platform independent software package that can
produce Celtic knot work based on user parameters. The user should be able to make a
considerable difference to the knot work being produced as well as affecting trivial factors
(e.g. the knot’s colour). Ensuring that the user’s decisions have a major effect on the
outcome of the knot should encourage the user to make use of the software and allow them
to see how their decisions influence the knot work produced.

1.2 This Document

This document presents the work carried out for the project. Chapter 2 details a review
of appropriate literature to the project. The literature covers the artistic background and
influences to Celtic knot work, the history and significance of knot work and some of the
methods and approaches to creating Celtic knot work. The software requirements for the
project are explained in Chapter 3. The requirements discussed in this chapter are used
to form a list of key requirements that need to be considered in the design of the software.
Chapter 4 presents the initial software design to meet the requirements from the previous
chapter. The design covers all aspects of the software, although its main focus is the process
to create Celtic knot work. The implementation of the design is discussed in Chapter 5.
This chapter includes the alterations and changes from the initial design and why they
have been implemented. Chapter 6 explains how the software developed and the knots it
produces have been evaluated. Future work that this project may contribute to and the
conclusions that can be drawn from this work are shown in Chapter 7.
Chapter 2

Literature Review

This section aims to give the reader an overview of the research conducted for this project
and what has been learned from it. The research covers multiple areas including Celtic
artwork, knot theory and some of the approaches to generating Celtic knot work.

2.1 Celtic Artwork

It is some what difficult to define any particular period in history as Celtic (Megaw &
Megaw, 2001, p9-12); although many periods are commonly referred to as Celtic. This
makes defining what constitutes Celtic artwork harder than art work of other well defined
periods in history. In modern times the British Isles and Ireland between the 5th and 12th
centuries are often called Celtic, especially when referring to artwork from this period. It
is widely believed that the term Celt is derived from Keltoi, a term used by the ancient
Greeks (Laing & Laing, 1992, p7). The Greeks classed the Iron Age tribes of Europe (those
from southern Germany in particular) as Keltoi, but they never extended this definition to
Britain or Ireland. The Greeks classed tribes with similarities in their language and culture
as Keltoi, although each tribe was independent of each other.
The Celtic period in history is thought to cover a vast time frame ( 500BC - 1100AD) and
spread across the entire continent; as such the artwork produced varies depending on the
time and location that it was produced. There are several features that persist in almost all
Celtic artwork throughout the centuries. The use of vivid colour, non narrative style and
lack of the human form (with a simplified / abstract human head being the exception) are
common throughout Celtic art. Some features however are more specific to certain periods
or regions which are discussed later. This project focuses on one particular area of Celtic
art knot work and interlacing; as such the information following will concentrate on the
development and influences for Celtic knot work.

3
CHAPTER 2. LITERATURE REVIEW 4

2.1.1 Insular Pre-Roman Artwork

Defining exactly when the Celtic period begins in Britain and Ireland is difficult due to lack
of direct dateable evidence. It is believed that small groups of settlers would have brought
their Celtic culture with them when they came to Britain, for instance the Arras culture
in Yorkshire which may have come from northern Gaul. Links with the Celtic continent
were reasonably strong during the iron age and many aspects of Celtic culture may have
emerged through trade. This leads many people to believe that Celtic culture in Britain
and Ireland grew and developed over time as it was absorbed into the existing local culture.
There are few examples of Celtic artwork produced on the continent that have been found
in Britain and Ireland from this period; the pieces that have been found may well have
been imported by the wealthiest members of the communities or have belonged to rich
immigrants (Laing & Laing, 1992, p93). There is evidence that British Celts were sometimes
influenced by artwork produced by their counterparts on the continent, especially when
looking at the artwork found on weapons and horse related accessories (Megaw & Megaw,
2001, p192-202). Several swords and scabbards have been found in the north of Ireland
and in Britain with artistic similarities to those produced in central Europe. These feature
pseudo birds’ heads, openwork chapes1 , hatched comma leaves2 with hair spring spirals3
at their ends and diagonal patterns all of which can be found on similar pieces from the
continent. Although theses pieces have aspects of European design, they also include
uniquely British and Irish traits such as cross hatching in the background, full coverage
designs, engraving and repoussé4 techniques. Celtic artwork in this period contains many
of the common Celtic features including flowing curves, triskels5 , abstract human faces and
animals. During this time the most commonly used material were metals including bronze,
iron, gold and silver. Wood work was carried out but in much lower quantities than metal
working.
As time progressed much of Europe fell under Roman control and the style of artwork in
Britain and Ireland diverged from the rest of the continent. Pieces produced in Ireland and
the British Isles became more elaborate, complicated and abstract than those produced
abroad. The artwork continued to contain many of the Celtic elements it had previously.
Making the design more elaborate by containing more detail and covering larger areas be-
came more common. Two shield covers are prime examples of this, the first was discovered
in the River Witham dating from around the 2nd century BC and the second found in
the River Thames (near Battersea) from the 1st century BC. Both feature three roundels,
with the one at each end being supported by a head. As you can see in figures 2.1(a) &
2.1(b) the Witham shield has lots of open space, a relatively simple design and the head
supporting the roundels is well defined. The design on the Battersea shield uses a much
larger area and is far more elaborate.
1
Chape - Metal mounting on scabbard
2
Comma Leaves - S like shape
3
Hair Spring Spiral - A tightly wound spiral
4
Repoussé - A method of metal working where the design is raised by hammering it from the back
5
Triskel - A symbol with three protrusions and a threefold rotational symmetry
CHAPTER 2. LITERATURE REVIEW 5

(a) Witham Shield (b) Battersea Shield

Figure 2.1: Celtic Shields


The
c Trustees of the British Museum

2.1.2 Celtic Art In Roman Britain

During the Roman occupation of Britain the Celtic artists had to change their styles and
methods, especially in areas where Roman control was strongest. Celtic art began to
incorporate Roman styles and ideas, for example artwork featured more straight lines than
it had previously and the Celts increased their use of die casting processes. Much of the
Celtic art work prior to the occupation was devoted to war and would be found on weapons,
horse trappings etc. As Roman control increased the Celtic artists had to find other areas
to continue their trade, as the Romans would not have wanted them to continue to produce
the tools of war. At this point we see a massive increase in the amount of smaller, personal
items such as broaches, boxes and other forms of jewellery that are decorated with the
Roman - Celtic style.
In areas where Roman influence and control was lower than the south of Britain, Celtic
art continued in a similar fashion to before the occupation. In Ireland for example little
changed and the Irish Celts continued to produced artwork in the pre-Roman style; the
CHAPTER 2. LITERATURE REVIEW 6

Irish continued to produce artwork for weapons and for horse trappings.
Up until this point in time Celtic artwork did not feature realistic representation of the
human form. A stylised abstract human head (with oval eyes, straight mouth and little or
no contours) was often used. This representation was getting more abstract prior to the
occupation e.g. the Battersea Shield (2.1(b)). Under Roman rule Celtic artists began to
produce artwork where the human head was represented far more accurately than previ-
ously. In many pieces Celtic features such as the oval eyes remained, but they all began
to model facial structure including the brow, nose and checks as can be seen in figure 2.2.
Interlace and knot work begins to emerge on a larger scale during the Roman occupation.

Figure 2.2: Stone Female Head


The
c Trustees of the British Museum

The majority of the interlace can be found on mosaics from across Britain, although ex-
amples can be found on jewellery and other metalwork. The style of the interlace in the
mosaics is distinctly Roman from which the distinctive Celtic style knot work will develop
over time. Figures 2.3(a), 2.3(b) & 2.4 show some examples of interlace in Roman Britain.

2.1.3 Dark Age Celtic Art

The Romans brought many new ideas, methods and other aspects of culture to Britain
during their occupation. As their power declined in the late 3rd century some aspects of
life in Britain began to shift back to the way they were before the occupation. There is little
CHAPTER 2. LITERATURE REVIEW 7

(a) Mosaic Center Piece (b) Mosaic From The Bank Of England

Figure 2.3: Mosaics Using Celtic Knot Work


The
c Trustees of the British Museum

evidence of mosaics, fresco painting or three dimensional wood and stone work after the
official withdrawal by the Romans in 410 AD. The Romans introduced many religions to
Britain during their occupation, Christianity was the only one to have a lasting effect after
their control ceased. Christianity in Britain would decrease with the numerous invasions
over the next few hundred years, but it would continue to survive in some areas of the
country.
Much of the art work produced at this time featured the more traditional Celtic ideas such
as curves and triskels, with the elaborate style seen prior to the Roman occupation. Cross
hatching of the backgrounds also continued, however it became more common to use forms
of interlace in the background. Between the 4th and the early 7th centuries we see the style
of interlace develop from a very Roman style in the early period to a style which is almost
identical to what we now call Celtic knot work.
The Catach of St Columba (produced late 5th - early 6th century) (Megaw & Megaw, 2001,
p250-251) is one of the first known examples of Celtic artistic style being used to decorate
a religious artefact, where the artefact is from a non Celtic religion and is written in a
non Celtic language. This piece also breaks from Celtic tradition by being one of the first
pieces to be produced using ink on vellum; prior to this the majority of Celtic artwork had
been produced using metal, stone and wood. This piece is the forerunner to illustrated
manuscripts we shall see later.
CHAPTER 2. LITERATURE REVIEW 8

2.1.4 Christian Celtic Art & The Illustrated Manuscripts

By the second half of the 7th century Christianity became increasingly common throughout
the British Isles and Ireland. Christianity provided new patronage for the Celtic artists,
which required the use of narrative art for the first time.
The Book of Durrow is one of the earliest examples of a Christian manuscript being illus-
trated with a mixture of Celtic, Germanic and Coptic styles. The Book of Durrow was
produced in the late 7th century, its exact location of origin is unknown, although it thought
to have been produced either in Northumbria or in Ireland (Megaw & Megaw, 2001, p252).
The book is thought to be the first produced in Britain and Ireland to feature so called car-
pet pages (Figure 2.5), which use all over coverage and are normally found at the beginning
of each gospel. Interlaced borders are common within the book especially on the carpet
pages. The style of the interlace appears to be heavily influenced by Coptic manuscripts
which often featured interlaced patterns. Some of the more traditional features associated
with Celtic art are still present in the book, many patterns feature triskels, spirals and
flowing curves. The representation of people and saints often are drawn in Celtic style with
only the head being shown and faces being 2 dimensional (although more detail appears
than the earliest representations). Images of the Book of Durrow can be found on pages 9
& 10.
The increasing use of interlace and knot during this period is also seen in the metal work
produced by the Celtic artists. A brooch (Figure 2.4) found in Bettystown, Ireland dating
from the 8th century demonstrates how intricate the knot work was becoming. The brooch
also shows the increasing trait of filling all available space when decorating objects.

Figure 2.4: Celtic Brooch Found in Bettystown, Ireland


CHAPTER 2. LITERATURE REVIEW 9

Figure 2.5: Carpet Page From The Book Of Durrow


CHAPTER 2. LITERATURE REVIEW 10

Figure 2.6: Stylised First Letter In The Book Of Durrow


CHAPTER 2. LITERATURE REVIEW 11

The 8th and 9th centuries produced some of the most sophisticated and complex manuscript
illustration in the Lindisfarne Gospels, the Book of St Chad and the Book of Kells. All
of these have major similarities to the Book of Durrow, but they are often more ornate.
It is within these manuscripts that we see the style of interlace that we commonly call
Celtic. The knot work has distinctive features such as pointed returns and uniform cord
widths. The use of other Celtic motifs and styles is still very apparent with the use of
roundels, triskels and animals all featuring. The Book of Kells (Figures(2.7 & 2.8)) is the
most elaborate of all the manuscripts, the level of detail found on the carpet pages and
the on the exaggerated first letter of a gospel goes far beyond the Book of Durrow. Like
Durrow, Kells features different types of interlace, including combining animals with more
traditional forms of knot work. Pages 12 & 13 show a carpet page and a stylised opening
first letter from the Book of Kells.

2.2 Celtic Knot Work

2.2.1 Why Knot Work

Knot work is often the first thing people think about when considering Celtic artwork,
however it is just one aspect of Celtic artwork. Knot work is a fascinating area to study
and analyse especially from a computing and computer software point of view. Like the rest
of Celtic artwork, knot work has adapted and evolved over the centuries and has a timeless
elegance and beauty. Some of the most beautiful and intricate manuscripts in Britain and
Ireland have been decorated using Celtic knot work. Modern jewellery and other items of
fashion often use Celtic knot work as a core theme or a major influence, which suggests
that people still enjoy and relish these patterns.
Knot work in general (see Section 2.3) has been an area of study of mathematicians and
computer scientists for a long period of time and thus Celtic knot work is also of interest
as it covers many of the same areas and topics.

2.2.2 Rules & Principles of Celtic Knot Work

From studying Celtic knot work it is possible to create a set of rules and guidelines which
define what makes a particular knot Celtic in style. It is rare to find knots by Celtic artists
that do not follow the rules, where such examples exist there is not a universally accepted
reason for the break from tradition. Reasons for not following the rules have included
simple human error and reluctance to presume that a human work could be perfect in the
eyes of God(Bain, 1990, p50). Several authors(Thinky Things, n.d.; Bain, 1990) describe
the rules and guidelines in different manors, a summary of these is shown below.

• Over and under pattern - Knot work is produced so that intersections alternate in
an over then under fashion. If a cord goes through 2 consecutive intersections, it will
pass under the intersecting cord at one and then go over at the next intersection.
CHAPTER 2. LITERATURE REVIEW 12

Figure 2.7: Carpet Page From The Book Of Kells


CHAPTER 2. LITERATURE REVIEW 13

Figure 2.8: Stylised First Letter In The Book Of Kells


CHAPTER 2. LITERATURE REVIEW 14

• Uniform cord width - All of the cord(s) within the pattern are the same width through-
out; if the pattern changes in the knot then the width of the cord may also change.
• Pointed returns - When a cord turns back on itself, the corner of the turn often has
straight edges and the corner is a point rather than a U shape or a curve.
• Repetition - The pattern used is repeated throughout the knot. This often leads to
the knot being symmetrical on several planes.
• Single cord - Many knots are formed from one cord which turns back on itself to form
the pattern.
• Continuity of path - The overall direction of the knot is consistent. For example if
you are at one end of the knot it will always appear to travel toward the other end.
• The spiral look - Knots may appear to spiral due to the pointed returns and the over
and under pattern.

The first three can be classed as the rules and knots that differ from this are very rare
and often classed as errors(Bain, 1990, p54). The remaining points are principles and
generalisations that often appear in Celtic knot work and following them is not essential
but may produce more attractive artwork. A knot that does not follow the generalisations
may still be Celtic in style, it may just appear different to the vast majority of Celtic
knots. The last two points in the list refer to how the knot appears to a viewer; they are
not necessarily guidelines that can be followed by an artist. These points may just be side
effects of creating the knot. Most Celtic knots do appear to travel in one direction at a time
(if a corner is turned in the knot area the direction changes); it is entirely possible however
for this knot to consist of a cord which travels from one end to the other and back again.
The illusion of the knot travelling from end to end is often due to the cord not coming
back on itself quickly or the return path having close proximity to the outbound path. The
cord within a Celtic knot often appears to spiral, this is another illusion which results from
following the rules when creating the knot. If looking at a pointed return (particularly cord
returns not on the knot’s edge) and you follow the cord in both directions to the next cord
intersections, one will pass under the cord and the other over. Giving the impression that
one side of the return is higher than the other, combined with the return it appears that
the cord is spiralling.
Following the rules when designing Celtic knot work is reasonably simple task which will
result in knots with a Celtic style. Designing a system that is capable of checking if a knot
breaks the rules should also be a simple matter. Interpreting and using the guidelines is
a much more difficult proposition. When looking at some of the most attractive Celtic
knot work, it is obvious that it follows these guidelines. It is this area which requires the
most artistic talent and where many people struggle. Developing a technique or system
that can make accurate suggestions which uses the guidelines in an artistic manor would
be of considerable benefit to those with out an artistic flare. Many artists will know when
something is not at its best and what changes to make to correct the piece; encoding this
into a concrete process is where the difficulty lies.
CHAPTER 2. LITERATURE REVIEW 15

2.2.3 The 8 Basic Knots

J. Romilly Allen listed a series of knots in their book (Allen, 2001) that make up the
majority of Celtic knot work. There are other knots used in pieces of artwork, however
these are the most common and make a large number of Celtic patterns. Celtic knots are
often found to use either one or two of the basic knots or almost all of them combined. The
knots using a small number of the basic patterns will repeat the pattern to fill the space
being used. Where as knots using most of the patterns will pick a small number to form
the core of the knot and the remaining patterns for the rest of the knot. Illustrations of
these knots can be seen in figure 2.9.

(a) Knot 1 (b) Knot 2 (c) Knot 3 (d) Knot 4

(e) Knot 5 (f) Knot 6 (g) Knot 7 (h) Knot 8

Figure 2.9: The Eight Basic Celtic Knots

2.3 Mathematical Knot Theory

Knot work has been studied by mathematicians for a considerable amount of time, more
recently it has also become an area of interest by computer scientists as the discipline had
evolved. The study of knot work is normally called knot theory by mathematicians as its
study is concerned with the theory and principles behind knot work.

2.3.1 Origins of Knot Theory

Knots have been studied for a long period of time, although its study has become more
formal over the last 2 centuries. The modern day study of knot theory is said to have
originated with three Scottish physicists Sir William Thomson (a.k.a Lord Kelvin), Pe-
ter Guthrie Tait and James Clerk Maxwell. Thomson believed that atoms were actually
made up of vortex rings6 . Victorian scientists believed in a perfect invisible fluid called
the ether(Connor, 2004; Silver, n.d., p7) and many scientific theories (especially those of
6
A vortex ring is a region of rotating fluid, which takes a ring shape(Wikipedia, n.d.)
CHAPTER 2. LITERATURE REVIEW 16

physicists) were based upon this fluid. Thomson formed his theory about the structure of
atoms being knotted tubes of swirling ether after seeing Tait perform tricks with a smoke
ring box(Brown, n.d.; Silver, n.d.). At the beginning of time a creator had imparted vortex
motion onto the ether, some of which had formed into the knotted vortex rings making the
elements we see today. Thomson speculated that different vortex ring knots formed the
different elements, thus chemical properties were a result of topology(Silver, n.d.).
Maxwell and Tait both became deeply interested in knot work, probably as a result of
Thomson’s theory. Maxwell focused on studying properties of knots and links and acted as
a source of encouragement for Tait and his work. Almost a decade after Thomson published
his theory, Tait began a program to try and catalogue different knots in the belief that he
was creating a table of elements. Tait lacked a good scientific method for proving that each
knot he draw was unique and thus a different element; he relied on good geometric skills
and confidence7 . Tait’s original work focused on knots with 6 or less crossings but this
was insufficient to account for all elements; he expanded his work to cover knots with 7
crossings, but found this was also insufficient. The following quote from Tait in 1877 showed
is reluctance to catalogue knots with more crossings, due to the difficulties of carrying out
this process.

Eight and higher numbers are not likely to be attacked by a rigorous process
until the methods are immensely simplified.

Evidence for the existence of the ether has never been found and Thomson’s theory began
to fade as new theories were developed. Knot theory however continued to grow as new
uses were discovered and as those interested in geometry took up the challenge.

2.3.2 The Study Of Knot Theory

Much of the work on knot theory focuses on proving that two knots are different from
each other. A knot is said to be the same as another knot if you can deform one into the
other. The first knot can be twisted or deformed but the cord must not be broken at any
stage. Showing two knots are the same is a relatively simple task as one knot can be made
to look the same as the second, proving two knots are different is a much more difficult
task(Brown, n.d., p35). There are infinite ways to deform any given knot, thus trying each
possibility and showing this is different from another knot is also an infinite task.
A partial solution to this problem came with the introduction of knot invariants. Invariants
can be represented by a diagram such that if two knots are made from the same invariant
they can be expressed with the same diagram. In the 1920’s Reidemeister showed that two
knots are equivalent if and only if one can be transformed into the other in using five basic
moves(Brown, n.d.). The first is to distort the knot with out altering any of its crossing
points. The other four steps are different ways to manipulate the cord and the crossing
7
In the 1920’s new techniques were developed which proved Tait’s catalogue was on the whole correct
CHAPTER 2. LITERATURE REVIEW 17

points (still without breaking the knot). These four manipulations will not fundamentally
effect a knot invariant and are used as a test to see if a knot is invariant.
Knots can often be created by combining two or more knots and thus be broken down into
distinct separate knots. A knot which can not be broken down is called a prime knot. As
with numbers in arithmetic a knot that is not prime can be constructed from a collection
of prime knots. Breaking a knot into prime knots is used when trying to determine if a
knot is unique, as a prime knot is a knot invariant by definition.
Currently there is no algorithmic way to reduce any knot to simpler knots or to the prime
knots that it is made from. Work is still being carried out in this area along with trying to
find more knot invariants if they exist.

2.3.3 Uses Of Knot Theory

Since Thomson’s theory of atoms faded scientists and mathematicians have found other uses
for knot theory. The most notable of these is in relation to DNA. It is possible for DNA to
become knotted, which can make it harder for it to function correctly. Understanding the
knotting that occurs in DNA may lead to a greater understanding of how DNA works and
its effects.
Knot theory also plays a part in molecular biology and chemistry, quantum computing and
aspects of weather analysis and prediction.

2.3.4 Celtic Knot Work & Knot Theory

Knot theory covers all forms of knotting and as such Celtic knot work can be seen as a sub
set of this field. Mathematicians and scientists study and use knot theory to help answer
specific questions about real life problems. Where as Celtic knot work is used in artistic
works and as a source of design inspiration.
As Celtic knots are a sub set of all possible knots, the principles of knot theory still apply.
Celtic knots can be constructed by combining other knots, provided the new knot still
conforms to the Celtic knot rules. Often a knot being examined will be almost identical in
general knot theory and in Celtic form. The trefoil is an excellent example of this, in the
Celtic version the returns are pointed where as in a general knot they will be rounded. In
this instance this is the only difference between the two knots.
If a major breakthrough occurs in knot theory, the benefits would also be applicable to
Celtic knots. For example if a reliable algorithm is created that can break a knot into its
prime knots, it could be applied to any knot Celtic in style or not. From a design point
of view this would be a great help in teaching knot drawing and creation techniques; as
it would make demonstrating the simple prime knots within a complex knot easier. Once
some one had mastered the creation of a reasonable number of prime knots, showing them
how a seemingly very complex knot could be broken down would be a much simplified task.
CHAPTER 2. LITERATURE REVIEW 18

2.4 Computer Generated Celtic Knot Work

Several different programs have been created to produce Celtic inspired knot work. To
produce knot work patterns a range of methods exist; which can be summarised into two
types of approaches, algorithmic methods and the tile method. These approaches can
produced very similar work in some cases although it is possible for them to produce very
distinct pieces.

2.4.1 The Tile Method

The first approach to producing computer generated knot work uses a relatively simple
but effective method that relies on pre-drawn knot work sections (called tiles) and a set
of rules. To begin this method requires a collection of pre-drawn knot work sections; if
this collection is comprehensive then it is feasible to create any knot. Within each of these
sections the cord(s) will always leave at one or more of the corners (sections are normally
squares, as these are the easiest shapes to fit together later). When leaving a section the
cord can travel in a diagonal, horizontal or vertical direction and example of this can be
seen in figure 2.10. In this example section all three of the different exits are shown. The
top left corner shows a cord exiting horizontally, the bottom right corner shows a vertical
exit and the other two are where the cord leaves diagonally.

Figure 2.10: Example Tile of Knot Work

A set of rules govern which tiles can be placed near each other. Obviously each exit from
a section must match up with the cord on the tile next to it or the knot would not be
continuous. If you consider a tile whose right edge contained horizontal exits at both the
top and bottom, the tile on the right must have cords exiting it horizontally on its left
edge.
The knot workspace is made up of a grid and the tiles will be used to populate this grid.
There are special tiles to go in the cells that make up the edges of the grid. These tiles
must contain cord patterns which turn back into the grid, without these the cords would
just end at the edge of the grid.
CHAPTER 2. LITERATURE REVIEW 19

The knot generation process is a series of steps or stages outlined below.

1. The user is presented with a blank grid and a starting position (normally one of the
corner cells)

2. A list of tiles that can start the knot is shown to the user, once they have chosen the
tile they want it is inserted into the grid.

3. The system moves to the next free cell in the grid8 .

4. A list of acceptable tiles is generated and presented to the user. This list is determined
by:

(a) Is the current cell on the grid edge or corner?


• Yes - Use the edge / corner set of tiles
• No - Use the standard (non-edge) set of tiles
(b) Select the tiles from the chosen set where their cord exits match the cord exits
on all the surrounding cells. If a surrounding cell is blank then any exit on that
edge is acceptable.

5. The user can then choose the tile they want and it is inserted into the grid at the
current location.

6. Repeat steps 3 to 5 until all cells in the grid have been populated with a tile

This method does allow users to produce knot work quickly and it lends itself to computer
programs as each tile can be treated as an image file in its own right. To produce a knot
the application must simply load the selected tile into a grid of tiles which can be saved
as an image when the user has finished. Adding extra tiles is a simple process as they
can just be inserted to the collection along with information about their cord exits. In
its current state this method allows users to produce very free form knots with limited
conditions (such as the knot being continuous). This method does not enforce some of the
basic principles that make up Celtic knot work. Figure 2.11 shows a knot built with this
method, as you can see the knot is not symmetrical and looks completely different from
what might be expected in a Celtic knot. To make this process produce knots which follow

Figure 2.11: Knot Produced Using Tile Method


8
This is normally the next cell in the row, or the first cell in a row if the previous cell was a row end
CHAPTER 2. LITERATURE REVIEW 20

more of the Celtic principles the rules used would have to be expanded greatly. The rules
would still have to consider the cord exits from the sections but they would have to also
consider a tiles role in the knot as a whole. It is feasible to make the rules look at every
tile currently in use and the pattern of cord exits and then ensure that the tiles it allows
the user to choose from conform to the existing pattern. By expanding the rules we would
damage this method’s attraction, its simplicity. Implementing the method would be far
more complicated, although its use would appear identical to users
This method owes much of its development to Andy Sloss who gives a detailed account of
how the method works in his book (Sloss, 1995). He has also written a Flash application
(51055 Designs, n.d.) which provides an excellent, easy to use implementation of the
process.

2.4.2 Algorithmic Approach

The alternative to using a tile based approach of loading pre-drawn images is to use an
algorithm to draw the knot each time the process is run. There are several algorithms (or
processes that could be easily adapted into an algorithm) in use to draw knot work, I will
look at two that a well suited to producing Celtic knots.

Marc Wallace’s Method

This method is currently aimed at drawing knots by hand, however its step by step approach
could be modelled within a computer program. This approach builds knots in stages, with
each stage expanding on the previous. Like the tile method this process uses a grid, however
it creates the knot around the grid lines rather than using it as a place holder for tiles.
The process is summarised below; a more detailed explanation can be found on Marc
Wallace’s website (Wallace, n.d.). Below is the method for creating knots by hand, if
turning this into an algorithm for a computer it may be possible to combine or remove
certain steps. Figure 2.12 illustrates the steps outlined in this method9 .

1. Place dots at intersections on the blank grid. Dots should be placed where the x and
y coordinates are odd if you add them together (with the top left corner being 0,0)
(Figure 2.12(a))

2. Place a line through each of the dots on the grid border. The lines are drawn just
less than two grid units long with the dot as the center point (Figure 2.12(b))

3. Place internal borders / splittings - These are lines through which the knot can not
pass. They will determine the eventual shape of the knot. All of these lines must
conform to the following constraints (Figure 2.12(c))

• Lines must be horizontal or vertical


9
All images from http://www.wallace.net/knots
CHAPTER 2. LITERATURE REVIEW 21

• Only one line may be placed at each dot

4. Straight lines - The remaining dots are the points where the cord will cross over itself.
Straight lines should be placed between diagonally adjacent dots (Figure 2.12(d))

5. Going diagonally outwards from each dot, if the dot is approaching a splitting, draw
a short curve out from just past the dot to the edge of the grid square. The curve
should go straight towards the dot, and should be parallel to the splitting where it
meets the grid edge (Figure 2.12(e))

6. Corners - Add pointed returns where ever a vertical and horizontal border meet
(Figure 2.12(f))

7. Long lines - Wherever there are thin stretches bounded by splittings all going in the
same direction a straight line should be drawn (Figure 2.12(g))

8. Centres - Wherever there is a dot, fill in the center according to the rules (Fig-
ure 2.12(h))

• On odd rows, connect the lower right piece to the upper left piece
• On even rows, connect the lower left piece to the upper right piece

9. Remove construction lines

The first three of the steps produce construction aids and could be altered or removed
completely when converting this method to an algorithm for use in a computer program.
The final step would also become obsolete at this point. ‘Brian’s Celtic Knot Generator’
(Unknown, n.d.) uses a very similar process to the one outlined here, however the knot
work produced does not follow all of the rules or guidelines associated with Celtic knot
work.

(a) Step 1 (b) Step 2 (c) Step 3 (d) Step 4

(e) Step 5 (f) Step 6 (g) Step 7 (h) Step 8

Figure 2.12: The Marc Wallace Method


CHAPTER 2. LITERATURE REVIEW 22

In its current form this method also allows for quite free flowing knots although it is closer
to the Celtic style than the tile method. Making this method produce knots that follow
the Celtic principles would be reasonably easy. To get the repetition and symmetry a rule
could be added to the internal border stage which specified that the placement of borders
must be symmetrical on the x and y axes.

Christian Mercat’s Method

The method developed by Christian Mercat (Mercat, n.d.) is aimed at producing Celtic
knots rather than knots in general. It is a method for drawing knots by hand, but maps
almost unchanged into a algorithm that could be used in a computer program. Like several
other methods this process relies on graphs to act as the basis of the knot. The difference
is that this method uses connected graphs to give the overall shape and structure of the
knot; the cords are then built on top of the graph. The original graph and the construction
lines it provided are removed.

1. Produce a symmetrical connected graph (Figure 2.13(a))

2. Put a crossing (an X shape) on the middle of each edge. The direction of the lines
will give the direction that the cord will take and thus it is important that they are
precise (Figure 2.13(b))

3. Connect the lines from the crossings to each other. If the crossings have been
drawn correctly then joining the crossings together should be reasonably simple (Fig-
ure 2.13(c))

4. Sort out the over/under pattern following the principle if a cord goes over another at
a crossing it should go under at the next crossing (Figure 2.13(d))

5. Thicken your design such that it is not just a single pen line

Using this method can have a more rigid structure than some of the other methods. In
this process a graph will only map to one knot, to create a different knot the original graph
must be altered. The advantage of this method is that the artist or user does not need
to make artistic decisions about how to actually draw the knot, this is controlled by the
method definition. This can be beneficial to users who are learning how to draw Celtic
knots, as they may be able to associate the basic structure of their graph to the final knot.
As the knot generation part of this method is based entirely on the graph produced at the
start, it is one of the most suitable methods for converting into a computer program. The
stages in the process define how a knot is constructed on top of the user’s graph, meaning
that system does not need to make artistic decisions. Connected graphs are structures that
a computer system can analyse and understand; thus suggesting improvements can be done
in several ways including comparing the user’s graph to pre-determined graphs.
CHAPTER 2. LITERATURE REVIEW 23

2.5 Summary

Celtic knot work has evolved through the centuries, as has the rest of Celtic artwork. Celtic
designs and patterns have been influenced by a variety of sources ranging from the Romans
through to Christianity. Knot work has been present in Celtic art for a large amount
of time; although it really flourished in the Christian era where it was used to illustrate
manuscripts.
Celtic knot work can be defined by a set of rules and guidelines, each with varying effects
on the final knot. If certain rules are broken the knot will cease to be Celtic in style, where
as the guidelines are open to interpretation and often require artistic talent to produce the
best results. Any system produced as part of this project will need to ensure that the rules
are obeyed. There may be significant benefit to less artistic users if the system could assist
and make good suggestions concerning the application of the guidelines.
There are several approaches to producing Celtic knot work, each with its own strengths and
weaknesses. The tile method lends itself to a reasonably simple computer system, by using
a pre-drawn set of knot sections and rules to determine which can be placed alongside
each other. This approach would be quite straight forward to program but it does not
allow the user a large amount of freedom or creativity. The algorithmic methods are in
general more creative from the user’s perspective, this comes with the cost of being more
difficult to program. Christian Mercat’s method is the best of the algorithmic approaches.
Originally designed as a method for drawing Celtic knots by hand, it can be mapped into
a computer program in a logical and structured way. This method also requires the least
artistic talent from its users, as the actual drawing of the knot is defined by concrete steps.
Whilst requiring the least artistic skill from its users, it is also one of the most informative
approaches. Users can edit a graph and see the effect this has on a knot, knowing that if
they revert to the previous graph the knot will be identical to the last use of that graph.
Overall this research has provided good information on the background and history of
Celtic knots (and knotting in general), whilst also showing techniques and ideas that may
be beneficial to this project.
CHAPTER 2. LITERATURE REVIEW 24

(a) Create Graph (b) Add Crossings

(c) Join Crossings (d) Sort Out Over & Under Pattern

Figure 2.13: The Christian Mercat Method


Chapter 3

Problem Description &


Requirements Analysis

This section aims to provide the reader with a description of the problem this project will
try to address and information about the requirements for the application being developed.
The requirements are based on the initial analysis of the problem and the subsequent
discussions between Dr Willis and myself.
The requirements are intended to act as a guide for the development and testing of the
software application associated with this project.

3.1 Problem Description

The following is an extract taken from the initial description provided by Dr. Willis and
provides a top level view of what the project aims to achieve

Celtic artwork is filled with examples of intricate geometric patterns, usually


called knot work. The knot work patterns follow well-defined rules. This project
is to develop a software package for generating Celtic knots, by allowing the user
to choose basic components and motifs, and then automatically combining these
according to appropriate rules to give a complete knot.

Using this information as a basis, research was carried out into existing ideas and technolo-
gies to see how similar projects addressed the issue. Two prominent approaches quickly
emerged from the research relating to how knot work is drawn. The first approach is known
as a tile method (see Section 2.4.1) which joins pre-determined sections of knot work to-
gether to form a complete knot and the second approach uses algorithms or set processes
to generate a knot based on parameters. The tile method can produce good Celtic knot
work but it has limited scope for allowing a user to learn about knot work. The user simply

25
CHAPTER 3. PROBLEM DESCRIPTION & REQUIREMENTS ANALYSIS 26

selects pre-drawn sections of knot work which fit together, there is little opportunity for the
user to see how their parameter choices affect the final result. The algorithmic approach
allows for a much more interactive and involving experience for a user, thus it was decided
that the project should focus on producing knots based on user parameters and input using
an algorithmic approach.
Following the initial series of meetings about the project a description of the project aim
was established. From this description it was possible to start defining the requirements
for the software package to be developed.

This project aims to develop a software package that creates Celtic knot work based on
input from a user. The user should be able to alter their input and see the effect the
changes have on the final knot. The software should use an algorithmic approach to
generate the knot, rather than allow the user to choose from a selection of suitable
pre-drawn knot work sections.

3.2 Non-Functional Requirements

The non-functional requirements will heavily effect the user’s experience when using the
software developed for this project and as such they may include some of the most important
design requirements. Making the user’s experience enjoyable and stress free is essential for
ensuring that they desire to keep using the software.
The interface shall be simple and intuitive to use; a bad interface will hinder the user
whilst they try to complete their task where as a well designed interface may actually
assist the user. Screen layout and the size of components within it should follow existing
design principles for good interface design. A badly laid out screen is likely to confuse
and frustrate the user as they may not be able to find functions or tools where they would
expect them. The layout should also be consistent in each view if the software uses different
screens; if a component is used on multiple screens, the user will expect it to be in the same
place regardless of which screen they are on. A well laid out and consistent interface should
help the user become familiar with the software in the least amount of time, which allows
them to focus on the task of drawing knots rather than trying to understand how to use
the software.
Use of icons as representations should be appropriate and should not confuse the user.
Many users will have predetermined ideas about what an icon should do from previous
computer use (e.g. the image of a floppy disk representing save); using these icons for
another purpose will make it harder for the user to become familiar with the software as
they will keep double checking what the icon does. In certain circumstances it may be
easier for the user to understand what a button or function does if it is represented by
word rather than an icon or vice versa. Although many users may understand common
icon usage it is important to cater for those who are new to this area; all buttons shall have
tool tips associated with them to help give the user a better understanding of the function.
CHAPTER 3. PROBLEM DESCRIPTION & REQUIREMENTS ANALYSIS 27

Information displayed on the screen in textual format should be clear and concise so that
the user can quickly read and understand the information being presented. Buttons are
just one example of information is displayed as text, others include error messages and
menu names. Error messages should inform the user what incident or action caused the
error and what they can do to correct the problem. Unhelpful error messages and unclear
names could easily confuse and irritate the user and thus cause them to loose interest in
the software.
The user should not be interrupted by the software unnecessarily unless a major system
event has occurred which will effect their use of the system. If the user must be interrupted
then they should not be able to carry on working until the interruption has been dealt with
or acknowledged. If the software functions are still active whilst the user is handling the
interruption then they may inadvertently cause a function to activate. For example if a
dialog box is presented to the user but the buttons on the original window are still active
then the user may be able to click on them and run the associated function. This could
lead to changes to the user’s work that they did not wish to make and thus will need to be
undone, which may be frustrating to the user.
The software to be developed should be usable by anyone interested in Celtic knot work
regardless of their experience of drawing knot work. The software should not hinder the
more experienced user with unnecessary information whilst providing enough feedback and
information for a less experienced user to become familiar with knot work and the software.

3.3 Functional Requirements

The functional requirements define how the software produced for this project will function
and features that it will contain. These requirements should cover all aspects of the systems
functionality from user interaction to the knot generation process.
The software shall implement Mercat’s method (see Section 2.4.2) for creating Celtic knot
work. This method has been chosen for this project as it can be converted into an algorithm
for use within a computer program and it should be easy to use regardless of the amount
of knot working experience. Using this method means the software must implement several
functions. Firstly the user shall be provided with an area where they can input their
undirected graph that will form the basis of the knot. This area shall resemble basic graph
paper, with horizontal and vertical lines, the user will be able to create a vertex at any
intersection of these lines. The software will not allow the user to create more than one
vertex at any given intersection, if the user could do this then the graph may become very
complicated and hard to understand. The user shall be able to create an edge between two
existing vertices, if the user attempts to create an edge that does not connect two vertices
it should be disregarded by the software. For Mercat’s method to function correctly an
edge must not intersect any vertices (other than its start and end point) or any other edges;
thus the software must ensure that the edge created by the user meets these requirements.
If an edge intersects a third vertex, which lies between the start and end point then the
CHAPTER 3. PROBLEM DESCRIPTION & REQUIREMENTS ANALYSIS 28

software should split the edge into two separate edges using the third vertex as the end
point for one edge and the start point for the other. The user shall be given the ability to
delete an edge or a vertex that they have already created. If deleting an existing vertex
then all edges that use that vertex should also be removed.
From the user’s graph the software shall create a knot using the process described by
Mercat. To achieve this the software must find the midpoint of each edge and place a
crossing point at this location. Each crossing point shall be correctly connected to another
crossing point to form the basic shape of the knot. The cord in the knot must not cross
itself at any point other than the crossing points already calculated. Once this has been
done the software shall calculate the over and under pattern for each crossing point. The
knot is then ready to be thickened and coloured according to the user’s preferences; if the
user has not specified these parameters defaults should be used.
Once the knot has been generated the user should have ability to change some aspects
of the knot’s appearance including colour and thickness; these parameters should also be
configurable before the user generates the knot. If the user wishes to change parameters
affecting the look and feel of the knot then the software must perform these changes with
out recalculating the knot from the graph. If the knot is fully processed each time there
may be a considerable effect on performance, which could result in frustration for the user.
If the user wishes to change the structure of the knot the software shall give them an
option to go back to the graph they produced originally. If the user has changed any other
preferences (e.g. colour) these should be remembered when returning to the graph input
stage.
The software shall provide the user with the ability to save and load knots; different formats
should be available to the user depending what they are trying to save. The user shall be
able to save and load the basic graph which forms the knot structure. The software should
handle the translation of the knot to and from a file, the user should not be required to
provide any information except a file name. There should be an option to save the finished
knot as a picture so that the user can use their knots in other applications and in different
tasks. The software should provide at least two different picture formats to increase the
chances of another application accepting the picture. Any pictures produced will not store
the basic graph information and thus the user shall be made aware if they have not saved
the graph before exiting the program, even if they have saved a picture of the knot.
To ensure the software is available to the largest selection of users, it shall be platform inde-
pendent; such that it runs correctly on any system, providing system meets the minimum
specifications for the chosen development language. Java is likely to be an appropriate
language for this project due its existing graphics libraries and platform independence.

3.4 Summary Of Key Requirements

There a number of requirements in place for this project, some of which are more important
than others. The key requirements in descending importance can be summarised as the
CHAPTER 3. PROBLEM DESCRIPTION & REQUIREMENTS ANALYSIS 29

following list.

1. The software shall generate Celtic knot work

2. Knot work shall be directly based on the input of user parameters

3. The software shall implement an algorithmic approach to creating the knot work

4. Any interface developed shall be simple and intuitive to use

5. The software should provide appropriate auxiliary functions such as the saving and
loading of knot work

6. The software shall be written using a platform independent language


Chapter 4

Design

This section aims to provide the reader with information about the software design for
this project. This document approaches the design at a high level, which covers the ideas
and concepts in the software and does not cover specific code to be developed (which
can be found in Appendix B). Section 5 discusses how some of the concepts are actually
implemented.
This section of the document contains figures to assist in describing some of the proposed
processes. The majority of figures are representations of the user’s input graph (see Sec-
tion 4.3) using the standards below unless otherwise stated.

• Larger circles are used to represent vertices in the input graph

• Thick lines represent edges between vertices on the input graph

• Smaller circles represent crossing points

• Dashed lines are a representation of conceptual lines (often infinitely extended)

• Dotted lines are conceptual lines parallel to the x and y axes

4.1 The Celtic Knot Creation Process

The process of producing a Celtic knot can be viewed as a series of stages or steps from
start to finish. The first stage is graph creation, where the user is able to input their
graph which will form the basis of the knot. The next step is to generate a collection of
shapes that form knot components based on the edges and vertices in the user’s graph.
Following this is the linking phase, which takes the various components from the previous
stage and links them together; the over and under pattern will also be calculated at this
point. The final stage is to draw the finished knot on the screen. These stages can be

30
CHAPTER 4. DESIGN 31

mapped approximately to the steps in method for drawing knots outlined by Mercat (see
section 2.4.2).
In the graph production stage (see Section 4.3) the user is able to create (or delete) vertices
and edges between existing vertices. At this point the actual knot does not exist, it is simply
a collection of edges which can be used later to create the knot. Once the user has finished
creating their graph the generation of knot components (see Section 4.4) can begin. During
this process the software will take the user’s graph and generate crossings at the mid point of
each edge. From these crossings the software can then produce the other knot components
which will sit between the crossings. By the end of this stage the software will have a
collection of knot components which need to be linked together. The linking phase (see
Section 4.5) follows the cord(s) through the newly created knot joining each component to
the next. The links between components will allow the software to easily create the over
and under pattern which gives the knot its distinctive Celtic style. After the linking phase
has completed the software will have a complete Celtic knot, which it can then display to
the user (see Section 4.6). From here the user can choose to save their knot or return to
the graph production stage to edit their original graph and start the process again.

4.2 The Knot Structure

4.2.1 Representation

The knots produced by the software will be a collection of different geometric shapes joined
together and there are several different ways of representing and storing this information.
The simplest structures that could be used are lists, arrays or other similar data structures.
The various shapes that make up the knot could be stored in a structure of this form and
then extracted and displayed when required. This approach would work in theory, although
it presents several problems and missed opportunities. Firstly working out which shapes
should be joined together could only be achieved by looking at the points within each shape
and comparing these to the points in every other shape until a match can be found. Doing
this would require a linear search for every single point, which would need to be carried out
each time the join information is required. Working out which shapes should be displayed
on top and which should be on the bottom to form the over and under pattern required
would also be difficult. The software would have to pick a shape to start with and draw it;
then repeatedly find the shape which connected to the current shape and work out if this
shape should be over or under. To calculate the over and under pattern a large number of
comparisons and checks would have to be performed.
A potential solution to these problems would be to store the meta data about each shape,
again in a list or array. Once the joins and the over and under ordering have been calculated
this could be stored; when the information is required for a particular shape the software
could look up the associated meta data. Looking up the data could be done in two ways;
either the data is stored in the second array or list using the same position (index) as the
CHAPTER 4. DESIGN 32

shape in the list of shapes and is directly accessed using the shapes index or the software
must search through the meta data until it finds the data corresponding to the shape.
The first approach requires careful maintenance of the array positions of the shape and its
meta data, which can be difficult if the chosen development language does not guarantee
to maintain positions after updates to the data structure. The second idea overcomes the
problem of needing to know which position to use but requires searching through the meta
data each time it is required. It is possible to implement either of these approaches, however
the effects on performance and future development may be severe. If these solutions were
implemented neither of them would be elegant or easy to follow for a human reader and
as such future developments may be slow as the developer tries to understand the data
flow. System performance may also be severely affected by implementing either of these
approaches; the amount of time taken to repeatedly search lists or the amount of processing
to maintain the lists may be great.
This project will be developed using Java an object orientated language, which can help to
solve these problems. Instead of the knot consisting of a series of shapes joined together,
the knot will be made up of a series of knot component objects; each component stores the
appropriate shape(s) and all of the relevant meta data. As each component will store its
own meta data the system should not need to spend time searching through all meta data
to find the piece it requires and the issue of array positions is eliminated as the meta data
is stored with the shape in the corresponding component. Thus if the index of a component
is altered all of the meta data moves with it. Creating custom knot component objects
should also help improve the maintainability and future development of the software as the
flow should be simpler and easier to understand. Using the custom objects should make
the overall code tidier as data is accessed via method calls which can easily be mapped
back to their parent object rather than directly accessing information which can be hard
to follow and trace. The downside of taking the custom object based approach is that it
is likely there will be a larger memory overhead, required to store the objects compared to
just storing the basic elements on their own; however this is a small price to pay for the
benefits gained. Thus this will be the approach adopted for this project.

4.2.2 Knot Components

The majority of variables inside the different types of knot components will be calculated
in the knot generation phase (see section 4.4) of the software and then stored in the com-
ponents; thus the knot components do not require methods to calculate variable values.
The knot will be a collection of shapes and associated meta data for each of these shapes,
each shape and its meta data will constitute one component in the knot. A Celtic knot will
be divided into 4 different types of components each represented by distinct Java classes.
Several component types share similar attributes and meta data which can be stored in
a parent class called KnotComponent; each type of component will be a subclass of this
parent (Figure 4.1 shows the initial class diagram for the knot components). The common
data contained within the parent may include the start and end points of the component
and the two crossing objects (see section 4.2.2) which the component connects to. The start
CHAPTER 4. DESIGN 33

and end points of the non crossing components will be points in the two crossing compo-
nents that this component connects to. The references to the crossing objects represent
the previous crossing the component links from and the next crossing that the component
links to. The start and end points and the crossings they are part of will be calculated
in the knot generation phase and then stored in the appropriate type of knot component.
KnotComponent may make use of abstract methods to force its sub classes to implement
methods with standard signatures. Using a parent class provides a standard way of inter-
acting with certain aspects of the different types of components without needing to know
which type of component is being handled. Thus if a process wishes to operate on all knot
components it does not need logic to handle each type, it can call the same method on each
component.

Figure 4.1: Class Diagram For Knot Structure

Crossings

A crossing will be formed when the cord passes over itself in the knot (Figure 4.2(b)); in
the user’s graph these occur at the midpoint of each edge. A crossing will consist of two
straight lines (crossing lines) that pass over each other at their midpoint; which will be
represented as the four end points of the two lines within this component. This component
will make the greatest use of meta data, most of which will be used in the linking phase
CHAPTER 4. DESIGN 34

(see Section 4.5). Each one of the points will be shared with another knot component and
references to the knot component sharing each point will be required. Information needs
to stored about which of the crossing lines has been used in calculating the over and under
pattern along with the result of this calculation.
This component will be a subclass of KnotComponent to make use of its abstract methods,
but it will not use the variables inherited.

Straight Lines

The class StraightLine will be used to represent a straight line between the ends of two
crossing lines (Figure 4.2(c)), when they line up exactly with each other. This will oc-
cur when two edges of equal length and one common vertex are drawn perpendicular to
each other on the input graph with no obstructions between them. This class will extend
KnotComponent but should not contain any extra variables as they are not required. Ad-
ditional points are not required because the component represents a straight line from the
start point to the end point and these points will already be contained within the compo-
nent. Section 4.4.2 describes how the start and end points that form straight lines will be
determined.

Curved Lines

The CurvedLine component will link the ends on two crossing lines with a curve (Fig-
ure 4.2(d)) when two edges with a common vertex exist on the input graph with an angle
less than or equal to 180o between them. This class requires two points in addition to those
that will be inherited from KnotComponent; which will be used to store the control points
in the curve. The control points will be calculated in the knot generation phase and then
stored in this object along with the start and end points of the curve. Section 4.4.5 details
how the control points can be calculated and section 4.4.3 gives the proposed method for
finding the start and end points which require a curved knot component to link them.

Pointed Returns

Pointed returns should be used when the cord in the knot needs to turn back on its self
(Figure 4.2(e)). If a smooth curve is used then the end result will appear to look like an arc,
however the rules behind Celtic knot work (see section 2.2.2) state that the return should
be pointed in appearance. This component will represent pointed returns in the knot by
connecting two curves and two straight lines in a certain order. The curves and the straight
lines can not be represented using the previous components as one or more of their end
points will not connect to a crossing. The two straight lines will have a point in common
and thus will be joined at this location; this will give the return its pointed appearance.
The non common end points in the straight lines will be connected to the end points of the
two curves; whilst the other end of each curve connects to either the start or end point (i.e.
CHAPTER 4. DESIGN 35

the points on the crossing lines). Like the previous two components this will connect points
in crossings lines, although in this case the two crossing points being connected may be on
the same crossing. To hold the required information this component needs to contain seven
extra points in addition to the start and end point. Three of these points will represent the
two straight lines and the other four will be the control points for the two curves. The start
and end points for the curves will not need to be stored separately as they are connecting
the crossings to the straight lines and thus they will share points. Section 4.4.4 details how
the start and end points will be determined and how the seven additional points can be
calculated.

(a) Complete Knot

(b) A Crossing (c) Straight Line (d) Curve (e) Pointed Return

Figure 4.2: The Four Knot Components

4.2.3 The Celtic Knot Class

The class CelticKnot will act as a container to store all of the knot components that make
up a particular knot, which will consist of a single variable, the set of all knot components.
This class may contain the functionality required to carry out the linking and drawing
stages of the knot production process as well as providing information for the other key
CHAPTER 4. DESIGN 36

processes.
All access to the knot components should be carried out via this class, meaning other
classes will not need to maintain references to the knot components. When a component or
part(s) of a component are needed, this class will have to provide the appropriate methods
to retrieve them. Lists of unconnected crossing lines can be generated by iterating over
all knot components picking out the crossings and then selecting any of the unconnected
points within that crossing. This approach can also be used to find crossings which have
not been linked or had their over and under pattern set. Functionality will be required to
select components that use are particular point. Several of these methods are likely to be
used in the linking phase and in the creation of knot components.

4.3 Graph Production

The user will be presented with an area resembling graph paper which they can use to draw
an undirected graph. The graph produced by the user acts as a structure for the knot be
draw around in the generation stage (see section 4.4).

4.3.1 Graph Area

The standard Java libraries currently do not feature a specific graph type and as such it
is necessary to create a custom area. Drawing equally spaced horizontal and vertical lines
onto a white background would give the visual effect of graph paper. The aim is for the
user to create vertices at the intersection of the drawn lines (grid lines) and then create
edges between vertices. Mouse listeners will need to be implemented to capture the user’s
movements and actions. When interpreting the user’s actions the location of the mouse
can be mapped to the intersections of the grid lines using the following process.

1. Take actual mouse location

2. Take the float value of mouse location divided by the grid spacing

3. Take the integer value of mouse location divided by the grid spacing

4. If float - integer ≥ 0.5 increase the integer by 1

5. Return the point with the coordinates (integer of x * grid spacing), (integer of y *
grid spacing)

When the user clicks twice on the same location a new vertex will be created, providing
that one does not exist already at that location. The software should prevent the user from
adding a vertex (thus edges) to the edge of the graph area; the new vertex must be at least
one grid line away from any edge. This is to prevent the user from creating a knot which
will go off screen when drawn. If the user clicks on a point, which is interpreted to be the
CHAPTER 4. DESIGN 37

location of an existing vertex (or edge) then the element should be selected and highlighted
on screen to show the user of their selection. Only one element may be selected at one time
and if a different element is clicked whilst one is selected, the selection should change to
the new element. If the user selects a vertex and does not release the mouse button they
will be able to drag a line to another point on the grid. If this second point is interpreted
as another vertex the software will create an edge if it meets these conditions.

• Both the first (start) point and second (end) point are existing vertices

• The start and end points are different to each other

• The start and end points have not been used together in an existing edge

• The proposed edge does not intersect any existing edges

If any of these conditions are not met the proposed edge will be discarded. If the proposed
edge intersects a third vertex in between the start and end points the proposed edge should
be split into two new edges, from the start point to the intersecting vertex and from this
vertex to the end point; both of these new edges will need to be recursively checked to
see if they intersect any other vertices. Once all of the conditions have been met and the
edge split accordingly the new edge(s) can be added to the graph. Edges are undirected
in Mercat’s method so only one edge can exist from point 1 to point 2, if the user tries to
create an edge from 2 to 1 the edge should be discarded.
The following functionality should be available to the user when they are producing their
graph, all of which will be initially disabled unless otherwise stated. After each function
has been executed the screen will need to be redrawn to display the changes.

• Generate Knot - Starts the knot generation process and changes the view to display
the knot. Enabled when at least one edge exists, disabled at all other times

• Delete Vertex - Deletes the currently selected vertex and any edges that use the
vertex. Only enabled when a vertex is selected

• Delete Edge - Deletes the currently selected edge, the vertices at either end are not
affected. Only enabled when an edge is selected

• Clear All - Deletes all edges and vertices regardless of what is selected. Enabled when
at least one vertex exists

• Save Graph - Opens a file dialog for the user to enter a file name or select an existing
file, then saves the graph and the knot preferences in the given file. Enabled when at
least one vertex exists

• Load Graph - Opens a file dialog for the user to enter a file name or select an existing
file, then the graph and preference data is read in. Always enabled
CHAPTER 4. DESIGN 38

4.3.2 Storage & Representation Of Vertices & Edges

The vertices and edges created by the user will need to be stored for use in the knot gener-
ation process. The simplest way this information could be stored would be as collections of
points. Vertices can be represented by a single point and edges can be stored as a start and
end point (both of which must be vertices). The benefit of storing both edges and vertices
as points is that they are easier to compare. If a line representation of the edge is required,
then this is simple to create, as Java provides methods to create lines from points.
Representing the user’s graph on screen should be reasonable easy if the graph’s vertices
and edges are stored as points. The point can be taken as the center of a vertex and a circle
drawn around this point. Drawing a line between the start and end point of each each can
be done in several ways, as the Java graphics libraries provide several ways of drawing a
line from points or actual lines.

4.3.3 Saving & Loading Of Graphs

The user must be able to save a graph they have produced or load a previously saved
graph into the software. The proposed file format to be used is a text file saved with a
.knot extension. When saving a graph the software will save the vertices and edges created
as well as saving the knot preferences (see Section 4.7) that user has chosen. The saving
process will work by converting all of the required information into a text format, which
will be written to a given file. The load process will read text in from a file and then
convert this back into the software data structures and variables.
Converting the graph information and knot preferences into a text format will require
separators to be used between elements to ensure the data is not jumbled. A specific string
(e.g. ####) will be used to separate elements of the same type (e.g. all the vertices) and
a second string (e.g. @@@@) will be used to separate the different types of elements. Any
points used in the software can be written as their x value followed by the y value separated
by a symbol (e.g. a comma ‘,’). Colours stored with the software can be converted into
their red-green-blue-alpha components (alpha is used by Java to specify the transparency
of the colour). Each colour component can then easily be converted to text.
The process to load a graph into the software will need to reverse the process for saving the
graph. Using the text separators as markers, the software should split the text it reads in
from a file. Once the text has been split appropriately and thus all the markers have been
removed, the information can be converted into software data structures. When splitting
text Java uses arrays to store the result of the split. These arrays can then be read and
the data stored within them converted into the appropriate data types for the software.
In both the save and load process the file used will come from file choosers loaded by the
user from the graph area. The software will need to check the provided files suitability
before it can be used. This will included among others checking if the file exists and if it
can be read or written to.
CHAPTER 4. DESIGN 39

4.3.4 Software Generated Improvements

Once the user has input their graph, the software should analyse the graph and then make
suggestions to the user about how they could improve the graph. Any valid graph that the
user inputs (i.e. has at least one edge) should produce a piece of knot work, however there is
no guarantee that the finished knot will be Celtic in style or even attractive to look at. The
software should analyse the edges in the original graph and make recommendations how
to improve the look and feel of the knot by changing the graph. The rules and guidelines
associated with Celtic knot work (see Section 2.2.2) can be encoded into the software as
rules which can be applied to the input graphs. The process to generate the over and under
pattern is described later in this section (see Section 4.5) and will be enforced for all knots
produced. Keeping a uniform cord width will be maintained by only allowing the user to
choose two knot thickness at a time, one for the knot itself and one for its outline. If the
user changes one (or both) of the thicknesses the whole knot will change in appearance.
The guideline about pattern repetition within a knot is likely to be the area where the
software can suggest improvements to the user. Checking for symmetry in the graph should
give a good indication if the knot that will be produced will be suitable for use within a
repeating pattern. If the graph is not symmetrical then it is unlikely that the resulting
knot will be symmetrical as it will be based on the graph. A symmetrical knot should be
far easier to use within a repeating pattern than an unsymmetrical knot. The software
could use the following process to determine if a graph is symmetrical.

1. Determine the central point of the graph

2. Create a plane or planes that intersect this point

3. For each edge in the graph check to see if a mirror image of the edge exists, where
the plane is the mirror

4. If the edge does not exist recommend that it be created

Working out the central point of the graph can be done by fining the rectangular bounding
box of all edges, then finding the mid point of the rectangle. Determining what plane(s)
to use on the graph is likely to be more challenging. The obvious planes are ones which
intersect the centre of the graph area at common angles from the x and y axes such as
90o , 180o or 45o . These planes would work well if the user’s graph has also been drawn
at these angles form the axes, if the graph is drawn at different angles then these planes
would report the the graph to be unsymmetrical regardless of the symmetry of the graph.
Calculating the correct plane to use could be done by finding two edges which are the
same distance from the central point (the first and second vertices in one edge are the
same distance from the central point as the first and second vertices in the other edge
respectively), then finding the plane which intersects the central point and splits the gap
between the two edges in half. Every graph may have several planes which can be used to
bisect the graph, each may show different edges which are not symmetrical in that plane.
CHAPTER 4. DESIGN 40

A symmetrical graph should produce a correct knot, however it may still be unattractive.
If two crossings are close to each other as a result of a very acute angle between edges
the curved component which will be generated to link the crossings may have sharp bends
instead of a smooth curve. The sharp bends are not strictly against the rules of Celtic knot
work but are often less pleasing to look at as they break the smooth cord flow through
the knot. One approach the software could take to find edges which may result in crossing
being close to each other is summarised in the following way.

1. Identify two edges with an acute angle between them

2. Calculate mid points of both edges

3. Find distance between mid points, if below a pre-determined threshold recommend


moving one (or both) edges to increase crossing separation

Calculating the angle between edges can be calculated by normalising the edges and then
finding the dot product between them, Section 4.4.4 details a similar process.

4.4 Knot Generation

The knot generation process revolves around the graph created by the user; the vertices
and edges within the graph will be used to determine some of the points for the knot
components. When graph information is required by this process it must be retrieved
from the class containing the graph area. The different types of knot components (see
section 4.2.2) will be created within distinct phases of the knot generation process. The
process begins by calculating the crossings that will be used in the knot. The next step is
to calculate any of the straight line components that exist in the knot, followed by the the
curved line components and finally the pointed returns are calculated.
The class responsible for generating the knot components will contain an instance of the
CelticKnot class (see section 4.2.3). This class should also store any constant data required
to when calculate the lengths of lines and positions of points; these are discussed in the
relevant subsections.

4.4.1 Calculate Crossing Components

Crossing points that appear within the knot correspond to the midpoints of edges in the
user’s graph. The mid point of every edge on the original graph needs to be converted
into a crossing object; an example of which can be seen in Figure 4.3. References to the
lists containing the start and end points of edges will be obtained at the beginning of this
phase. A loop runs over each edge in turn, stopping once all edges have been processed.
Inside the loop the x and y components of the current edge’s mid point will be calculated
using the formula:
CHAPTER 4. DESIGN 41

midP ointx = (startP ointx + endP ointx )/2


midP ointy = (startP ointy + endP ointy )/2

Figure 4.3: Example Edge & It’s Crossing

A crossing is made up of two lines whose mid points will be identical to the mid point
of the edge used as the basis for the crossing. The lines of the crossing should always be
at 90o to each other and at 45o to the edge. The four points required to make the two
crossing lines can be viewed as the mid point with different x and y offsets. The offsets can
be calculated using trigonometric identities and the length of the crossing line; which will
be fixed so all crossing lines are the same length. To use the basic trigonometric identities
to calculate the x and y offsets the triangle which the crossing line is part of needs to be
a right angled triangle, with one edge parallel to the x-axis and one edge parallel to the
y-axis. If these conditions are not met then the values calculated for the x and y offsets
will be inaccurate and thus when used as a translation on the mid point may give a point
in the wrong position.
The x and y offsets are the lengths of the adjacent and opposite sides in a right angled
triangle where the hypotenuse is the crossing line (Figure 4.4(a)). Thus the software must
CHAPTER 4. DESIGN 42

find the lengths of these two sides. The crossing line will always be treated as the hypotenuse
in the triangle by the software, so the required x and y offsets will always be the lengths of
the opposite and adjacent sides. Calculating these lengths requires the length of one side
and one of the angles at either end of the known side. The known side is the hypotenuse (the
crossing line) whose length is fixed. To find the lengths of the adjacent and the opposite
the following formula can be used:

cos (δ) = lengthadj ÷ lengthhyp ⇐⇒ lengthadj = lengthhyp × cos (δ)


sin (δ) = lengthopp ÷ lengthhyp ⇐⇒ lengthopp = lengthhyp × sin (δ)

δ is the difference between two other angles α and β. α is the angle between the edge and
x-axis, β is the angle between the edge and the crossing line which we know is 45o at all
times. The value of α can be found using a right angle triangle (shown in Figure 4.4(b))
where the edge is the hypotenuse:
lengthadj = starty − endy
lengthopp = startx − endx
oppositeOverAdjacent = lengthopp ÷ lengthadj
α = arctan (oppositeOverAdjacent)

Logic must be in place within the software to detect the orientation of the edge and subtract
start point values from the end point values or vice versa depending on which set of values
is larger. There are two special cases involving different α values, when the value of α is
equal to 45o or 0o . The first of the special cases occurs when the difference in the start and
end point y values is equal to the difference in the x values. In this case one of the crossing
lines for the edge will be parallel to the x-axis and the other crossing line will be parallel
to the y-axis. To calculate the position of the four crossing points we can carry out very
simple transforms on the mid point using just the crossing length. For one crossing point
the x value is increased, the second has its x value decreased, third point has its y value
increased and the forth point has the y value decreased. In the second of the special cases
the edge itself is parallel with one of the axes. In this case the the length of the opposite
and adjacent in the triangle to find the crossing point will be equal so we only need to find
one of them. Applying Pythagoras’ theorem1 is a quick method to get the length of the
opposite and adjacent sides when we know they are the same as the following process can
be applied:
length2hyp = length2opp + length2opp
length2hyp = 2 × length2opp
length2opp = length2hyp ÷ 2
q
lengthopp = length2hyp ÷ 2
1
In a right angled triangle the length of the hypotenuse (a) squared is equal to the length of the opposite
(b) squared plus the length of the adjacent (c) squared. Which can be written as a2 = b2 + c2
CHAPTER 4. DESIGN 43

(a) Calculating X & Y Offsets

(b) Calculating α

Figure 4.4: Calculating The Offsets Required To Position Crossing Points


CHAPTER 4. DESIGN 44

To calculate the four crossing points the mid point needs to be transformed by all four
combinations of addition and subtraction of this length to the mid point’s x and y values.
Once the four crossing points have been determined they will be stored in a new Crossing
object. Only the four points are set at this time, the meta data will be left with its initial
values; the meta data will be set later once all knot components exist. The Crossing can
then be added to the list of knot components in the CelticKnot.

4.4.2 Calculate Straight Line Components

This phase of the process will calculate the straight line knot components (see section 4.2.2).
For two crossing points (on different crossings) to be connected with a straight line they
must both fully lie on the same infinitely extended line with no obstructions between them.
This phase will be run immediately after the crossings have been created, so none of the
crossing points will be connected to other knot components. Which means a list of all
crossing lines (primary list) can be retrieved from the CelticKnot object, there is no need
to check each crossing point to see if it connected. A loop which runs over the primary
list will be entered, the crossing line at the current index position (original crossing line)
will be stored in a temporary variable and a new empty list (secondary list) of lines will
be created. An inner loop will then be created which runs over the primary list. When
the two loop indices are different the line at the inner loop index can be compared to the
original crossing line. If both points from the line being compared lie on the infinite line
that contains the original crossing line then the line being compared should be added to
the secondary list. Figure 4.5 shows three crossings, each with one crossing line on the
same infinitely extended line.
Once the inner loop has completed and all crossing lines have been compared to the orig-
inal crossing line, the secondary list now contains all crossing lines which lie on the same
infinitely extended line. The lines in the secondary list can then be processed to find the
line(s) which are closest to the original crossing line. As the original crossing line has
two points it may be possible to connect each one of these to another crossing using two
different straight line components. Finding the crossing lines in the secondary list which
the original crossing line should connect to can be done by calculating the distance from
the original crossing line to each line in the secondary list and finding the closest. The
following pseudo code illustrates a method which could be implemented to find the closest
CHAPTER 4. DESIGN 45

Figure 4.5: Crossing Lines On The Same Infinite Line


CHAPTER 4. DESIGN 46

crossing line to the start point of the original crossing line.

if (distance(current, originalstartP oint ) < distance(current, originalendP oint ) ∧


(closestSP 6= null ∨
distance(current, originalstartP oint ) < distance(closestSP, originalstartP oint )))
{
thenif (distance(original, currentstartP oint ) < distance(original, currentendP oint )
∧noObstruction(originalstartP oint , currentstartP oint ))
{
thenclosestSP ← newLine(originalstartP oint , currentstartP oint )
}
elseif (distance(original, currentstartP oint ) > distance(original, currentendP oint )
∧noObstruction(originalstartP oint , currentendP oint ))
{
thenclosestSP ← newLine(originalstartP oint , currentendP oint )
}
}

An else if statement needs to follow the outer if statement shown above which will perform
the same logical checks except for switching the start point of the original line with the end
point and using a variable to store the closest line to the end point. The check called noOb-
struction above will check to see if the proposed line (which could become a straight line
knot component) does not intersect any existing edges in the user’s graph. Once this loop
has completed the two variables representing the crossing lines closest to the start and end
points will be left. These may still be empty if no line conforms to the right conditions. If
either one or both of these variables are not empty then a new straight line knot component
can be created for each non empty value. The two points in the knot component(s) will be
set to the two points in the closest line(s). The knot component(s) will then be added to the
CelticKnot object. In the example shown in Figure 4.5 two straight line components would
be generated; the first from originalCrossingLinestartP oint to otherCrossingLine2endP oint
and the second from originalCrossingLineendP oint to otherCrossingLine1startP oint .
The original crossing line must then be removed from the primary list of crossing lines being
looped over. This is done to ensure that the line is not connected to any other crossing
lines.

4.4.3 Calculate Curved Line Components

The curved line knot components (see section 4.2.2) will be created within this phase of
the knot generation process. This phase will be run once the straight line components have
been created, so it is possible that some of the crossing points have already been connected.
CHAPTER 4. DESIGN 47

A list of unconnected crossings points needs to be retrieved from the CelticKnot class. The
number of unconnected crossings will be used as the limit for the main loop which runs
within this phase.
The crossing point at the list position equivalent to the current loop index will be retrieved
from the list and stored for future use (original crossing point). The edge that this crossing
point corresponds to is then calculated using the mid point of the current crossing line to
search through the edges. Once the two vertices in the edge have been found the closest
vertex to the current crossing point is determined. A list of edges using the closest vertex
(common vertex) minus the current edge can then be produced by searching through all
edges to see if the closest vertex is a start or end point in any other edge. An inner loop
will run over the the list of edges; processing each edge in turn. References to the edge
containing the crossing point to connect to the original point must be created (secondary
edge). A check will be performed to see if the crossing point can be connected to the non
common vertex on the edge being processed. If this check fails the software will move to
the next edge in the list because there must be an obstruction (another edge) in the way
that would prevent the original crossing point connecting to a crossing point on this edge.
If the check passes and the reference to the secondary edge is empty or the edge being
checked is closer to the original crossing point than the secondary edge, then the secondary
edge will be set to the current edge.
If the secondary edge is not empty after the loop has run over all edges using the common
vertex then the correct crossing point on this edge must be found. If the secondary edge does
not lie on the same infinite line as the crossing line’s edge (primary edge) (see Figure 4.6(a)),
a triangular search area will be formed using the original crossing point and the two vertices
in the secondary edge. A list of crossing points within the search area will be generated and
from this the closest crossing point will be selected. If the primary and secondary edges do
lie on the same infinite line (see Figure 4.6(b)) a different approach to find the unconnected
crossing point to connect the original to must be used. A search will be made of the list
containing unconnected crossing lines picking out all crossing points on the secondary edge,
using the edge (and thus crossing) midpoint. The closest point to the original crossing point
is chosen as the point to connect to. The first method is more efficient than the second
as it does not need to calculate the mid point of each crossing line and then compare it
to the calculated mid point of the secondary edge. Calculating and comparing mid points
will require type casting and mathematical operations as there is no built way in Java to
compare lines this way. The first method just needs to check if a point lies within an area
which can be done using built in Java methods, that all shapes implement. The search area
approach can not be used when the primary and secondary edges are on the same infinite
line because the search area would not contain any unconnected crossing points. If a line
is drawn from the original crossing point (in Figure 4.6(b)) to the common vertex and a
separate line drawn to other vertex on the second edge there would be no crossing points
inside the triangle they form (using the secondary edge as the third side in the triangle).
The search area will always intersect the crossing line containing the crossing point that
should be connected to, between the crossing line mid point and the required crossing point.
Thus the search area will never contain the crossing point which needs to be connected to
CHAPTER 4. DESIGN 48

the original.

(a) Edges Not On The Same Infinite Line

(b) Edges Not On The Same Infinite Line

Figure 4.6: Finding The Crossing Points For Curved Components

If an unconnected crossing point to connect the original unconnected crossing point to is


found then a curved line must be drawn between them; Section 4.4.5 discusses how the
control points for curves will be calculated using two points, which in this case are the
two crossing points being connected. Once the two crossing points have been connected
they must be removed from the list of unconnected crossing points. This prevents the two
crossing points being connected to any other crossing points and stops the same two points
being connected again.

4.4.4 Calculate Pointed Return Components

Pointed return knot components (see section 4.2.2) will be used to connect two crossing
points where the cord must turn back on itself. These components are the only components
which can connect a crossing point on one edge to another crossing point on the same edge
or to a crossing point on a second edge. Like the curved line components this phase will
use a list of unconnected crossing points, obtained from the CelticKnot object. This list of
unconnected crossing points will be used as the basis for the main loop within this phase
CHAPTER 4. DESIGN 49

of knot generation. The unconnected crossing point at the position equivalent to the loop
index will need to be extracted and stored in a local variable (original crossing point). The
vertices on this crossing point are found in the same way as the curved line process.
All edges using the closest vertex (common vertex) to the crossing point need be found;
this list will then be searched for the edge which forms the greatest angle with the original
crossing point’s edge (primary edge). If only one edge is found to be using the common
vertex no calculation of angles needs to be performed, as both the original and the crossing
point to connect to the original are on the same crossing. If there is more than one edge
using the common vertex, then the primary edge is normalised using the formula below.

length ← distance(primarycommonV ertex , primaryotherP oint


normalisingF actor ← 1 ÷ length
normalisedXV alue ← normalisingF actor × (primaryotherP ointX − primarycommonV ertexX )
normalisedY V alue ← normalisingF actor × (primaryotherP ointY − primarycommonV ertexY )

Variables will need to be created to hold the non common vertex (second vertex) of the edge
with the greatest angle to the primary edge and the angle itself; a loop will be formed over
the list of edges using the common vertex. The edge currently being checked (secondary
edge) will be normalised in the same as the primary. The dot product of the primary and
secondary edges can then be computed, which will be used to calculate the angle between
them.

dotP roduct ← ((normalisedXV alueprimary × normalisedXV aluesecondary )+


(normalisedY V alueprimary × normalisedY V aluesecondary ))
angleBetweenEdges ← arccos(dotP roduct)

If this is the first edge to be processed or the angle between this edge and the primary edge
is greater than the current maximum angle this edge will be stored. Once the loop has
completed the second vertex will belong to the edge which forms the greatest angle with
the primary edge.
A list of unconnected crossing points on the primary and secondary edges (which may be
the same) can be found by search through the list of all unconnected crossing points picking
crossings with the same mid point as the edge(s). The original unconnected crossing point
is removed from this list, then the closest remaining point to the common vertex will be
selected. The closest crossing point to the common vertex is used instead of the closest
to the original crossing point for when the two crossing points are on the same crossing
(primary and secondary edges are identical). In this scenario the distance from the original
crossing point to two of the other crossing points will be the same as shown in Figure 4.7(a),
thus either of these points could be returned. Using the common vertex will always return
correct point; in the single line situation the correct to connect to point will be closer to
the common vertex than the other crossing points in the list (as the original point has
been removed from the list). When the primary and secondary edges are different there
will be one unconnected crossing point on the secondary edge closer to the common vertex
CHAPTER 4. DESIGN 50

compared to the other unconnected crossing points on the edge. At least one of the crossing
points on the secondary edge must have been connected using a straight line or curved knot
component as shown in Figure 4.7(b).
Once the closest crossing point has been found the rest of the pointed return component
can be calculated, starting with the two straight lines meeting where the cord turns back
on itself (the tip of the return). If the primary edge is equal to the secondary edge then
the crossing points being connected must be on the same crossing; which means the cord
is going to turn back on itself around the common vertex. In this case the point where
the two straight lines meet (common return point) will be on the same infinitely extended
line as the primary edge. Section 4.4.4 explains how the common return point is calculated
from two points on the same line, which in this scenario are the two vertices in the primary
edge. The two lines in the return tip will be at 90o to each other as the cord is turning
around a single line. The position of the two unknown end points of the straight lines can
be calculated as x and y offsets from the common return point using trigonometry and the
fixed length of straight lines in a pointed return as shown below and in Figure 4.8. For one
of the straight lines the x offset will be applied to the x component and the y offset applied
to the y component of the common return point; for the other straight line the x offset is
applied to the y component and the y offset to the x component. The orientation of the
primary edge will alter whether the offsets should be added or subtracted to the x and y
components of the common return point to get the end points.
dif f erenceInX ← commonP ointX − f irstP ointX
dif f erenceInY ← commonP ointY − f irstP ointY
γ ← dif f erenceInY ÷ dif f erenceInX
α ← 45 − γ
of f setInX ← requiredStraightLineLength × cos(α)
of f setInY ← requiredStraightLineLength × sin(α)

Calculating the common return point and the two straight lines when the primary edge
is different to the secondary edge is more complicated. Common return points need to
be produced for both edges, then the mid point of these will be calculated. If an infinite
line was drawn which included the common vertex and the mid point of the two common
return points it would be the middle of the two edges. It is on this line where the true
common return point lies, which can be calculated using the common vertex and the mid
point using the same method as the other common return points. The cord will be turned
around the intersection of two edges and the two straight lines in the return tip should each
be parallel to one of the edges to maintain the angel between the edges. If this is ignored
and a default angle between the lines in the tip is assumed the return may be sharper or
flatter than the angle produced by the edges in the graph; which would be against the way
Mercat implements his method as the knot is not fully based on the graph. To make the
lines in the tip parallel to the edges they must have the same x to y ratio as the edges.
The differences in the x and y values in the start and end points of an edge can be viewed
as offsets from the common vertex. If these offsets were applied to the common return
CHAPTER 4. DESIGN 51

(a) Crossing On Single Line

(b) Crossings On Multiple Lines

Figure 4.7: Connecting Crossings In Pointed Returns


CHAPTER 4. DESIGN 52

Figure 4.8: Calculating X & Y Offsets For Straight Lines In Pointed Returns
CHAPTER 4. DESIGN 53

point the resulting lines would be parallel to the edges but they would be the same size.
To compensate for this effect the x and y differences for each edge must be scaled down
by a certain factor. The length of the primary and secondary edges may be different so
the factors used on their x and y differences would also be different. When applied to the
common return point the two resulting straight lines will have the same length. Working
out the offsets which need to be applied can be done in the following way.

lengthprimary ← distance(primaryotherP oint , primarycommonP oint


lengthsecondary ← distance(secondaryotherP oint , primarycommonP oint
ratioprimary ← lengthstraightLine ÷ lengthprimary
ratiosecondary ← lengthstraightLine ÷ lengthsecondary
dif f erenceInXprimary ← primarycommonP ointX − primaryotherP ointX
dif f erenceInYprimary ← primarycommonP ointY − primaryotherP ointY
dif f erenceInXsecondary ← primarycommonP ointX − secondaryotherP ointX
dif f erenceInYsecondary ← primarycommonP ointY − secondaryotherP ointY
dif f erenceInXprimary ← dif f erenceInXprimary × ratioprimary
dif f erenceInYprimary ← dif f erenceInYprimary × ratioprimary
dif f erenceInXsecondary ← dif f erenceInXsecondary × ratiosecondary
dif f erenceInYsecondary ← dif f erenceInYsecondary × ratiosecondary

At this stage the software will have identified the two crossing points to connect and
calculated the three points required to draw the two straight lines at the tip of the return.
To complete the pointed return two curves must be calculated which join the crossing points
to the straight lines. The control points for the curves will be calculated using the process
describe in Section 4.4.5. The start and end points for the first curve will be the original
unconnected crossing point and the end of the straight line nearest to it; the second curve
uses the other crossing point and the end point of the other straight line. Once these curves
have been calculated all of the information will be stored into a PointedReturn object which
is then added to the CelticKnot object.

Calculate Common Return Point

The process for calculating a common return point for use within PointedReturn objects
will take two points (first and second point) which it assumes are on the same infinitely
extended line. The return point will be calculated such that the second point lies between
the first point and the common return point. Calculation of the common return point’s
position will be done using right angled triangles and a fixed distance the common return
point should be from the second point. In the first triangle (∆1 ) the edge between the two
points provided is treated as the hypotenuse which gives the x and y offsets from the first
point to the second point. In the next triangle (∆2 ) the hypotenuse will be the line between
the first point and the common return point, applying trigonometry to the second triangle
CHAPTER 4. DESIGN 54

yields the x and y offsets to transform the first point into the common return point. The
following method is used to determine the required offsets which is illustrated in Figure 4.9.

dif f erenceInX∆1 ← secondP ointX − f irstP ointX


dif f erenceInY∆1 ← secondP ointY − f irstP ointY
hypLength∆1 ← distance(secondP oint, f irstP oint)
γ ← dif f erenceInY∆1 ÷ dif f erenceInX∆1
hypLength∆2 ← hypLength∆1 + returnP ointDistance
dif f erenceInX∆2 ← hypLength∆2 × cos(γ)
dif f erenceInY∆2 ← hypLength∆2 × sin(γ)

Figure 4.9: Calculating The Common Return Point

4.4.5 Calculate Control Points For Curves

Curves will be used in two places within the implemented system, the first is the curved
knot component (see Sections(4.2.2 & 4.4.3)) and the second is within the pointed return
components (see Sections(4.2.2 & 4.4.4)). All of the curves that will be used within the
CHAPTER 4. DESIGN 55

software require four points, the start point, end point and two control points to guide the
curve direction. The start and end points will be calculated in different ways depending on
where the curve is being used, but the control points will always be calculated in the same
way.
One control point (control point one) is calculated from the start point and the line which
contains the start point (start line) and the other control point (control point two) is
calculated using the end point and the line (end line) it is contained within. Control points
one and two will lie on the same infinite lines that contain the start line and the end line
respectively. The location of each control point is calculated using offsets from the start
and end points.

f actorOne ← distance(startP oint, endP oint) ÷ distance(startP oint, startLineotherP oint )


f actorT wo ← distance(startP oint, endP oint) ÷ distance(endP oint, endLineotherP oint )
dif f erenceInXstartLine ← startP ointX − startLineotherP ointX
dif f erenceInYstartLine ← startP ointY − startLineotherP ointY
dif f erenceInXendLine ← endP ointX − endLineotherP ointX
dif f erenceInYendLine ← endP ointY − endLineotherP ointY
controlP ointoneX ← (startP ointX + dif f erenceInXstartLine ) × f actorOne
controlP ointoneY ← (startP ointY + dif f erenceInYstartLine ) × f actorOne
controlP ointtwoX ← (endP ointX + dif f erenceInXstartLine ) × f actorT wo
controlP ointtwoY ← (endP ointY + dif f erenceInYstartLine ) × f actorT wo

The two factors used when multiplying the differences are used to determine how far away
the control point should be from the start or end point. If a constant factor is used then it is
possible that each of the control points will be positioned past the others infinite line as in
Figure 4.10. If a curve was produced using these control points it would actually be a loop
and the cord would cross itself where the two infinite lines meet. A crossing at this point
is not allowed according to the rules of Celtic knot work. Dividing the distance between
the start and end points by the length of the lines containing the start and end points
respectively should give factors which do not allow the overlapping situation to occur. An
upper limit must be imposed on the two factors to ensure that the control points are not
too far from the start and end points, if there is a large distance from the control points to
the start or end point then it is likely that the curve once drawn would intersect another
knot component.

4.5 Linking The Knot Components

A link between two knot components can be defined as a reference stored in each knot
component (see Section 4.2.2) that points to the knot component(s) attached to each of
this components end points (the are four end points & references for the crossings). For
the non crossing type knot components their references will point to crossing objects; in
CHAPTER 4. DESIGN 56

Figure 4.10: Overlapping Curve Control Points

crossing objects the references will point to non crossing objects. The generation phase will
produce all of the knot components required to form the finished knot, however it will not
link the components together or calculate the over and under pattern. The links between
knot components can be seen as the ground work required to calculate the over and under
pattern of the knot and thus need to be carried out first. Linking the components together
will be carried out immediately after the generation phase using the process outlined here.
The linking process can be summarised as taking a crossing line (see Section 4.2.2) which
has one or more of its references not set and then following the knot cord until it returns
to the crossing line. Following the knot cord is achieved by finding the component which
shares an end point with the current component being handled and then moving to the
next component.
Firstly a crossing which does not have all of its references to other components set needs
to be found, then one of the two crossing lines needs to be selected. If the first and second
crossing point references have not been set then the first crossing line will be used; if the
first and second references have been set then the third and forth can not have been set and
thus the second crossing line should be used. The first or third crossing point will be set as
the start point (common point) for the process and the other point on the crossing line is
stored as the point where the cord ends (break point). Using the methods in the CelticKnot
class (see Section 4.2.3) both components using the common point can be found. Only two
components can use the same point, one of which will be a crossing component which is
equal to the current crossing object. The non crossing object returned will be the other
knot component using the common point and this can be linked to the current crossing
and vice versa. The crossing object will have its reference corresponding to the common
CHAPTER 4. DESIGN 57

point set to point to the non crossing object which in turn will have its reference to the
previous crossing object set to the current crossing. The following loop will then be used
to set the references on the components in the current knot cord; the loop is broken when
the current common point is equal to the break point.

1. The next common point can be found by taking the point of the current non crossing
object which is not equal to the current common point

2. The two components using the common point are found, one of which will be equal
to the current non crossing. The current crossing is set to the crossing object from
the two components returned

3. The references in both the non crossing component and the current crossing can be
updated to link the two together

• The non crossing has the reference relating to the next crossing in the cord
updated to the current crossing
• The crossing has the crossing point (which is equal to the common point) refer-
ence updated to point to the current non crossing object

4. The next common point to be used will be the other point on the crossing line which
contains the current common point

• If the current common point is equal to the first point in the crossing then the
new common point is the second crossing point and vice versa
• If the current common point is equal to the third point in the crossing then the
new common point is the forth crossing point and vice versa

5. The two components using the common point are found, one of which is equal to the
current crossing; the current non crossing component is updated to the non crossing
object from the two returned

6. The references in both the non crossing component and the crossing can be updated
to link the two together

• The non crossing has the reference relating to the previous crossing in the cord
updated to point to the current crossing
• The crossing has the crossing point (which is equal to the common point) refer-
ence updated to point to the current non crossing object

7. Goto step 1

This process may need to be run multiple times depending on the number of cords in the
knot. For example a knot based on a graph consisting of a single edge will have a single
cord; however a knot based on a square graph will have two cords as seen in Figure 4.11.
If you apply this process to Figure 4.11(a) then all knot components will have been used
CHAPTER 4. DESIGN 58

after one complete iteration. If you pick a crossing point in Figure 4.11(b) and apply the
same process you will be left with several knot components unused. Figure 4.12 shows an
annotated version of the the knot in Figure 4.11(b). Running this process on this knot
would give the following.

1. Pick a crossing to start the process e.g. Crossing A

2. Pick crossing point 1 as the common point and thus crossing point 2 as the break
point

3. Find all components using the common point, which are Crossing A & Pointed Return
B

4. Set the references

• Set crossing point 1 reference in Crossing A to point to Pointed Return B


• Set the from crossing reference in Pointed Return B to point to Crossing A

5. The main loop can then be run (following is shorthand for each iteration)

i CommonP oint ← CrossingP oint5


Crossing C found using common point
P ointedReturnBT oCrossing ← CrossingC
CrossingCCrossingP oint5Ref ← P ointedReturnB
CommonP oint ← CrossingP oint6
Straight Line D found using common point
CrossingCCrossingP oint6Ref ← StraightLineD
StraightLineDF romCrossing ← CrossingA
ii CommonP oint ← CrossingP oint9
Crossing D found using common point
StraightLineDT oCrossing ← CrossingD
CrossingDCrossingP oint9Ref ← StraightLineD
CommonP oint ← CrossingP oint10
Pointed Return C found using common point
CrossingDCrossingP oint10Ref ← P ointedReturnC
P ointedReturnCF romCrossing ← CrossingD
iii CommonP oint ← CrossingP oint14
Crossing B found using common point
P ointedReturnCT oCrossing ← CrossingB
CrossingBCrossingP oint14Ref ← P ointedReturnC
CommonP oint ← CrossingP oint13
Straight Line A found using common point
CrossingBCrossingP oint13Ref ← StraightLineA
StraightLineAF romCrossing ← CrossingB
CHAPTER 4. DESIGN 59

iv CommonP oint ← CrossingP oint2


Crossing A found using common point
StraightLineAT oCrossing ← CrossingA
CrossingACrossingP oint2Ref ← StraightLineA
v Loop Exits

6. This leaves Pointed Returns A & D, Straight Lines B & C and Crossing Points 3, 4,
7, 8, 11, 12, 15 & 16 unused in the process

(a) Knot Based On Single Line Graph (b) Knot Based On Square Graph

Figure 4.11: Knots With Different Number Of Cords

To overcome this problem the above process needs to be surrounded by another loop, which
will be exited once all crossing objects have all their references set.

1. Get a list of all crossings with one or more missing references to a non crossing
component

2. Run the process starting with the first crossing in the list

3. Goto step 1

After the knot components have been linked together the over and under pattern needs to
be calculated. Using the references set in the linking phase this process can move quickly
from the current component to the next component. Following the references in each knot
component will follow a cord through the knot and thus may need to be run multiple times
if more than one cord exists. Again an outer loop will be used to cover all cords in the
knot.
CHAPTER 4. DESIGN 60

(a) Numbered Crossing Points

(b) Knot Components Named

Figure 4.12: Knots With Different Number Of Cords: Annotated


CHAPTER 4. DESIGN 61

1. Get a list of all crossings where both crossing lines have not been used in the over
and under pattern calculation

2. Run the process below on the first crossing in the list

3. Goto step 1

The process to calculate the over and under pattern will be started with a crossing (previous
crossing) where either the first or second crossing lines have not been followed. If neither
line has been followed then this crossing will be set such that its first crossing line travels
over the second line and the first crossing line is marked as having been followed in the
crossing. The first crossing point reference in the crossing will be used to the non crossing
object connected to the first crossing point. The loop break point will be set to the second
crossing point. If one line has been followed already then the over and under pattern for
this crossing will have been set previously. The over and under value of this crossing can be
retrieved and the crossing line not already followed is marked as having been followed. The
current non crossing component will be set to the reference for the first or third crossing
point depending on which crossing line had been followed before; similarly the break point
will be set to the second or forth crossing point. The following process will be run in a loop
to set the over and under pattern for this cord, which is broken when the current common
point is equal to the break point.

nextCrossing ← currentN onCrossingnonCommonP ointRef


if (currentN onCrossingConnects(previousCrossingcrossingLineOne , nextCrossingcrossingLineOne )∨
currentN onCrossingConnects(previousCrossingcrossingLineT wo , nextCrossingcrossingLineT wo ))
{
f irstCrossingLineOverSecond ← ¬f irstCrossingLineOverSecond
}
nextCrossingf irstCrossingLineOverSecond ← f irstCrossingLineOverSecond
setCrossingLineAsF ollowed(nextCrossing, commonP oint)
commonP oint ← getOtherP ointOnCrossingLine(nextCrossing, commonP oint)
currentN onCrossing ← nextCrossingcommonP ointRef
previousCrossing ← nextCrossing

The logic in this process is required to ensure the over and under pattern will be set
correctly. The over and under pattern will be stored in the crossing objects using the
notion of the first crossing line being on top (over) the second or vice versa. There is
no guarantee that a non crossing component will connect any particular combination of
crossing lines (e.g. first to first or second to second), therefore the software can not assume
that the first line in a crossing will always be over the second line in the current crossing
if it was the other way round in the previous crossing. For example the following situation
would lead to an incorrect pattern using the previous assumption. The first crossing is
selected and the first crossing line is set to be over the second. A straight line component
CHAPTER 4. DESIGN 62

connects the first crossing line in the first crossing to the second crossing line in a second
crossing object. If the second crossing is set to use the opposite over and under ordering
of the previous crossing, the first line will be set to go under the second crossing line. The
result of this is that the crossing line at each end of the straight line component are on
top of the other crossing lines in their respective crossing objects. This is clearly a breach
of the the rules of Celtic knot work (see Section 2.2.2). By understanding which crossing
lines (i.e. namesakes) are being connected the software can determine what over and under
assignment to use. Using this information on the example the software would understand
that the crossings being connected are not namesakes and thus assign the second crossing
the same over and under value as the first. This would result in the crossing lines at either
end of the straight line component having the correct over and under pattern. The crossing
line in the first crossing would be set to over and the crossing line in the second crossing
would be under.

4.6 Drawing & Displaying The Knot

After the knot components have been produced and linked together it should be possible
to display the knot on screen or render it to a file; both of which will make use of the
same core process. Each knot component will contain the functionality required to draw
the shape(s) that it consists of. All of the non crossing components should be rendered
first followed by the crossing components. As the non crossing components do not overlap
other components all of the shapes within each component can be drawn at the same time.
The crossings will be rendered after the non crossing components to ensure that they are
displayed on top. Using the over and under information stored within a crossing object
allows the system to decide which crossing line should be drawn first. Drawing one crossing
line on top of the other gives the impression that the knot cord goes over itself at the point
where it crosses.
When rendering the knot the software will use the knot preferences specified by the user to
determine the cord and background colours and the thicknesses of the cord and its outline.
To give the impression that the knot has an outline it will actually be drawn twice. The
first knot to be drawn will use the outline preferences and the second knot which will make
use of the interior preferences is drawn on top of the first.

4.7 Knot Preferences

The knot preferences that will be built into the software affect certain aspects of the knot’s
appearance; they must not alter the structure or shape of the knot. These preferences
can be altered by the user during the graph creation stage or once the knot has been
produced and displayed. All of the preferences will be stored in variables located in the
class representing the graph area and can be passed to other classes where appropriate.
CHAPTER 4. DESIGN 63

The areas affected by these parameters are the background on which the knot is displayed
and the knot itself. Visually the knot has a border or outline (see section 4.6) around the
main cord interior, both of which have their own preferences.

4.7.1 Colours

All of the paints used for the knot and the background will be gradient paints which run
from the top left corner to the bottom right; which allows the user to set colour transitions
if they wish. Each gradient paint requires two colours, thus six different colour variables
will be needed to represent the background and the two lines in the knot (outline and knot
interior); all of which can be set independently. On screen the user should be able to see
the samples of all the colours in use, along with options to change each of the colours.
The standard Java libraries contain classes such as JColorChooser which may be suitable
for use in changing the colours that will be present in the software. When the user changes
a colour the software will need to redraw the knot if it has been generated to reflect the
colour change; the sample representing this colour would also need to be updated.

4.7.2 Line Thicknesses

The knot cord outline and interior will have independent thicknesses that the user will be
able to change. To maintain a visible outline on the knot the outline thickness must always
be greater than the thickness of the interior. If the interior was thicker than the outline,
only the interior would be displayed due to the way the knot is drawn. Checks must be in
place to ensure minimum thicknesses of both the interior and outline. Allowing the cord
to become too thin would result in the knot being displayed incorrectly or not all.
Chapter 5

Implementation

This section of the document aims to give the reader an overview of how certain aspects
of the design (see Section 4) have been implemented. This section is intended to follow
on from and add to the information in the design section and as such only the details not
included in the design are shown here. A full code listing for the project can be found in
Appendix B.

5.1 The Knot Components

Straight line, curved and pointed return components are represented by classes (see Sec-
tions(B.2.6, B.2.3 & B.2.5) respectively) which contain minimal functionality consisting of
the required points for each type, the references to the two crossing objects and the relevant
getter and setter methods. The class representing the crossing (see Section B.2.2) makes
more use of meta data than any of the other knot components. The class contains the four
points used to represent the end points of the two crossing lines, four references to knot
components (one for each crossing point) and four boolean variables for the over and under
pattern. Two of the boolean variables show if the two crossing lines have been followed
whilst setting the over and under pattern. The third boolean is used to represent the actual
over and under pattern. If the first crossing line is over the second it is set to true, when
the first line travels under the second the variable is set to false. In Java booleans are
initialised to false, so when a crossing is created the third boolean variable automatically
gets the value false. The forth boolean is used to show if the third has actually been set
during the over and under calculation. The forth boolean is set to true once the third
boolean has been set.

64
CHAPTER 5. IMPLEMENTATION 65

5.2 The Celtic Knot Class

When implemented the CelticKnot class (see Section B.2.1) contains several key methods
for linking the knot components together, calculating the over and under pattern (see Sec-
tion 5.5) as well as support methods for other phases in the knot creation phases. The
method to calculate unconnected crossing points is of particular interest due to the way
in which it presents the crossing points. Crossing lines are returned with the unconnected
crossing point as the first point in the line. Thus if both points on a crossing line are uncon-
nected two lines will be returned. The points are needed in combination with their crossing
lines to calculate the curved and pointed return knot components. One line instance per
unconnected crossing point is used so that extra meta data about which point in the line is
unconnected is not required. By putting the unconnected crossing point in the first point
place holder within the line, any methods which are interested in the unconnected point
can just extract the first point, knowing it needs connecting.
To find unconnected crossing points a list of all crossing lines is iterated over, with each
crossing line analysed in turn. A list is generated for each of the two points (start and end
points) in the line of all components using that point. If the size of the list of components
is equal to one then the point is unconnected as it is only being used by the crossing. If
the unconnected crossing point is the first point in the crossing line, it can be added to
a list of unconnected lines. If the unconnected point is the second point, the order of the
points must be reversed so the second point is in the first point place holder. A new line
is created which has its first point set to the second point of crossing line and its second
point set to the first points of the crossing line.

5.3 The Graph Area

As discussed in the design (see Section 4.3) the graph area presented to the user has been
implemented using a JPanel with horizontal and vertical lines drawn on top to resemble
physical graph paper. MouseListeners and MouseAdapters have been implemented to cap-
ture the user’s movement; the mouse position is taken when the user performs an action
and the nearest grid line intersection is calculated using the process describe previously.
Vertices and edges are stored as point objects in three ArrayListshPoint2Di objects within
the graph area class.
Unfortunately it has not been possible to implement the software generated improvements
discussed in the design (see Section 4.3.4) due to time constraints. Implementing this aspect
of the software should be achievable using the design already outlined. Java features several
methods associated with the transformation of points which could be utilised to determine
if an edge (which is made from two points) existed on both sides of a plane as a mirror
image of itself. If the edge did not exist on both sides of the plane the data calculated
could easily be used to give the locations of points needed to create the mirror image of the
edge. Checking to see if two crossing are two close to each other can be implemented by
CHAPTER 5. IMPLEMENTATION 66

making use of the distance function associated with points. This would give the straight
line distance between two crossing points which could then be compared to a pre-defined
minimum distance. If the crossing points are too close, a process could be implemented
which calculates the ideal locations of the two crossing points. From here the location of the
crossing mid points could be established; the edges (and the crossings on them) could then
be translated using the newly calculated points, to give edge locations with the appropriate
separation.

5.4 Generating The Knot Components

The processes to create the four knot components have been implemented using the methods
and approaches outlined in the design section (see Section 4.4). The key concepts and ideas
outline previously have not changed significantly although there have been additions and
other minor changes to the processes to actually implement some of the functionality.

5.4.1 Calculating The Crossing Components

The overall process to produce the crossing objects has not changed from the design to
the implementation. A reasonable amount of conditional logic has been introduced to the
process to calculate the orientation of the edge being worked on. The orientation of the
edge is worked out by looking at the x and y components of the two points that the edge is
made up of. Once the orientation is known the correct angle required to work out the x and
y offsets from the edge mid point can be calculated. Ignoring the orientation of the edge
can lead to incorrect offsets, which when applied to the mid point will result in crossing
points at the wrong location.
A small length has been used for the crossing lines in the software, this is to improve the
appearance of the knot once drawn. The larger the crossing lines become the distance
between crossing lines which should connected becomes smaller. In the majority of knots
this will not present a problem, however where the distance between crossing lines (or
between crossing line and the straight lines in the pointed return tip) is already small
the knot is likely to become unattractive. As the distance decreases the bends in curved
components (or the curves in pointed returns) will appear to become sharper. Very sharp
bends generally do not look as attractive or elegant as shallow smooth curves. By keeping
the size of the crossing lines small it maximises the chance of using a shallow curve.

5.4.2 Calculating The Straight Line Components

Generating the straight line knot components is carried out using much of the process
described in the design with one significant change. The main part of the process remains
unchanged, a list of crossing lines is obtained from the CelticKnot object stored within
the knot generator, which is then looped over extracting the crossing line at the current
CHAPTER 5. IMPLEMENTATION 67

loop index. The list of crossing lines is then searched to find any other crossing lines which
lie on the same infinitely extended line as the current crossing line; any matching crossing
lines are stored in a temporary list. The pseudo code describe in Section 4.4.2 which selects
which crossing lines from the temporary list are closest to the current crossing line maps
almost directly into actual Java code. The check called noObstruction in the pseudo code
has been implemented using the two points in the potential straight line and lists containing
the start and end points of all edges. A check is performed to see if the two lines intersect
each other and that none of the points are equal to each other. If the two lines have a
common point at either end they are classed as intersecting. For the purpose of this process
the software is not concerned if the lines have a common start or end point, only if they
intersect at another location.
The change from the original design is the addition of a restriction on the crossing lines
which can be selected to connect to the current crossing line. The two vertices in the
crossing line’s edge are found using the mid point of the crossing and the edge mid point.
When searching through the list of all crossing lines to find the lines which are on the same
infinitely extended line as the current, an extra condition is imposed before the crossing
lines being checked can be added to the temporary list. The crossing line being checked
must share one edge vertex with the original crossing line. The vertices of the crossing line
being checked are found and if either of them are equal to the original crossing line’s edge
vertices, the crossing line being checked is added to the temporary list. This extra check
ensures that a crossing line can only connect to a crossing line on an edge which is directly
connected to its edge. Excluding this check enabled crossing lines to connect to other
crossing lines which they should not have been allowed to connect to. Figure 5.1 shows
two knots which are based on the same input graph. The first knot (Figure 5.1(a)) was
generated without the additional restriction and the second (Figure 5.1(b)) was generated
with the restriction in place. The cord in the first knot is messy and does not follow the
over and under pattern. This knot appears to have extra crossings compared to the second
knot, which is caused by straight line components joining crossing lines on opposite sides
of the graph; in doing so the straight line components cross over each other. The points
where the straight line components cross are the locations which break the over and under
pattern.

5.4.3 Calculating The Curved Components

Curved knot components are generated using the process outlined in the design section.
Finding the edge which corresponds to the current crossing has been implemented as a
search using the crossing line (and edge) mid point. The mid point of the current crossing
line is calculated, and then compared to the mid point of each edge in the software. To
compensate for rounding and casting errors relating to the position of the mid point, the
comparison between the crossing line mid point and edge mid point is done using the
distance between them rather than a direct comparison of x and y values. A reference to
the edge with the nearest mid point to that of the crossing line is created. Each edge is
processed in a loop, the distance from its mid point to the crossing line mid point compared
CHAPTER 5. IMPLEMENTATION 68

(a) Knot Generated Without Additional Re- (b) Knot Generated With Additional Restric-
striction tion

Figure 5.1: Knots From The Same Graph With & Without Additional Constraint

to the current nearest. If this edge’s mid point is closer it becomes the current nearest.
The search area described in the design, has been implemented using the Polygon class.
In Java a Polygon is created using a series of x and y coordinates of points that should be
connected together by straight lines. The Polygon automatically joins the last point entered
to the first to create a complete shape. For this implementation a Polygon is created using
the original crossing point and the two vertices on the secondary edge forming a triangle.
Polygon implements the Shape interface which provides a method to check if a point is
inside the shape’s boundaries. To find the correct crossing point to connect to the original,
the closest point to the original that is inside the Polygon is selected.
If the original crossing point has been connected to another crossing point via a curved
component, both points are removed from the list of unconnected crossing points. The loop
index is set to -1 at this point; when the current iteration finishes the index is automatically
incremented to 0. This is necessary to ensure that all points in the list of unconnected
crossing points are processed. If this was not in place and the following situation occurred
unconnected crossing points would be skipped. The loop index is at 0, the original crossing
point has been connected to the point in position 1. These two points are removed from
the list and every point’s position will be decreased by two. The index is incremented to
1 and the crossing point is extracted from this location and processed. The crossing point
now residing in position 0 has effectively been skipped. By setting the loop index to -1
every time crossing points are removed from the list, the loop will always check every point
in the list at least once.
CHAPTER 5. IMPLEMENTATION 69

5.4.4 Calculating The Pointed Returns

The process to create the pointed return knot components follows the process described
in the design. Its implementation looks similar to elements of the curved line and straight
line creation processes. Like the curved line generation process this makes use of the same
methods to calculate the correct edge for a given crossing line and similar logic for removing
unconnected crossing points. When calculating the common return point, logic is in place to
determine the orientation of the line containing the two points used to generate the return
point. The orientation logic is required so the software can work out if offsets should be
added or subtracted to the point.

5.5 Linking The Knot Components

Linking the knot components together has been implemented using the process discussed in
the design. The bulk of the implementation of this process focuses on the logic to determine
which crossing point is currently the common point and thus which point should be the
next common point. This is done using direct comparisons of the common point against
the four crossing points until a match is found.
The over and under pattern is set using a similar process to the linking together of knot
components, except that it does not need to search for the next component to use. The
component to use after the current can be quickly found using the references that link
components together. In the original design the process to generate the over and under
pattern picked a starting crossing where not all of the crossing lines had been followed in
the process so far. This requirement was not strict enough to produce accurate results in
all cases. A crossing that had neither of its crossing lines followed, matches the original
requirement as a starting crossing. In the majority of cases this is acceptable and the
resulting pattern is correct. In certain cases the resulting pattern would be incorrect with
two ’overs’ or two ’unders’ following each other in succession. This occurred when one
cord in the knot had already been followed, leaving some crossings partially followed. The
partially followed crossings had their over and under pattern correctly assigned. If the first
crossing to be selected in the second cord had neither crossing line followed, it would have
its over and under pattern set to ’over’. If this crossing connected to one of the partially
complete crossings, it is possible that the cord being followed would be over (or under)
in both crossings. The partially complete crossing would not have its pattern updated as
this would break the pattern for the first cord followed. When the process completed an
incorrect pattern could be left. The solution to this problem is to always select a partially
followed crossing (if available) as the starting crossing. As described in the design, if a
partially followed crossing is used as the start point, its over and under pattern is retrieved
and used as the starting value for the cord being followed. This ensures that the correct
pattern is maintained throughout the knot.
CHAPTER 5. IMPLEMENTATION 70

5.6 Drawing The Knot

Rendering the finished knot to screen (or to file) is carried out using a Graphics2D object,
obtained from the class containing the graph area. The knot is made up of a collection of
geometric shapes stored within the knot components. Graphics2D has specific methods to
paint each type of shape used within the software; each type of knot component calls the
relevant method(s) in Graphics2D to paint the shape(s) it is made from. GradientPaint is
used to provide the user with colour transition effects in the finished piece of knot work.
The GradientPaint objects used in the software, transition one of their two input colours
into the second in a diagonal from the top left of the image to the bottom right.
All of the non crossing knot components are drawn first then the crossing components are
drawn; this ensures that the crossing components are on top of the non crossing objects.
When the non crossing components are drawn their ends may over lap due to the thickness
of the cord (see Section 6.2). By drawing the crossing components after the other knot
components any cord overlap is hidden beneath the crossing object.
Chapter 6

Testing & Evaluation

This section of the document aims to provide the reader with information about the testing
carried out on the software written for this project. The testing is primarily focused on
ensuring that the software produces the correct piece of knot work for a given graph.

6.1 Mercat’s Method

The method implemented for this project in the software was that proposed by Mercat
(see Section 2.4.2). This method takes a undirected graph and then builds a Celtic knot
around the vertices and edges within the graph. On Mercat’s website (Mercat, n.d.) he
has a hand drawn example of his process, to show the software implementing this method
the same example has been reproduced. Figure 6.1 shows the four basic stages outlined
in the method implemented by the software; the second and third stages are not normally
shown in the software.

6.2 The Knot Components

Each knot produced by the system is consists of a collection of four different kinds of
components (see Sections(4.2.2 & 5.1). Once these components have been generated and
linked together, they are drawn to the screen (see Section 5.6) and the user has the option
to save the knot as an image file. Figure 6.2 shows a complete Celtic knot which contains
all four types of components and the graph used to create it; Figure 6.4 shows each of
the four knot components individually laid on top of a greyed out version of the whole
knot. The size of the crossing lines is reasonably small and as such the crossing points
are close together which can lead to the ends of non crossing knot components appearing
to overlap each other as seen in Figures(6.4(b) & 6.4(c)). Non crossing components do
not actually overlap each other in the software, they all use distinct start and end points;
however when the components are drawn with a thick cord as opposed to a thin cord

71
CHAPTER 6. TESTING & EVALUATION 72

(a) The Input Graph (b) The Crossings

(c) The Crossings Linked Together (d) Finished Knot

Figure 6.1: Software Implementation Of Mercat’s Method


CHAPTER 6. TESTING & EVALUATION 73

(a) The Original Graph (b) The Finished Knot

Figure 6.2: A Complete Celtic Knot Generated By The Software

the edges of the cord may overlap as shown in Figure 6.3. The crossings are then drawn
on top of the other components which hides the overlap of other knot components which
completes the pattern. If the crossings were made bigger and the maximum cord thickness
was kept the same, the cord(s) in non crossing knot components should not overlap with
each other. There are problems with this approach as discussed in Section 5.4.1, which is
why the current approach of drawing small crossings on top of the non crossing has been
adopted. The crossings (Figure 6.4(a)) have been generated at the mid point of each edge
on the original graph (Figure 6.2(a)). All of the edges in the graph are parallel to one of
the two axes, which means all of the crossing lines are at 45o to the axes. Crossings on
perpendicular edges with a common vertex will have crossing lines on the same infinitely
extended line and as such are connected via a straight line component as in Figure 6.4(b).
All of the remaining crossing lines, except for those pointing to a corner of the graph will
be connected by curved lines (Figure 6.4(c)). The eight crossing lines pointing towards the
four corners of the graph will be joined using a pointed return component at each corner
(Figure 6.4(d)). Once all four types of components have been drawn to screen (or to file)
the knot will be complete.

6.3 The Effect Of Different Graphs

Mercat’s method for generating Celtic knot work relies heavily on the graph created by
the user. Different graphs will lead to different knots and patterns. To demonstrate this
concept within the software the knot in Figure 6.5(a) can be transformed into the knot in
Figure 6.5(b) by altering the original graph. The starting point uses the simplest graph
CHAPTER 6. TESTING & EVALUATION 74

Figure 6.3: Overlapping Components Using Thick Cord

that will actually produce a knot, a single edge (Figure 6.5(c)) where as the second knot
uses a rectangular graph made up of square sub graphs (Figure 6.5(d)). By adding extra
vertices and edges the first graph can easily be made into the second. Each new edge will
give a different graph, however for this document only major changes are shown. The first
step is to go from the single vertical edge to three vertical edges (Figures(6.6(a) & 6.6(b)),
which produces a similar knot to the original but with extra crossings in the cord. From
here eight horizontal edges are added to the graph, four at the top of the existing edges
and four at the bottom (Figures(6.6(c) & 6.6(d)). The knot from this graph looks like
three copies of the previous graph, two of which have been rotated 90o and then centred
at the top and bottom of the third knot. The next step is to add the vertical edges to
the left and right edges of the graph (Figures(6.6(e) & 6.6(f)) producing a knot whose
edge closely resembles the edge of the final knot. Adding the remaining horizontal edges
to the graph (Figures(6.6(g) & 6.6(h)) results in very similar knot to step 3, however the
horizontal pattern on the top (or bottom) of the knot is repeated throughout the knot.
The final stage is to add the remaining vertical edges to the graph, which results in the
finished knot. This is just one of many ways to create the final knot from the first; making
different choices as to which edges to add and when could result each step looking distinctly
different.
CHAPTER 6. TESTING & EVALUATION 75

(a) Crossing Components (b) Straight Line Components

(c) Curved Components (d) Pointed Return Components

Figure 6.4: Highlighted Knot Components Generated By The Software


CHAPTER 6. TESTING & EVALUATION 76

(a) Starting Knot (b) Ending Knot

(c) Graph Of Starting Knot (d) Graph Of Ending Knot

Figure 6.5: Transforming One Knot To Another: Starting & Ending Knots

6.3.1 Symmetry Of Knots

All of the graphs used in the above transformation were symmetrical on at least two planes,
horizontal and vertical lines running through the middle of the knot and its graph. Sym-
metrical graphs and thus symmetrical knots are not a requirement of the system, although
they one of the rules in Celtic knot work (see Section 2.2.2). Not including this rule in the
system, allows the user to produce graphs that are not completely Celtic in style but it
increases the freedom and creativeness of the user. If a non symmetrical graph is used the
software will still follow the other rules and principles such as the over and under pattern.
The resulting knot work will have Celtic look and feel, but it will not be a true Celtic knot.
Figure 6.7 shows the final graph and knot from above with certain edges removed to break
the knot symmetry.
CHAPTER 6. TESTING & EVALUATION 77

(a) Step 1: Graph (b) Step 1: Knot

(c) Step 2: Graph (d) Step 2: Knot

(e) Step 3: Graph (f) Step 3: Knot

(g) Step 4: Graph (h) Step 4: Knot

Figure 6.6: Transforming One Knot To Another: Key Steps


CHAPTER 6. TESTING & EVALUATION 78

(a) Unsymmetrical Graph (b) Unsymmetrical Knot

Figure 6.7: Breaking Symmetry

6.4 Celtic Knot Work In Borders

Celtic knot work was often used to fill space in borders and other areas in illuminated
manuscripts (Figures(2.5 & 2.6 & 2.7 & 2.8), mosaics (Figure 2.3) and many other items.
The knot shown earlier (Figure 6.5(b)) is similar to the knot work used in Celtic jewellery
found in Ireland (Figure 2.4). The main difference between the brooch and the knot from
the software is that the knot work in brooch generally has three horizontal lines where as
the software knot has four. By removing the top (or bottom) horizontal edges in the graph
and the vertical edges directly connected to horizontal edges, an almost identical knot
would be formed. The mosaics shown make use of knot work which can be generated in
the software by linking a series of edges on the same infinite line together. This shows that
the software is capable of producing knot work which is very similar to the knot work used
in original Celtic art work. More complex examples of Celtic knot work can be seen in the
illuminated manuscripts, the software can directly reproduce the same knot work pattern
found in some of the manuscripts (Figure 2.6). Some of the knot work in the manuscripts
is to complicated (Figure 2.7) for the software to reproduce in its entirety, however it can
reproduce smaller elements and subsections of the originals.
Figure 6.8 shows three examples of the knot work and the graphs they come from that
could be suitable for use within borders or as space fillers in other work.

6.5 More Examples Of Celtic Knot Work

Appendix A contains more examples of Celtic knot work produced using the software
developed for this project. Each knot is shown alongside the graph used to create it for
reference.
CHAPTER 6. TESTING & EVALUATION 79

6.6 Testing Against Requirements

Several key requirements were produced for this project (see Section 3.4) to guide what
functionality and attributes the software developed should contain. The software can be
tested against these requirements to ensure it meets the original aims. Producing Celtic
knot work was the first and most important requirement; the knot work already described
in this section demonstrates that the software meets this requirement. The implemented
method to generate the knot work (see Section 5.4) builds a knot based on the edges in
the users graph, using the algorithmic approach created by Christian Mercat. If the graph
changes so will the finished knot; this can be seen in the example above which converts
one knot into another by modifying the graph used. This satisfies the second and third
requirements, by implementing an algorithmic approach which creates knot work based on
a graph produced by the user.
The forth requirement stated that the interface to be developed should be simple and
intuitive to use. This is a largely subjective requirement as an interface which is easy to
use for one person may be hard to use for a different person. In my opinion the interface
follows several good design principles which help to give a simple interface. The software
only presents users with options relevant to the task they are doing for example the options
relating to graph production are knot available once the knot has been generated and vice
versa. Options which are applicable at all times (for example changing the knot appearance)
are always available to the user and are in a the same location at all times to provide
consistency. Use of colour is minimal and there are not contrasting colours (unless the
user chooses contrasting colours for use within their knot). No functions have been given
multiple actions depending on the current system status to help avoid contusion in the
user. The majority of the interface makes use of the standard Java classes and thus has the
look and feel of many Java applications. The interface used could be improved by applying
more techniques, approaches and methodologies used by design experts.
Developing the software in Java meets the platform independent requirement as the software
should run on any system for which a Java virtual machine has been written. For the
purposes of this project the software has been tested on Windows XP (Service Pack 2) and
Mac OS X (Leopard) both running Java 6 (required by the software).

6.7 Summary

The software produced for this project has been tested in two aspects. Firstly the software
has been tested to see that the functionality implemented works correctly and that it meets
the requirements set out earlier in project. Secondly that the Celtic knots produced by the
software are accurate in terms of structure and style; the structure of the knot depends on
the input graph and the style is governed by the rules and guidelines for Celtic knot work.
The graph produced by the user is central to the whole knot generation process as the knot
is formed around the graph. As shown distinct graphs will lead to distinct knots and one
CHAPTER 6. TESTING & EVALUATION 80

knot can be made into another by editing the graph. Many of the rules and guidelines that
govern Celtic knot work have been implemented to produce knot work which is Celtic in
style. Using unsymmetrical graphs will produce unsymmetrical knots which can be difficult
to use within a repeating pattern, but this does allow the user greater flexibility in the knots
they create and they can see the effect that making a graph unsymmetrical will have on
the knot.
CHAPTER 6. TESTING & EVALUATION 81

(a) Octagonal Graph (b) Octagonal Knot

(c) Square Graph (d) Square Knot

(e) Corners Graph (f) Corners Knot

Figure 6.8: Border Knot Work


Chapter 7

Conclusion

This project has successfully implemented a software program which creates Celtic knot
work based on user parameters. The primary parameter entered into the software by the
user is an undirected graph. Using the method developed by Christian Mercat a Celtic
knot is built out of the graph provided. The mid point of each edge in the graph is used
as the location for the crossings in the knot. The crossings are joined together using one
of three components (straight lines, curved lines and returns) depending on the structure
of the graph around each crossing.

7.1 Background & Literature

To gain an insight into the background and an understanding of existing approaches into
producing Celtic knot work, research was carried out on the area. The research into the
background revealed that the knot work we call Celtic had origins and influences much
further back in history than just the Celtic periods in the British Isles and Ireland. Celtic
artwork may have originated as far back in time as 500BC in parts of Europe, with minimal
amounts of knot work being used. During the Roman occupation of Britain knot work and
interlace became more common, being used within mosaics in villas and other places of
importance. Much of the knot work used was in the borders or edges of the mosaics. When
the Romans left Britain the native Celtic peoples retained many aspects of Roman culture.
Celtic artwork in Britain and Ireland began to feature knot work and interlace with very
similar patterns to that found in the Roman mosaics. The knot work became more intricate
and elaborate as time progressed and by the time Christianity had spread to most of Britain
and Ireland, the knot work was at its most elaborate. Works devoted to God, especially
manuscripts were decorated with large quantities of knot work; the patterns found in these
manuscripts are now normally referred to when talking about Celtic knot work.
There are a number of methods and approaches to producing Celtic knot work, the majority
of which are concerned with drawing knot work by hand. The approaches can be divided

82
CHAPTER 7. CONCLUSION 83

into two distinct categories, the first group is known as the tile methods which rely on
connecting pre-defined sections of knot work together to produce a piece of knot work.
The approaches in the second group are often referred to as algorithmic methods; these
build a knot using a certain process, if the input to the process changes the knot should
also change. In general the algorithmic approaches are better for creating complex knot
work, although the tile methods can quickly produce simple knot work. Several of the
algorithmic approaches use undirected graphs to build a knot around, the method created
by Mercat is an example of this. This method was chosen for this project as it was felt
that the process could be mapped into steps that a piece of software could carry out. The
other methods featured processes which require the user to have existing artistic talent or
flare to produce the graph or to draw the knot from the graph, which would be extremely
complicated to model in a computer program. Mercat’s method produced results of equal
quality to the other methods when drawing by hand; which combined with its ability to
convert any graph into a piece of knot work without the same artistic talent as the other
methods made it the perfect choice for this project.

7.2 The Software

Based on a set of requirements the design for the software was produced. To enable the
user to provide the software with an undirected graph an area that represents simple graph
paper was designed. Once the user had completed their graph, Mercat’s method is applied
in stages to build up the knot. The first step is to create a crossing at the mid point of
each edge; the exact position and orientation of the crossing depends on the orientation
of the edge in the graph area. The next stage in Mercat’s method is to link the crossings
together to create a complete knot. When drawing the knots by hand it is intuitive which
crossings should be joined together and how they should be joined. The decision on which
crossings to connect is made by looking at the graph and working out where the rest of the
knot can be drawn such that it follows the structure of the graph and does not intersect
any edges (all the intersection of edges is done by the crossings). The decision as to which
crossings to join has been converted into rules that a computer understands; the ability
to do this is why this method was chosen. The position of a crossing on an edge and
angle between two edges which share a vertex determines how a crossing on one edge is
connected to a crossing on the second edge. Within the software this is done in distinct
steps, firstly the crossings which should be joined by straight lines are connected, followed
by the crossings which require a curve to connect them and finally the crossing(s) that
make use of pointed returns are connected. It would be possible to combine these into a
single step, however this would lead to large amounts of logic in the same method(s) which
would be highly unmaintainable. Once the knot is complete the next stage of Mercat’s
method is to calculate the over and under pattern of the cord(s) in the knot. If a cord
goes through two consecutive intersections (crossings) then it passes over the other cord in
the intersection on one occasion and under the second cord at the next intersection. The
software creates this pattern by following the cords through the knot one by one, setting
CHAPTER 7. CONCLUSION 84

the over and under patter at each crossing it encounters. The final part of the process when
drawing the knot by hand is to thicken the knot from the single thin line to a thicker line,
which is created by drawing two lines parallel to each existing line using the over and under
pattern to sort out any overlaps. This part of the process is much simpler in the software
as the existing lines simply have their thicknesses increased and the knot is rendered using
the user’s chosen colours.
In testing the software and the knots it produces were examined to ensure that they were
correct (e.g. had the correct over and under pattern). The software was used to produce
knots that are similar to those found in existing examples of Celtic knot work such as
the illuminated manuscripts or the mosaics which heavily influenced the Celtic style. The
software is not capable of reproducing all of the knot work in the manuscripts exactly, but
it can produce knot work used within the stylised first letters, the page borders and certain
sections of the most complicated knots.

7.3 Future Work

7.3.1 The Software Generated Improvements

Unfortunately the functionality to provide the user with improvements to their graph could
not be implemented due to time constraints. The initial design for this functionality (see
Section 4.3.4) could be implemented to provide the desired functionality. If the design was
implemented in its present form, the software would make suggestions based on relatively
simple rules. The user’s graph would be checked for symmetry and the distance between
edges checked to ensure they are not too close to each other. The improvements suggested
by the software would give the user knot work which is more consistent with the rules and
guidelines; which in turn should give more attractive knot work than not including the
suggestions. The suggestions provided by the software may help user’s create knots which
are Celtic in style, but they are not likely to help the user understand the artistic element
of what makes the knot attractive.
If the software was able to analyse the user’s graph and make suggestions that altered the
key structure of the graph, (and thus the knot) to make the finished piece more attractive
this may be highly beneficial to the user. The benefits could be even greater if the software
could explain the artistic principles behind the suggestions it is making. A potential way to
achieve this functionality would be to move the software towards being an expert system,
which understands the key artistic concepts behind knot work. It could then analyse a
graph and make suggestions for edge addition and / or subtraction to improve the artistic
quality of the knot work.
CHAPTER 7. CONCLUSION 85

7.3.2 Knot Tutor

The improvements mechanism in the software could be further extended to turn the software
into a Celtic knot tutor. The software could act as a tutor in two different ways. Firstly the
software could help teach a user about the concepts and processes involved in making Celtic
knot work. By presenting the user with graphs and explaining how the knot components
are constructed and joined together, the user may be able to build their understanding
of how to create knot work. This aspect may be particularly effective in teaching users
about Celtic knot work if other methods of generating Celtic knot work were incorporated.
Currently the software uses Mercat’s method (see Section 2.4.2) to produce Celtic knot
work from an input graph; by incorporating and contrasting other methods, such as Marc
Wallace’s method (see Section 2.4.2), the software could show the user how one graph forms
different knots using different techniques.
The second area where the software could acts a tutor is helping users create knot work for
a specific task. The user could choose the purpose of the knot work they are creating and
this would influence the improvements that the software suggests to the user. For example
if the user was creating a piece of knot work to be used in a border, the software should
suggest putting edges around the edge of the graph area and keeping the center free. In
contrast to this if the user was trying to create a key focal point the software should suggest
making a knot with a bold center which draws attention towards the center.

7.3.3 Other Knot Work

The software has been written using an object orientated language, one benefit of this
is the potential to incorporate different types of knot work into the same system. The
classes that provide the graph area, loading and saving of files are not dependant on the
classes that generate the Celtic knot work; which should allow generators for other forms
of knot work to be developed and easily incorporated into the software. Knot work and
knot theory covers several research areas (see Section 2.3.3) and it may be possible to use
parts of this project within these fields. Elements of Celtic knot work (such as the over
and under pattern) are present in other knot work areas, thus parts of the Celtic knot
generation process developed could be reused.

7.4 Overall Project Conclusions

Initially this project set out to produce a software package that could produce Celtic knot
work based on user parameters; which has been achieved with the software developed.
Using the information in the background research on the style of Celtic knot work and
the approaches to generating them, the method developed by Mercat was chosen. A soft-
ware package which implemented this method was then designed and implemented. The
undirected graph is central to the knot generation process and this is entered by the user,
CHAPTER 7. CONCLUSION 86

meeting part of the primary aim of the project. The knot work produced by the software
is easily identifiable as Celtic in style which satisfies the other part of the primary project
aim. After implementing the software it is clear that there are areas which the software
package could extend into. This project successfully meets its aims and provides a basis
for future work relating to the generation of Celtic knot work and knot work as a whole.
Bibliography

51055 Designs (n.d.), http://www.51055.com/portfolio/makeknots/makeknot.html.


Accessed on 20/04/2008.

Allen, J. R. (2001), Celtic Art in Pagan and Christian Times, Dover Publications.

Bain, I. (1990), Celtic Knotwork, Constable and Company.

Brown, R. (n.d.), ‘Mathematics and knots’.

Connor, S. (2004), Transported shiver of bodies: Weighing the victorian ether, in ‘British
Association of Victorian Studies (2004)’.

Laing, L. & Laing, J. (1992), Art Of The Celts, Thames and Hudson.

Megaw, R. & Megaw, V. (2001), Celtic Art, Thames and Hudson.

Mercat, C. (n.d.), http://www.entrelacs.net. Accessed on 20/04/2008.

Silver, D. (n.d.), ‘Scotish physics and knot theory odd origins’.

Sloss, A. (1995), How To Draw Celtic Knotwork: A Practical Handbook, Cassell.

Thinky Things (n.d.), www.thinkythings.org. Accessed on 02/03/2008.

Unknown (n.d.), http://pidancer.com/Projects/CelticKnot/. Accessed on


23/04/2008.

Wallace, M. (n.d.), http://www.wallace.net/knots. Accessed on 02/03/2008.

Wikipedia (n.d.), http://en.wikipedia.org/wiki/Vortex_ring. Accessed on


02/03/2008.

87
Appendix A

Sample Celtic Knots

This appendix contains examples of Celtic knot work that has been generated using the
software developed for this project. Each knot is displayed alongside the graph entered to
create the knot.

(a) Original Graph (b) Resulting Celtic Knot

Figure A.1: Square Knot

88
APPENDIX A. SAMPLE CELTIC KNOTS 89

(a) Original Graph (b) Resulting Celtic Knot

Figure A.2: Nested Squares

(a) Original Graph (b) Resulting Celtic Knot

Figure A.3: Basic Cross


APPENDIX A. SAMPLE CELTIC KNOTS 90

(a) Original Graph (b) Resulting Celtic Knot

Figure A.4: Rectangle Intersected By Line

(a) Original Graph (b) Resulting Celtic Knot

Figure A.5: Hexagon


Appendix B

Source Code

This appendix lists the source code created for this project. All class files in the project are
shown split by Java package. All code has been formatted using the Sun Java standard.

B.1 Package: knot.gui

B.1.1 File: ButtonPanelColours.java


package knot . g u i ;

import j a v a . awt . C o l o r ;
import j a v a . awt . Component ;
import j a v a . awt . Dimension ;
import j a v a . awt . Font ;
import j a v a . awt . G r i d B a g C o n s t r a i n t s ;
import j a v a . awt . GridBagLayout ;
import j a v a . awt . I n s e t s ;
import j a v a . awt . e v e n t . ActionEvent ;
import j a v a . awt . e v e n t . A c t i o n L i s t e n e r ;
import j a v a . awt . e v e n t . ItemEvent ;
import j a v a . awt . e v e n t . I t e m L i s t e n e r ;
import java . u t i l . Hashtable ;

import j a v a x . swing . JButton ;


import j a v a x . swing . JCheckBox ;
import j a v a x . swing . J C o l o r C h o o s e r ;
import j a v a x . swing . J L a b e l ;
import j a v a x . swing . JPanel ;
import j a v a x . swing . J S l i d e r ;
import j a v a x . swing . b o r d e r . T i t l e d B o r d e r ;
import j a v a x . swing . e v e n t . ChangeEvent ;
import j a v a x . swing . e v e n t . C h a n g e L i s t e n e r ;

p u b l i c c l a s s B u t t o n P a n e l C o l o u r s e x t e n d s JPanel implements A c t i o n L i s t e n e r ,
ChangeListener , ItemListener {
private s t a t i c f i n a l long serialVersionUID = 1;

91
APPENDIX B. SOURCE CODE 92

p r i v a t e s t a t i c JPanel me ;

private static JSlider innerThicknessSlider ;

private static JSlider outlineThicknessSlider ;

p r i v a t e s t a t i c JCheckBox makeTransCheck ;

p r i v a t e I n s e t s l a b e l I n s e t = new I n s e t s ( 5 , 5 , 5 , 5 ) ;

p r i v a t e I n s e t s m a i n I n s e t = new I n s e t s ( 5 , 5 , 5 , 5 ) ;

p r i v a t e Dimension d = new Dimension ( 3 0 , 3 0 ) ;

p r i v a t e Font l a b e l F o n t = new Font ( ” LabelFont ” , Font . PLAIN , 1 2 ) ;

p r i v a t e Font t i t l e F o n t = new Font ( ” T i t l e F o n t ” , Font .BOLD, 1 4 ) ;

p u b l i c ButtonPanelColours ( ) {
t h i s . s e t L a y o u t ( new GridBagLayout ( ) ) ;

G r i d B a g C o n s t r a i n t s c = new G r i d B a g C o n s t r a i n t s ( ) ;

c . i n s e t s = new I n s e t s ( 1 0 , 1 0 , 0 , 1 0 ) ;
c . gridx = 0;
c . gridy = 0;
t h i s . add ( c r e a t e O u t l i n e P a n e l ( ) , c ) ;

c . gridx = 0;
c . gridy = 1;
c . weighty = 5 ;
t h i s . add ( c r e a t e I n n e r P a n e l ( ) , c ) ;

c . gridx = 0;
c . gridy = 2;
c . weighty = 5 ;
t h i s . add ( c r e a t e B a c k g r o u n d P a n e l ( ) , c ) ;

c . gridx = 0;
c . gridy = 3;
c . weighty = 5 ;
t h i s . add ( c r e a t e S l i d e r P a n e l ( ) , c ) ;

me = t h i s ;
}

public s t a t i c void s e t S l i d e r V a l u e s ( i n t outlineThickness , i n t innerThickness ) {


innerThicknessSlider . setValue ( innerThickness ) ;
outlineThicknessSlider . setValue ( outlineThickness ) ;
}

p u b l i c v o i d a c t i o n P e r f o r m e d ( ActionEvent e ) {
i f ( ” c h a n g e O u t l i n e 1 ” . e q u a l s ( e . getActionCommand ( ) ) ) {
C o l o r newColour = J C o l o r C h o o s e r . showDialog ( t h i s ,
” S e l e c t Co lo u r For Knot O u t l i n e ” , GridGraphView
. getOutlinePaintOne ( ) ) ;
i f ( newColour != n u l l ) {
GridGraphView . s e t O u t l i n e P a i n t O n e ( newColour ) ;
changePreviewPanelColour ( ” outlineColourOneSample ” ,
APPENDIX B. SOURCE CODE 93

” o u t l i n e P a n e l ” , newColour ) ;
}
} e l s e i f ( ” c h a n g e O u t l i n e 2 ” . e q u a l s ( e . getActionCommand ( ) ) ) {
C o l o r newColour = J C o l o r C h o o s e r . showDialog ( t h i s ,
” S e l e c t Co lo u r For Knot O u t l i n e ” , GridGraphView
. getOutlinePaintTwo ( ) ) ;
i f ( newColour != n u l l ) {
GridGraphView . s e t O u t l i n e P a i n t T w o ( newColour ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” ou tline Col ourT woS am ple ” ,
” o u t l i n e P a n e l ” , newColour ) ;
}
} e l s e i f ( ” changeOutline2Same ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView
. s e t O u t l i n e P a i n t T w o ( GridGraphView . g e t O u t l i n e P a i n t O n e ( ) ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” outline Col ourT woS am ple ” , ” o u t l i n e P a n e l ” ,
GridGraphView . g e t O u t l i n e P a i n t O n e ( ) ) ;
} e l s e i f ( ” c h a n g e I n n e r 1 ” . e q u a l s ( e . getActionCommand ( ) ) ) {
C o l o r newColour = J C o l o r C h o o s e r . showDialog ( t h i s ,
” S e l e c t Co lo u r For I n t e r n a l Part Of Knot” , GridGraphView
. getInnerPaintOne ( ) ) ;
i f ( newColour != n u l l ) {
GridGraphView . s e t I n n e r P a i n t O n e ( newColour ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” innerColourOneSample ” , ” i n n e r P a n e l ” ,
newColour ) ;
}
} e l s e i f ( ” c h a n g e I n n e r 2 ” . e q u a l s ( e . getActionCommand ( ) ) ) {
C o l o r newColour = J C o l o r C h o o s e r . showDialog ( t h i s ,
” S e l e c t Co lo u r For I n t e r n a l Part Of Knot” , GridGraphView
. getInnerPaintTwo ( ) ) ;
i f ( newColour != n u l l ) {
GridGraphView . s e t I n n e r P a i n t T w o ( newColour ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” innerColourTwoSample ” , ” i n n e r P a n e l ” ,
newColour ) ;
}
} e l s e i f ( ” changeInner2Same ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView . s e t I n n e r P a i n t T w o ( GridGraphView . g e t I n n e r P a i n t O n e ( ) ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” innerColourTwoSample ” , ” i n n e r P a n e l ” ,
GridGraphView . g e t I n n e r P a i n t O n e ( ) ) ;
} e l s e i f ( ” changeBackground1 ” . e q u a l s ( e . getActionCommand ( ) ) ) {
C o l o r newColour = J C o l o r C h o o s e r . showDialog ( t h i s ,
” S e l e c t Co lo u r For Background ” , GridGraphView
. getBackgroundPaintOne ( ) ) ;
i f ( newColour != n u l l ) {
GridGraphView . setBackgroundPaintOne ( newColour ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourOneSample ” ,
” backgroundPanel ” , newColour ) ;
makeTransCheck . s e t S e l e c t e d ( f a l s e ) ;
}
} e l s e i f ( ” changeBackground2 ” . e q u a l s ( e . getActionCommand ( ) ) ) {
C o l o r newColour = J C o l o r C h o o s e r . showDialog ( t h i s ,
” S e l e c t Co lo u r For Background ” , GridGraphView
. getBackgroundPaintTwo ( ) ) ;
i f ( newColour != n u l l ) {
GridGraphView . setBackgroundPaintTwo ( newColour ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourTwoSample ” ,
” backgroundPanel ” , newColour ) ;
makeTransCheck . s e t S e l e c t e d ( f a l s e ) ;
}
} e l s e i f ( ” changeBackground2Same ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView . setBackgroundPaintTwo ( GridGraphView
. getBackgroundPaintOne ( ) ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourTwoSample ” ,
APPENDIX B. SOURCE CODE 94

” backgroundPanel ” , GridGraphView . getBackgroundPaintOne ( ) ) ;


makeTransCheck . s e t S e l e c t e d ( f a l s e ) ;
}

this . repaint () ;
i f ( GridGraphView . i s G e n e r a t i n g K n o t ( ) ) {
GridGraphView . r e p a i n t V i e w ( ) ;
}
}

public s t a t i c void repaintView ( ) {


me . r e p a i n t ( ) ;
}

p u b l i c s t a t i c v o i d c h a n g e P r e v i e w P a n e l C o l o u r ( S t r i n g panelName ,
S t r i n g outerPanelName , C o l o r newColour ) {
Component [ ] components = me . getComponents ( ) ;
f o r ( i n t i = 0 ; i < components . l e n g t h ; i ++) {
i f ( components [ i ] . getName ( ) . e q u a l s ( outerPanelName ) ) {
Component [ ] panelComponents = ( ( JPanel ) components [ i ] )
. getComponents ( ) ;
f o r ( i n t j = 0 ; j < panelComponents . l e n g t h ; j ++) {
i f ( panelComponents [ j ] . getName ( ) != n u l l
&& panelComponents [ j ] . getName ( ) . e q u a l s ( panelName ) ) {
panelComponents [ j ] . s e t B a c k g r o u n d ( newColour ) ;
break ;
}
}

break ;
}
}
}

p u b l i c v o i d itemStateChanged ( ItemEvent e ) {
Object source = e . g e t I t e m S e l e c t a b l e ( ) ;
i f ( s o u r c e . e q u a l s ( makeTransCheck ) ) {
i f ( makeTransCheck . i s S e l e c t e d ( ) ) {
C o l o r currentBackgroundOne = GridGraphView
. getBackgroundPaintOne ( ) ;
C o l o r currentBackgroundTwo = GridGraphView
. getBackgroundPaintTwo ( ) ;

C o l o r newBackgroundOne = new C o l o r ( currentBackgroundOne


. getRed ( ) , currentBackgroundOne . g e t Gr e e n ( ) ,
currentBackgroundOne . g e t B l u e ( ) , 0 ) ;
C o l o r newBackgroundTwo = new C o l o r ( currentBackgroundTwo
. getRed ( ) , currentBackgroundTwo . g e t Gr e e n ( ) ,
currentBackgroundTwo . g e t B l u e ( ) , 0 ) ;

GridGraphView . setBackgroundPaintOne ( newBackgroundOne ) ;


GridGraphView . setBackgroundPaintTwo ( newBackgroundTwo ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourOneSample ” ,
” backgroundPanel ” , newBackgroundOne ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourTwoSample ” ,
” backgroundPanel ” , newBackgroundTwo ) ;
} else {
C o l o r currentBackgroundOne = GridGraphView
. getBackgroundPaintOne ( ) ;
APPENDIX B. SOURCE CODE 95

C o l o r currentBackgroundTwo = GridGraphView
. getBackgroundPaintTwo ( ) ;

C o l o r newBackgroundOne = new C o l o r ( currentBackgroundOne


. getRed ( ) , currentBackgroundOne . g e t Gr e e n ( ) ,
currentBackgroundOne . g e t B l u e ( ) , 2 5 5 ) ;
C o l o r newBackgroundTwo = new C o l o r ( currentBackgroundTwo
. getRed ( ) , currentBackgroundTwo . g e t Gr e e n ( ) ,
currentBackgroundTwo . g e t B l u e ( ) , 2 5 5 ) ;

GridGraphView . setBackgroundPaintOne ( newBackgroundOne ) ;


GridGraphView . setBackgroundPaintTwo ( newBackgroundTwo ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourOneSample ” ,
” backgroundPanel ” , newBackgroundOne ) ;
c h a n g e P r e v i e w P a n e l C o l o u r ( ” backgroundColourTwoSample ” ,
” backgroundPanel ” , newBackgroundTwo ) ;
}
}
this . repaint () ;
i f ( GridGraphView . i s G e n e r a t i n g K n o t ( ) ) {
GridGraphView . r e p a i n t V i e w ( ) ;
}
}

p u b l i c v o i d s t a t e C h a n g e d ( ChangeEvent e ) {
i n t outlineThickness = o u t l i n e T h i c k n e s s S l i d e r . getValue ( ) ;
i n t innerThickness = i n n e r T h i c k n e s s S l i d e r . getValue ( ) ;

GridGraphView . s e t O u t l i n e T h i c k n e s s ( o u t l i n e T h i c k n e s s ) ;
i f ( i n n e r T h i c k n e s s >= o u t l i n e T h i c k n e s s ) {
innerThickness = outlineThickness − 2;
innerThicknessSlider . setValue ( innerThickness ) ;
}
i n n e r T h i c k n e s s S l i d e r . setMaximum ( o u t l i n e T h i c k n e s s − 2 ) ;
Hashtab le <I n t e g e r , JLabel> i n n e r L a b e l T a b l e = new H a s h t a b l e <I n t e g e r ,
JLabel >() ;
i n n e r L a b e l T a b l e . put ( new I n t e g e r ( 2 ) , new J L a b e l ( ” Thin ” ) ) ;
i n n e r L a b e l T a b l e . put ( new I n t e g e r ( o u t l i n e T h i c k n e s s − 2 ) , new J L a b e l (
” Thick ” ) ) ;
innerThicknessSlider . setLabelTable ( innerLabelTable ) ;
GridGraphView . s e t I n n e r T h i c k n e s s ( i n n e r T h i c k n e s s ) ;
this . repaint () ;
i f ( GridGraphView . i s G e n e r a t i n g K n o t ( ) ) {
GridGraphView . r e p a i n t V i e w ( ) ;
}
}

p r i v a t e JPanel c r e a t e S l i d e r P a n e l ( ) {
JPanel s l i d e r P a n e l = new JPanel ( ) ;
s l i d e r P a n e l . setName ( ” s l i d e r P a n e l ” ) ;
s l i d e r P a n e l . s e t L a y o u t ( new GridBagLayout ( ) ) ;
G r i d B a g C o n s t r a i n t s c = new G r i d B a g C o n s t r a i n t s ( ) ;
T i t l e d B o r d e r b o r d e r = new T i t l e d B o r d e r ( ”Knot T h i c k n e s s ” ) ;
border . setTitleFont ( t i t l e F o n t ) ;
s l i d e r P a n e l . setBorder ( border ) ;

J L a b e l o u t l i n e T h i c k n e s s L a b e l = new J L a b e l ( ” O u t l i n e T h i c k n e s s ” ) ;
outlineThicknessLabel . setFont ( labelFont ) ;
o u t l i n e T h i c k n e s s S l i d e r = new J S l i d e r ( J S l i d e r .HORIZONTAL, 4 , 1 8 , 1 2 ) ;
o u t l i n e T h i c k n e s s S l i d e r . setMinorTickSpacing (2) ;
APPENDIX B. SOURCE CODE 96

outlineThicknessSlider
. s e t T o o l T i p T e x t ( ” Adjust t h e t h i c k n e s s o f t h e knot o u t l i n e ” ) ;
o u t l i n e T h i c k n e s s S l i d e r . setSnapToTicks ( t r u e ) ;
o u t l i n e T h i c k n e s s S l i d e r . addChangeListener ( t h i s ) ;
Hashtab le <I n t e g e r , JLabel> o u t e r L a b e l T a b l e = new H a s h t a b l e <I n t e g e r ,
JLabel >() ;
o u t e r L a b e l T a b l e . put ( new I n t e g e r ( 4 ) , new J L a b e l ( ” Thin ” ) ) ;
o u t e r L a b e l T a b l e . put ( new I n t e g e r ( 1 8 ) , new J L a b e l ( ” Thick ” ) ) ;
o u t l i n e T h i c k n e s s S l i d e r . setLabelTable ( outerLabelTable ) ;
outlineThicknessSlider . setPaintLabels ( true ) ;

J L a b e l i n n e r T h i c k n e s s L a b e l = new J L a b e l ( ” I n t e r n a l T h i c k n e s s ” ) ;
innerThicknessLabel . setFont ( labelFont ) ;
i n n e r T h i c k n e s s S l i d e r = new J S l i d e r ( J S l i d e r .HORIZONTAL, 2 , 1 4 , 8 ) ;
i n n e r T h i c kn e s s S l i d e r . setMinorTickSpacing (2) ;
innerThicknessSlider
. s e t T o o l T i p T e x t ( ” Adjust t h e t h i c k n e s s o f t h e knot i n t e r i o r . W i l l
a u t o m a t i c a l l y a d j u s t t o be l e s s than t h e o u t l i n e t h i c k n e s s ” ) ;
i n n e r T h i c k n e s s S l i d e r . se t S n a p T o T i c k s ( t r u e ) ;
i n n e r T h i c k n e s s S l i d e r . addChangeListener ( t h i s ) ;
Hashtab le <I n t e g e r , JLabel> i n n e r L a b e l T a b l e = new H a s h t a b l e <I n t e g e r ,
JLabel >() ;
i n n e r L a b e l T a b l e . put ( new I n t e g e r ( 2 ) , new J L a b e l ( ” Thin ” ) ) ;
i n n e r L a b e l T a b l e . put ( new I n t e g e r ( 1 4 ) , new J L a b e l ( ” Thick ” ) ) ;
innerThicknessSlider . setLabelTable ( innerLabelTable ) ;
innerThicknessSlider . setPaintLabels ( true ) ;

c . gridx = 0;
c . gridy = 0;
c . i n s e t s = mainInset ;
s l i d e r P a n e l . add ( o u t l i n e T h i c k n e s s L a b e l , c ) ;

c . gridx = 1;
c . gridy = 0;
s l i d e r P a n e l . add ( o u t l i n e T h i c k n e s s S l i d e r , c ) ;

c . gridx = 0;
c . gridy = 1;
c . i n s e t s = mainInset ;
s l i d e r P a n e l . add ( i n n e r T h i c k n e s s L a b e l , c ) ;

c . gridx = 1;
c . gridy = 1;
s l i d e r P a n e l . add ( i n n e r T h i c k n e s s S l i d e r , c ) ;

return sliderPanel ;
}

p r i v a t e JPanel c r e a t e O u t l i n e P a n e l ( ) {
JPanel o u t l i n e P a n e l = new JPanel ( ) ;
o u t l i n e P a n e l . setName ( ” o u t l i n e P a n e l ” ) ;
o u t l i n e P a n e l . s e t L a y o u t ( new GridBagLayout ( ) ) ;
G r i d B a g C o n s t r a i n t s c = new G r i d B a g C o n s t r a i n t s ( ) ;
T i t l e d B o r d e r b o r d e r = new T i t l e d B o r d e r ( ”Knot O u t l i n e C o l o u r s ” ) ;
border . setTitleFont ( t i t l e F o n t ) ;
outlinePanel . setBorder ( border ) ;

J L a b e l o u t l i n e C o l o u r O n e L a b e l = new J L a b e l (
” Cu rren t Knot O u t l i n e C ol ou r 1 : ” ) ;
outlineColourOneLabel . setFont ( labelFont ) ;
APPENDIX B. SOURCE CODE 97

JPanel o u t l i n e C o l o u r O n e S a m p l e = new JPanel ( ) ;


o u t l i n e C o l o u r O n e S a m p l e . setName ( ” o u t l i n e C o l o u r O n e S a m p l e ” ) ;
outlineColourOneSample
. s e t B a c k g r o u n d ( GridGraphView . g e t O u t l i n e P a i n t O n e ( ) ) ;
o u t l i n e C o l o u r O n e S a m p l e . setMinimumSize ( d ) ;
outlineColourOneSample . s e t P r e f e r r e d S i z e (d) ;

JButton o u t l i n e C o l o u r O n e B u t t o n = new JButton ( ” Change ” ) ;


o u t l i n e C o l o u r O n e B u t t o n . setName ( ” c h a n g e O u t l i n e 1 ” ) ;
o u t l i n e C o l o u r O n e B u t t o n . setActionCommand ( ” c h a n g e O u t l i n e 1 ” ) ;
outlineColourOneButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o change t h e f i r s t c o l o u r o f
t h e knot o u t l i n e ” ) ;
outlineColourOneButton . addActionListener ( t h i s ) ;

c . gridx = 0;
c . gridy = 0;
c . gridwidth = 1;
c . insets = labelInset ;
o u t l i n e P a n e l . add ( o u t l i n e C o l o u r O n e L a b e l , c ) ;
c . gridx = 1;
c . gridy = 0;
c . gridwidth = 1;
c . i n s e t s = mainInset ;
o u t l i n e P a n e l . add ( o u t l i n e C o l o u r O n e S a m p l e , c ) ;
c . gridx = 0;
c . gridy = 1;
c . gridwidth = 1;
o u t l i n e P a n e l . add ( o u t l i n e C o l o u r O n e B u t t o n , c ) ;

J L a b e l o u t l i n e C o l o u r T w o L a b e l = new J L a b e l (
” Cu rren t Knot O u t l i n e C ol ou r 2 : ” ) ;
outlineColourTwoLabel . setFont ( labelFont ) ;

JPanel outlineColourTwoSam ple = new JPanel ( ) ;


outlineColourTwoSample . setName ( ” out lineC olo urT woS amp le ” ) ;
outlineColourTwoSample
. s e t B a c k g r o u n d ( GridGraphView . g e t O u t l i n e P a i n t T w o ( ) ) ;
outlineColourTwoSample . setMinimumSize ( d ) ;
outlineColourTwoSample . s e t P r e f e r r e d S i z e ( d ) ;

JButton o u t l i n e C o l o u r T w o B u t t o n = new JButton ( ” Change ” ) ;


o u t l i n e C o l o u r T w o B u t t o n . setName ( ” c h a n g e O u t l i n e 2 ” ) ;
o u t l i n e C o l o u r T w o B u t t o n . setActionCommand ( ” c h a n g e O u t l i n e 2 ” ) ;
outlineColourTwoButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o change t h e s e c o n d c o l o u r o f
t h e knot o u t l i n e ” ) ;
outlineColourTwoButton . addActionListener ( t h i s ) ;

JButton outlineColourTwoEqualOneButton = new JButton ( ”Same As C ol o ur 1 ” ) ;


outlineColourTwoEqualOneButton . setName ( ” changeOutline2Same ” ) ;
outlineColourTwoEqualOneButton . setActionCommand ( ” changeOutline2Same ” ) ;
outlineColourTwoEqualOneButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o make t h e s e c o n d o u t l i n e
c o l o u r t h e same a s t h e f i r s t ” ) ;
outlineColourTwoEqualOneButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

c . gridx = 0;
c . gridy = 2;
c . gridwidth = 1;
c . insets = labelInset ;
o u t l i n e P a n e l . add ( o u t l i n e C o l o u r T w o L a b e l , c ) ;
APPENDIX B. SOURCE CODE 98

c . gridx = 1;
c . gridy = 2;
c . gridwidth = 1;
c . i n s e t s = mainInset ;
o u t l i n e P a n e l . add ( outlineColourTwoSample , c ) ;
c . gridx = 0;
c . gridy = 3;
c . gridwidth = 1;
o u t l i n e P a n e l . add ( outlineColourTwoButton , c ) ;
c . gridx = 1;
c . gridy = 3;
c . gridwidth = 1;
o u t l i n e P a n e l . add ( outlineColourTwoEqualOneButton , c ) ;

return outlinePanel ;
}

p r i v a t e JPanel c r e a t e I n n e r P a n e l ( ) {
JPanel i n n e r P a n e l = new JPanel ( ) ;
i n n e r P a n e l . setName ( ” i n n e r P a n e l ” ) ;
i n n e r P a n e l . s e t L a y o u t ( new GridBagLayout ( ) ) ;
G r i d B a g C o n s t r a i n t s c = new G r i d B a g C o n s t r a i n t s ( ) ;
T i t l e d B o r d e r b o r d e r = new T i t l e d B o r d e r ( ”Knot I n t e r n a l C o l o u r s ” ) ;
border . setTitleFont ( t i t l e F o n t ) ;
innerPanel . setBorder ( border ) ;

J L a b e l i n n e r C o l o u r O n e L a b e l = new J L a b e l (
” Cu rren t Knot I n t e r n a l C o lo u r 1 : ” ) ;
innerColourOneLabel . setFont ( labelFont ) ;

JPanel innerColourOneSample = new JPanel ( ) ;


innerColourOneSample . setName ( ” innerColourOneSample ” ) ;
innerColourOneSample . s e t B a c k g r o u n d ( GridGraphView . g e t I n n e r P a i n t O n e ( ) ) ;
innerColourOneSample . setMinimumSize ( d ) ;
innerColourOneSample . s e t P r e f e r r e d S i z e ( d ) ;

JButton innerColourOneButt on = new JButton ( ” Change ” ) ;


innerColourOneButton . setName ( ” c h a n g e I n n e r 1 ” ) ;
innerColourOneButton . setActionCommand ( ” c h a n g e I n n e r 1 ” ) ;
innerColourOneButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o change t h e f i r s t c o l o u r o f
t h e knot i n t e r i o r ” ) ;
innerColourOneButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

c . gridx = 0;
c . gridy = 0;
c . gridwidth = 1;
c . insets = labelInset ;
i n n e r P a n e l . add ( i n n e r C o l o u r O n e L a b e l , c ) ;
c . gridx = 1;
c . gridy = 0;
c . gridwidth = 1;
c . i n s e t s = mainInset ;
i n n e r P a n e l . add ( innerColourOneSample , c ) ;
c . gridx = 0;
c . gridy = 1;
c . gridwidth = 1;
i n n e r P a n e l . add ( innerColourOneButton , c ) ;

J L a b e l in ne rCo lou rT woL a b e l = new J L a b e l (


” Cu rren t Knot I n t e r n a l C o lo u r 2 : ” ) ;
APPENDIX B. SOURCE CODE 99

in ner Co lou rT woLa bel . s e t F o n t ( l a b e l F o n t ) ;

JPanel innerColourTwoSample = new JPanel ( ) ;


innerColourTwoSample . setName ( ” innerColourTwoSample ” ) ;
innerColourTwoSample . s e t B a c k g r o u n d ( GridGraphView . g e t I n n e r P a i n t T w o ( ) ) ;
innerColourTwoSample . setMinimumSize ( d ) ;
innerColourTwoSample . s e t P r e f e r r e d S i z e ( d ) ;

JButton innerColourTwoButton = new JButton ( ” Change ” ) ;


innerColourTwoButton . setName ( ” c h a n g e I n n e r 2 ” ) ;
innerColourTwoButton . setActionCommand ( ” c h a n g e I n n e r 2 ” ) ;
innerColourTwoButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o change t h e s e c o n d c o l o u r o f
t h e knot i n t e r i o r ” ) ;
innerColourTwoButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

JButton innerColourTwoEqualOneButton = new JButton ( ”Same As C ol o ur 1 ” ) ;


innerColourTwoEqualOneButton . setName ( ” changeInner2Same ” ) ;
innerColourTwoEqualOneButton . setActionCommand ( ” changeInner2Same ” ) ;
innerColourTwoEqualOneButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o make t h e s e c o n d i n t e r i o r
c o l o u r t h e same a s t h e f i r s t ” ) ;
innerColourTwoEqualOneButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

c . gridx = 0;
c . gridy = 2;
c . gridwidth = 1;
c . insets = labelInset ;
i n n e r P a n e l . add ( innerColourTwoLabel , c ) ;
c . gridx = 1;
c . gridy = 2;
c . gridwidth = 1;
c . i n s e t s = mainInset ;
i n n e r P a n e l . add ( innerColourTwoSample , c ) ;
c . gridx = 0;
c . gridy = 3;
c . gridwidth = 1;
i n n e r P a n e l . add ( innerColourTwoButton , c ) ;
c . gridx = 1;
c . gridy = 3;
c . gridwidth = 1;
i n n e r P a n e l . add ( innerColourTwoEqualOneButton , c ) ;

return innerPanel ;
}

p r i v a t e JPanel c r e a t e B a c k g r o u n d P a n e l ( ) {
JPanel backgroundPanel = new JPanel ( ) ;
backgroundPanel . setName ( ” backgroundPanel ” ) ;
backgroundPanel . s e t L a y o u t ( new GridBagLayout ( ) ) ;
G r i d B a g C o n s t r a i n t s c = new G r i d B a g C o n s t r a i n t s ( ) ;
T i t l e d B o r d e r b o r d e r = new T i t l e d B o r d e r ( ” Background C o l o u r s ” ) ;
border . setTitleFont ( t i t l e F o n t ) ;
backgroundPanel . s e t B o r d e r ( b o r d e r ) ;

J L a b e l backgroundColourOneLabel = new J L a b e l (
” Cu rren t Background C ol ou r 1 : ” ) ;
backgroundColourOneLabel . s e t F o n t ( l a b e l F o n t ) ;

JPanel backgroundColourOneSample = new JPanel ( ) ;


backgroundColourOneSample . setName ( ” backgroundColourOneSample ” ) ;
APPENDIX B. SOURCE CODE 100

backgroundColourOneSample . s e t B a c k g r o u n d ( GridGraphView
. getBackgroundPaintOne ( ) ) ;
backgroundColourOneSample . setMinimumSize ( d ) ;
backgroundColourOneSample . s e t P r e f e r r e d S i z e ( d ) ;

JButton backgroundColourOneButton = new JButton ( ” Change ” ) ;


backgroundColourOneButton . setName ( ” changeBackground1 ” ) ;
backgroundColourOneButton . setActionCommand ( ” changeBackground1 ” ) ;
backgroundColourOneButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o change t h e f i r s t c o l o u r o f
t h e image background ” ) ;
backgroundColourOneButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

makeTransCheck = new JCheckBox ( ” T r a n s p a r e n t ” ) ;


makeTransCheck . s e t T o o l T i p T e x t ( ”Make t h e backgorund t r a n s p a r e n t ” ) ;
makeTransCheck . s e t S e l e c t e d ( f a l s e ) ;
makeTransCheck . a d d I t e m L i s t e n e r ( t h i s ) ;

c . gridx = 0;
c . gridy = 0;
c . gridwidth = 1;
c . insets = labelInset ;
backgroundPanel . add ( backgroundColourOneLabel , c ) ;
c . gridx = 1;
c . gridy = 0;
c . gridwidth = 1;
c . i n s e t s = mainInset ;
backgroundPanel . add ( backgroundColourOneSample , c ) ;
c . gridx = 0;
c . gridy = 1;
c . gridwidth = 1;
backgroundPanel . add ( backgroundColourOneButton , c ) ;
c . gridx = 1;
c . gridy = 1;
c . gridwidth = 1;
backgroundPanel . add ( makeTransCheck , c ) ;

J L a b e l backgroundColourTwoLabel = new J L a b e l (
” Cu rren t Background C ol ou r 2 : ” ) ;
backgroundColourTwoLabel . s e t F o n t ( l a b e l F o n t ) ;

JPanel backgroundColourTwoSample = new JPanel ( ) ;


backgroundColourTwoSample . setName ( ” backgroundColourTwoSample ” ) ;
backgroundColourTwoSample . s e t B a c k g r o u n d ( GridGraphView
. getBackgroundPaintTwo ( ) ) ;
backgroundColourTwoSample . setMinimumSize ( d ) ;
backgroundColourTwoSample . s e t P r e f e r r e d S i z e ( d ) ;

JButton backgroundColourTwoButton = new JButton ( ” Change ” ) ;


backgroundColourTwoButton . setName ( ” changeBackground2 ” ) ;
backgroundColourTwoButton . setActionCommand ( ” changeBackground2 ” ) ;
backgroundColourTwoButton
. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o change t h e s e c o n d c o l o u r o f
t h e image background ” ) ;
backgroundColourTwoButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

JButton backgroundColourTwoEqualOneButton = new JButton (


”Same As Colour 1 ” ) ;
backgroundColourTwoEqualOneButton . setName ( ” changeBackground2Same ” ) ;
backgroundColourTwoEqualOneButton
. setActionCommand ( ” changeBackground2Same ” ) ;
backgroundColourTwoEqualOneButton
APPENDIX B. SOURCE CODE 101

. s e t T o o l T i p T e x t ( ” C l i c k t h i s b u tt o n t o make t h e s e c o n d image
background c o l o u r t h e same a s t h e f i r s t ” ) ;
backgroundColourTwoEqualOneButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;

c . gridx = 0;
c . gridy = 2;
c . gridwidth = 1;
c . insets = labelInset ;
backgroundPanel . add ( backgroundColourTwoLabel , c ) ;
c . gridx = 1;
c . gridy = 2;
c . gridwidth = 1;
c . i n s e t s = mainInset ;
backgroundPanel . add ( backgroundColourTwoSample , c ) ;
c . gridx = 0;
c . gridy = 3;
c . gridwidth = 1;
backgroundPanel . add ( backgroundColourTwoButton , c ) ;
c . gridx = 1;
c . gridy = 3;
c . gridwidth = 1;
backgroundPanel . add ( backgroundColourTwoEqualOneButton , c ) ;

r e t u r n backgroundPanel ;
}

B.1.2 File: ButtonPanelGraphCreation.java


package knot . g u i ;

import j a v a . awt . BorderLayout ;


import j a v a . awt . Component ;
import j a v a . awt . Font ;
import j a v a . awt . e v e n t . ActionEvent ;
import j a v a . awt . e v e n t . A c t i o n L i s t e n e r ;
import j a v a . awt . e v e n t . KeyEvent ;
import java . io . F i l e ;

import j a v a x . swing . JButton ;


import j a v a x . swing . J F i l e C h o o s e r ;
import j a v a x . swing . JPanel ;
import j a v a x . swing . b o r d e r . T i t l e d B o r d e r ;

import knot . g e n e r a t i o n . G e n e r a t o r ;

p u b l i c c l a s s ButtonPanelGraphCreation e x t e n d s JPanel implements A c t i o n L i s t e n e r {


private s t a t i c f i n a l long serialVersionUID = 1;

p r i v a t e s t a t i c JPanel me ;

p r i v a t e Font t i t l e F o n t = new Font ( ” T i t l e F o n t ” , Font .BOLD, 1 4 ) ;

p r i v a t e s t a t i c J F i l e C h o o s e r f i l e C h o o s e r = new J F i l e C h o o s e r ( ) ;

p u b l i c ButtonPanelGraphCreation ( ) {
T i t l e d B o r d e r b o r d e r = new T i t l e d B o r d e r ( ”Graph C o n t r o l s ” ) ;
border . setTitleFont ( t i t l e F o n t ) ;
APPENDIX B. SOURCE CODE 102

t h i s . setBorder ( border ) ;

JButton g e n e r a t e K n o t P o i n t B u t t o n = new JButton ( ” G e n e r a t e Knot” ) ;


g e n e r a t e K n o t P o i n t B u t t o n . setName ( ” g e n e r a t e K n o t ” ) ;
g e n e r a t e K n o t P o i n t B u t t o n . setActionCommand ( ” g e n e r a t e K n o t ” ) ;
generateKnotPointButton . setEnabled ( f a l s e ) ;
g e n e r a t e K n o t P o i n t B u t t o n . setMnemonic ( KeyEvent . VK G) ;
generateKnotPointButton
. s e t T o o l T i p T e x t ( ” Ge n e r a t e t h e knot based on t h i s graph ” ) ;
generateKnotPointButton . addActionListener ( t h i s ) ;
t h i s . add ( g e n e r a t e K n o t P o i n t B u t t o n , BorderLayout . LINE START) ;

JButton d e l e t e V e r t e x B u t t o n = new JButton ( ” D e l e t e Ve rt e x ” ) ;


d e l e t e V e r t e x B u t t o n . setName ( ” d e l V e r t e x ” ) ;
d e l e t e V e r t e x B u t t o n . setActionCommand ( ” d e l V e r t e x ” ) ;
deleteVertexButton . setEnabled ( f a l s e ) ;
d e l e t e V e r t e x B u t t o n . setMnemonic ( KeyEvent . VK D) ;
deleteVertexButton
. s e t T o o l T i p T e x t ( ” D e l e t e t h e s e l e c t e d v e r t e x shown i n b l u e ” ) ;
deleteVertexButton . addActionListener ( t h i s ) ;
t h i s . add ( d e l e t e V e r t e x B u t t o n , BorderLayout .CENTER) ;

JButton c l e a r A l l B u t t o n = new JButton ( ” C l e a r A l l ” ) ;


c l e a r A l l B u t t o n . setName ( ” c l e a r A l l ” ) ;
c l e a r A l l B u t t o n . setActionCommand ( ” c l e a r A l l ” ) ;
clearAllButton . setEnabled ( f a l s e ) ;
c l e a r A l l B u t t o n . setMnemonic ( KeyEvent . VK C) ;
clearAllButton
. s e t T o o l T i p T e x t ( ” D e l e t e a l l e d g e s and v e r t i c e s i n t h e graph ” ) ;
clearAllButton . addActionListener ( t h i s ) ;
t h i s . add ( c l e a r A l l B u t t o n , BorderLayout .CENTER) ;

JButton d e l e t e E d g e B u t t o n = new JButton ( ” D e l e t e Edge ” ) ;


d e l e t e E d g e B u t t o n . setName ( ” delEdge ” ) ;
d e l e t e E d g e B u t t o n . setActionCommand ( ” delEdge ” ) ;
deleteEdgeButton . setEnabled ( f a l s e ) ;
d e l e t e E d g e B u t t o n . setMnemonic ( KeyEvent . VK D) ;
deleteEdgeButton
. s e t T o o l T i p T e x t ( ” D e l e t e t h e s e l e c t e d edge shown i n b l u e ” ) ;
deleteEdgeButton . addActionListener ( t h i s ) ;
t h i s . add ( d e l e t e E d g e B u t t o n , BorderLayout .CENTER) ;

JButton saveGraphButton = new JButton ( ” Save Graph” ) ;


saveGraphButton . setName ( ” saveGraph ” ) ;
saveGraphButton . setActionCommand ( ” saveGraph ” ) ;
saveGraphButton . s e t E n a b l e d ( f a l s e ) ;
saveGraphButton . setMnemonic ( KeyEvent . VK S ) ;
saveGraphButton . s e t T o o l T i p T e x t ( ” Save t h e c u r r e n t graph ” ) ;
saveGraphButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;
t h i s . add ( saveGraphButton , BorderLayout . LINE END) ;

JButton loadGraphButton = new JButton ( ” Load Graph” ) ;


loadGraphButton . setName ( ” loadGraph ” ) ;
loadGraphButton . setActionCommand ( ” loadGraph ” ) ;
loadGraphButton . setMnemonic ( KeyEvent . VK L) ;
loadGraphButton . s e t T o o l T i p T e x t ( ” Load a p r e v i o u s l y s a v e d graph ” ) ;
loadGraphButton . a d d A c t i o n L i s t e n e r ( t h i s ) ;
t h i s . add ( loadGraphButton , BorderLayout . LINE END) ;

f i l e C h o o s e r . a d d C h o o s a b l e F i l e F i l t e r ( new K n o t F i l e F i l t e r ( ) ) ;
fileChooser . setAcceptAllFileFilterUsed ( false ) ;
APPENDIX B. SOURCE CODE 103

me = t h i s ;
}

p u b l i c s t a t i c v o i d setEnabledForComponent ( S t r i n g componentName ,
boolean setEnabled ) {
Component [ ] components = me . getComponents ( ) ;
f o r ( i n t i = 0 ; i < components . l e n g t h ; i ++) {
Component temp = components [ i ] ;
i f ( temp . getName ( ) . e q u a l s ( componentName ) ) {
temp . s e t E n a b l e d ( s e t E n a b l e d ) ;
break ;
}
}
}

p u b l i c s t a t i c v o i d enableAndShowPanel ( b o o l e a n enableAndShow ) {
me . s e t E n a b l e d ( enableAndShow ) ;
me . s e t V i s i b l e ( enableAndShow ) ;
}

p u b l i c v o i d a c t i o n P e r f o r m e d ( ActionEvent e ) {
i f ( ” g e n e r a t e K n o t ” . e q u a l s ( e . getActionCommand ( ) ) ) {
Generator . generateKnotLines ( ) ;
GridGraphView . r e p a i n t V i e w ( ) ;
G e n e r a t o r . createLinksBetweenComponents ( ) ;
ButtonPanelKnotCreated . enableAndShowPanel ( t r u e ) ;
enableAndShowPanel ( f a l s e ) ;
} e l s e i f ( ” d e l V e r t e x ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView . d e l e t e V e r t e x ( ) ;
GridGraphView . r e p a i n t V i e w ( ) ;
setEnabledForComponent ( ” d e l V e r t e x ” , f a l s e ) ;
} e l s e i f ( ” c l e a r A l l ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView . c l e a r G r a p h ( ) ;
GridGraphView . r e p a i n t V i e w ( ) ;
setEnabledForComponent ( ” c l e a r A l l ” , f a l s e ) ;
setEnabledForComponent ( ” saveGraph ” , f a l s e ) ;
setEnabledForComponent ( ” g e n e r a t e K n o t ” , f a l s e ) ;
setEnabledForComponent ( ” delEdge ” , f a l s e ) ;
setEnabledForComponent ( ” d e l V e r t e x ” , f a l s e ) ;
} e l s e i f ( ” delEdge ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView . d e l e t e E d g e ( ) ;
GridGraphView . r e p a i n t V i e w ( ) ;
setEnabledForComponent ( ” delEdge ” , f a l s e ) ;
} e l s e i f ( ” saveGraph ” . e q u a l s ( e . getActionCommand ( ) ) ) {
i n t r e s u l t = f i l e C h o o s e r . showSaveDialog (me) ;
i f ( r e s u l t == J F i l e C h o o s e r .APPROVE OPTION) {
File saveFile = fileChooser . getSelectedFile () ;
String absFilePath = s a v e F i l e . getAbsolutePath ( ) ;
String f i l e E x t e n s i o n = KnotFileFilter . getExtension ( saveFile ) ;
i f ( f i l e E x t e n s i o n == n u l l ) {
f i l e E x t e n s i o n = ” knot ” ;
s a v e F i l e = new F i l e ( a b s F i l e P a t h + ” . knot ” ) ;
} e l s e i f ( ! ( f i l e E x t e n s i o n . e q u a l s ( ” knot ” ) ) ) {
S t r i n g newFileName = a b s F i l e P a t h . s u b s t r i n g ( 0 , a b s F i l e P a t h
. in de x O f ( f i l e E x t e n s i o n ) ) ;
s a v e F i l e = new F i l e ( newFileName + ” knot ” ) ;
f i l e E x t e n s i o n = ” knot ” ;
}
KnotFileReaderWriter . s a v e F i l e ( s a v e F i l e ) ;
APPENDIX B. SOURCE CODE 104

}
} e l s e i f ( ” loadGraph ” . e q u a l s ( e . getActionCommand ( ) ) ) {
i n t r e s u l t = f i l e C h o o s e r . showOpenDialog (me) ;
i f ( r e s u l t == J F i l e C h o o s e r .APPROVE OPTION) {
File saveFile = fileChooser . getSelectedFile () ;
String f i l e E x t e n s i o n = KnotFileFilter . getExtension ( saveFile ) ;
i f ( f i l e E x t e n s i o n != n u l l && f i l e E x t e n s i o n . e q u a l s ( ” knot ” ) ) {
KnotFileReaderWriter . l o a d F i l e ( s a v e F i l e ) ;
}

}
}
}
}

B.1.3 File: ButtonPanelKnotCreated.java


package knot . g u i ;

import j a v a . awt . BorderLayout ;


import j a v a . awt . Font ;
import j a v a . awt . e v e n t . ActionEvent ;
import j a v a . awt . e v e n t . A c t i o n L i s t e n e r ;
import j a v a . awt . e v e n t . KeyEvent ;
import java . io . F i l e ;

import j a v a x . swing . JButton ;


import j a v a x . swing . J F i l e C h o o s e r ;
import j a v a x . swing . JPanel ;
import j a v a x . swing . b o r d e r . T i t l e d B o r d e r ;

p u b l i c c l a s s ButtonPanelKnotCreated e x t e n d s JPanel implements A c t i o n L i s t e n e r {


private s t a t i c f i n a l long serialVersionUID = 1;
p r i v a t e s t a t i c JPanel me ;

p r i v a t e Font t i t l e F o n t = new Font ( ” T i t l e F o n t ” , Font .BOLD, 1 4 ) ;

p r i v a t e s t a t i c J F i l e C h o o s e r f i l e C h o o s e r = new J F i l e C h o o s e r ( ) ;

p u b l i c ButtonPanelKnotCreated ( ) {
T i t l e d B o r d e r b o r d e r = new T i t l e d B o r d e r ( ” P i c t u r e C o n t r o l s ” ) ;
border . setTitleFont ( t i t l e F o n t ) ;
t h i s . setBorder ( border ) ;

JButton r e t u r n T o G r a p h C r e a t i o n = new JButton ( ” Back To Graph” ) ;


r e t u r n T o G r a p h C r e a t i o n . setName ( ” graph ” ) ;
r e t u r n T o G r a p h C r e a t i o n . setActionCommand ( ” graph ” ) ;
r e t u r n T o G r a p h C r e a t i o n . setMnemonic ( KeyEvent . VK B) ;
returnToGraphCreation
. s e t T o o l T i p T e x t ( ” Return t o t h e graph c r e a t i o n s t a g e ” ) ;
returnToGraphCreation . addActionListener ( t h i s ) ;
t h i s . add ( returnToGraphCreation , BorderLayout . LINE START) ;

JButton s a v e K n o t A s P i c t u r e = new JButton ( ” Save P i c t u r e ” ) ;


s a v e K n o t A s P i c t u r e . setName ( ” s a v e P i c t u r e ” ) ;
s a v e K n o t A s P i c t u r e . setActionCommand ( ” s a v e P i c t u r e ” ) ;
s a v e K n o t A s P i c t u r e . setMnemonic ( KeyEvent . VK S ) ;
s a v e K n o t A s P i c t u r e . s e t T o o l T i p T e x t ( ” Save t h e knot a s a p i c t u r e f i l e ”) ;
saveKnotAsPicture . addActionListener ( t h i s ) ;
APPENDIX B. SOURCE CODE 105

t h i s . add ( sa ve Kno tAsPic t u r e , BorderLayout . LINE END) ;

t h i s . setEnabled ( f a l s e ) ;
this . setVisible ( false ) ;

f i l e C h o o s e r . a d d C h o o s a b l e F i l e F i l t e r ( new P i c t u r e F i l e F i l t e r ( ) ) ;
fileChooser . setAcceptAllFileFilterUsed ( false ) ;

me = t h i s ;
}

p u b l i c v o i d a c t i o n P e r f o r m e d ( ActionEvent e ) {
i f ( ” graph ” . e q u a l s ( e . getActionCommand ( ) ) ) {
GridGraphView . s e t G e n e r a t i n g K n o t ( f a l s e ) ;
ButtonPanelGraphCreation . enableAndShowPanel ( t r u e ) ;
enableAndShowPanel ( f a l s e ) ;
GridGraphView . r e p a i n t V i e w ( ) ;
} e l s e i f ( ” s a v e P i c t u r e ” . e q u a l s ( e . getActionCommand ( ) ) ) {
i n t r e s u l t = f i l e C h o o s e r . showSaveDialog (me) ;
i f ( r e s u l t == J F i l e C h o o s e r .APPROVE OPTION) {
File saveFile = fileChooser . getSelectedFile () ;
String absFilePath = s a v e F i l e . getAbsolutePath ( ) ;
String f i l e E x t e n s i o n = P i c t u r e F i l e F i l t e r . getExtension ( saveFile ) ;
i f ( f i l e E x t e n s i o n == n u l l ) {
f i l e E x t e n s i o n = ” png ” ;
s a v e F i l e = new F i l e ( a b s F i l e P a t h + ” . png ” ) ;
} e l s e i f ( ! ( f i l e E x t e n s i o n . equals ( ” jpg ” ) | | f i l e E x t e n s i o n
. e q u a l s ( ” png ” ) ) ) {
S t r i n g newFileName = a b s F i l e P a t h . s u b s t r i n g ( 0 , a b s F i l e P a t h
. in d e xO f ( f i l e E x t e n s i o n ) ) ;
s a v e F i l e = new F i l e ( newFileName + ” png ” ) ;
f i l e E x t e n s i o n = ” png ” ;
}
GridGraphView . s a v e P i c t u r e ( s a v e F i l e , f i l e E x t e n s i o n ) ;
}
}
}

p u b l i c s t a t i c v o i d enableAndShowPanel ( b o o l e a n enableAndShow ) {
me . s e t E n a b l e d ( enableAndShow ) ;
me . s e t V i s i b l e ( enableAndShow ) ;
}
}

B.1.4 File: GridGraphView.java


package knot . g u i ;

import j a v a . awt . B a s i c S t r o k e ;
import j a v a . awt . C o l o r ;
import j a v a . awt . G r a d i e n t P a i n t ;
import j a v a . awt . G r a p h i c s ;
import j a v a . awt . Graphics2D ;
import j a v a . awt . P o i n t ;
import j a v a . awt . S t r o k e ;
import j a v a . awt . e v e n t . MouseAdapter ;
import j a v a . awt . e v e n t . MouseEvent ;
import j a v a . awt . geom . Line2D ;
import j a v a . awt . image . B u f f e r e d I m a g e ;
APPENDIX B. SOURCE CODE 106

impo rt j a v a . i o . F i l e ;
import j a v a . i o . IOException ;
import j a v a . u t i l . A r r a y L i s t ;

import j a v a x . i m a g e i o . ImageIO ;
import j a v a x . swing . JPanel ;

import knot . g e n e r a t i o n . G e n e r a t o r ;

p u b l i c c l a s s GridGraphView e x t e n d s JPanel {
private s t a t i c f i n a l long serialVersionUID = 1;

p r i v a t e s t a t i c JPanel me ;

private static f i n a l i n t MAGIC NUMBER = 6 0 ;

private static f i n a l i n t VERTEX SIZE = 1 0 ;

p r i v a t e s t a t i c Point s t a r t P o i n t ;

p r i v a t e s t a t i c P o i n t endPoi n t ;

p r i v a t e s t a t i c Point s e l e c t e d ;

p r i v a t e s t a t i c Line2D . Double s e l e c t e d E d g e ;

p r i v a t e s t a t i c A r r a y L i s t <Point> v e r t i c i e s = new A r r a y L i s t <Point >() ;

p r i v a t e s t a t i c A r r a y L i s t <Point> e d g e s S t a r t = new A r r a y L i s t <Point >() ;

p r i v a t e s t a t i c A r r a y L i s t <Point> edgesEnd = new A r r a y L i s t <Point >() ;

p r i v a t e s t a t i c A r r a y L i s t <Point> e d g e s M i d p o i n t = new A r r a y L i s t <Point >() ;

private s t a t i c boolean generatingKnot = f a l s e ;

p r i v a t e s t a t i c C o l o r o u t l i n e P a i n t O n e = new C o l o r ( 2 5 5 , 1 3 3 , 0 ) ;

p r i v a t e s t a t i c C o l o r o u t l i n e P a i n t T w o = new C o l o r ( 2 5 5 , 1 3 3 , 0 ) ;

p r i v a t e s t a t i c C o l o r i n n e r P a i n t O n e = new C o l o r ( 2 5 5 , 2 5 5 , 2 0 4 ) ;

p r i v a t e s t a t i c C o l o r innerPaintTwo = new C o l o r ( 2 5 5 , 2 5 5 , 2 0 4 ) ;

p r i v a t e s t a t i c C o l o r backgroundPaintOne = C o l o r .WHITE;

p r i v a t e s t a t i c C o l o r backgroundPaintTwo = C o l o r .WHITE;

private s t a t i c int outlineThickness = 18;

private s t a t i c int innerThickness = 10;

p u b l i c GridGraphView ( ) {
APPENDIX B. SOURCE CODE 107

me = t h i s ;
this
. s e t T o o l T i p T e x t ( ” Double c l i c k an i t e r s e c t i o n t o c r e a t e a v e r t e x .
C l i c k and drag from one v e r t e x t o a n o t h e r t o c r e a t e and edge ” ) ;

t h i s . a d d M o u s e L i s t e n e r ( new MouseAdapter ( ) {

p u b l i c v o i d mou seC l ic k e d ( MouseEvent me) {


P o i n t p = getPointOnGrid (me) ;

i f (me . g e t C l i c k C o u n t ( ) == 2 && i s P o i n t W i t h i n B o u n d a r y ( p ) == t r u e
&& d o e s V e r t e x E x i s t ( p ) == f a l s e ) {
v e r t i c i e s . add ( p ) ;
repaint () ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” c l e a r A l l ” ,
true ) ;
ButtonPanelGraphCreation . setEnabledForComponent (
” saveGraph ” , t r u e ) ;
}
}

p u b l i c v o i d mousePressed ( MouseEvent me) {


P o i n t p = getPointOnGrid (me) ;
i f ( doesVertexExist (p) ) {
startPoint = p ;
selected = p;
ButtonPanelGraphCreation . setEnabledForComponent (
” delVertex ” , true ) ;
selectedEdge = null ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” delEdge ” ,
false ) ;
} e l s e i f ( doesPointLieOnEdge (me . g e t P o i n t ( ) ) != n u l l ) {
s e l e c t e d E d g e = doesPointLieOnEdge (me . g e t P o i n t ( ) ) ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” delEdge ” ,
true ) ;
selected = null ;
ButtonPanelGraphCreation . setEnabledForComponent (
” delVertex ” , f a l s e ) ;
} else {
selected = null ;
ButtonPanelGraphCreation . setEnabledForComponent (
” delVertex ” , f a l s e ) ;
selectedEdge = null ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” delEdge ” ,
false ) ;
}
}

p u b l i c v o i d mouseReleased ( MouseEvent me) {


P o i n t p = getPointOnGrid (me) ;
i f ( s t a r t P o i n t != n u l l && ! s t a r t P o i n t . e q u a l s ( p )
&& d o e s V e r t e x E x i s t ( p )
&& ( d o e s E d g e E x i s t ( s t a r t P o i n t , p ) == f a l s e ) ) {
endPoint = p ;
i f ( doesNewEdgeCrossExistingEdge ( s t a r t P o i n t , e n dPo i nt ) ==
false ) {
ButtonPanelGraphCreation . setEnabledForComponent (
” generateKnot ” , true ) ;
breakNewEdgeIfRequired ( s t a r t P o i n t , e n dPo i nt ) ;
}
APPENDIX B. SOURCE CODE 108

startPoint = null ;
endPoint = n u l l ;
repaint () ;
}
}) ;

t h i s . addMouseMotionListener ( new MouseAdapter ( ) {


p u b l i c v o i d mouseDragged ( MouseEvent me) {
i f ( s t a r t P o i n t != n u l l ) {
endPoint = me . g e t P o i n t ( ) ;
repaint () ;
}
}
}) ;
}

p r i v a t e boolean isPointWithinBoundary ( Point point ) {


b o o l e a n withinBoundary = f a l s e ;
i n t c u r r e n t H e i g h t = ( new Double ( t h i s . g e t S i z e ( ) . g e t H e i g h t ( ) ) ) . i n t V a l u e ( ) ;
i n t c ur ren t Wid th = ( new Double ( t h i s . g e t S i z e ( ) . getWidth ( ) ) ) . i n t V a l u e ( ) ;

i f ( p o i n t . x >= ( 0 + MAGIC NUMBER)


&& p o i n t . x <= ( c u r r e n t W i d t h − MAGIC NUMBER)
&& p o i n t . y >= ( 0 + MAGIC NUMBER)
&& p o i n t . y <= ( c u r r e n t H e i g h t − MAGIC NUMBER) ) {
withinBoundary = t r u e ;
}
r e t u r n withinBoundary ;
}

p r i v a t e v o i d p a i n t G r i d ( Graphics2D g2 ) {
i n t c u r r e n t H e i g h t = ( new Double ( t h i s . g e t S i z e ( ) . g e t H e i g h t ( ) ) ) . i n t V a l u e ( ) ;
i n t c ur ren t Wid th = ( new Double ( t h i s . g e t S i z e ( ) . getWidth ( ) ) ) . i n t V a l u e ( ) ;

g2 . s e t C o l o r ( C o l o r .WHITE) ;
g2 . f i l l R e c t ( 0 , 0 , currentWidth , c u r r e n t H e i g h t ) ;

g2 . s e t C o l o r ( C o l o r .BLACK) ;

f o r ( i n t i = MAGIC NUMBER; i < c u r r e n t W i d t h ; i = i + MAGIC NUMBER) {


g2 . drawLine ( i , 0 , i , c u r r e n t H e i g h t ) ;
}
f o r ( i n t j = MAGIC NUMBER; j < c u r r e n t H e i g h t ; j = j + MAGIC NUMBER) {
g2 . drawLine ( 0 , j , currentWidth , j ) ;
}
}

p r i v a t e v o i d p a i n t V e r t i c i e s ( Graphics2D g2 ) {
i f ( v e r t i c i e s . s i z e ( ) > 0) {
f o r ( i n t i = 0 ; i < v e r t i c i e s . s i z e ( ) ; i ++) {
Point p = v e r t i c i e s . get ( i ) ;
i f ( s e l e c t e d != n u l l && p . e q u a l s ( s e l e c t e d ) ) {
g2 . s e t C o l o r ( C o l o r .BLUE) ;
g2 . f i l l O v a l ( ( s e l e c t e d . x − (VERTEX SIZE / 2 ) ) ,
( s e l e c t e d . y − (VERTEX SIZE / 2 ) ) , VERTEX SIZE ,
VERTEX SIZE) ;
APPENDIX B. SOURCE CODE 109

g2 . s e t C o l o r ( C o l o r .BLACK) ;
} else {
g2
. f i l l O v a l ( ( p . x − (VERTEX SIZE / 2 ) ) ,
( p . y − (VERTEX SIZE / 2 ) ) , VERTEX SIZE ,
VERTEX SIZE) ;
}

}
}
}

p r i v a t e v o i d p a i n t E d g e s ( Graphics2D g2 ) {
i f ( edgesStart . s i z e ( ) > 0) {
B a s i c S t r o k e t h i c k L i n e = new B a s i c S t r o k e ( 3 ) ;

S t r o k e o l d S t r o k e = g2 . g e t S t r o k e ( ) ;
g2 . s e t S t r o k e ( t h i c k L i n e ) ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Point s = e d g e s S t a r t . get ( i ) ;
P o i n t e = edgesEnd . g e t ( i ) ;
g2 . drawLine ( s . x , s . y , e . x , e . y ) ;
}
i f ( s e l e c t e d E d g e != n u l l ) {
g2 . s e t C o l o r ( C o l o r .BLUE) ;
g2 . draw ( s e l e c t e d E d g e ) ;
g2 . s e t C o l o r ( C o l o r .BLACK) ;
}

g2 . s e t S t r o k e ( o l d S t r o k e ) ;
}
}

public s t a t i c void savePicture ( F i l e f i l e , String f i l e E x t e n s i o n ) {


try {
B u f f e r e d I m a g e b i = new B u f f e r e d I m a g e ( 6 0 0 , 6 0 0 ,
B u f f e r e d I m a g e . TYPE INT ARGB) ;
Graphics2D g3 = b i . c r e a t e G r a p h i c s ( ) ;
G r a d i e n t P a i n t o u t l i n e P a i n t = new G r a d i e n t P a i n t ( 0 , 0 ,
outlinePaintOne , 600 , 600 , outlinePaintTwo , tru e ) ;
G r a d i e n t P a i n t i n n e r P a i n t = new G r a d i e n t P a i n t ( 0 , 0 , in nerPaintO ne ,
6 0 0 , 6 0 0 , innerPaintTwo , t r u e ) ;
G r a d i e n t P a i n t b ac k g r o u n d P a i n t = new G r a d i e n t P a i n t ( 0 , 0 ,
backgroundPaintOne , 6 0 0 , 6 0 0 , backgroundPaintTwo , t r u e ) ;
G e n e r a t o r . paintKnot ( g3 , o u t l i n e P a i n t , i n n e r P a i n t , backgroundPaint ,
outlineThickness , innerThickness ) ;
ImageIO . w r i t e ( bi , f i l e E x t e n s i o n , f i l e ) ;
} c a t c h ( IOException e ) {
}

p u b l i c v o i d paintComponent ( G r a p h i c s g ) {
s u p e r . paintComponent ( g ) ;

Graphics2D g2 = ( Graphics2D ) g ;

i f ( generatingKnot ) {
APPENDIX B. SOURCE CODE 110

B a s i c S t r o k e t h i c k L i n e = new B a s i c S t r o k e ( 2 0 ) ;

S t r o k e o l d S t r o k e = g2 . g e t S t r o k e ( ) ;
g2 . s e t S t r o k e ( t h i c k L i n e ) ;

G r a d i e n t P a i n t o u t l i n e P a i n t = new G r a d i e n t P a i n t ( 0 , 0 ,
outlinePaintOne , 600 , 600 , outlinePaintTwo , tru e ) ;
G r a d i e n t P a i n t i n n e r P a i n t = new G r a d i e n t P a i n t ( 0 , 0 , in nerPaintO ne ,
6 0 0 , 6 0 0 , innerPaintTwo , t r u e ) ;
G r a d i e n t P a i n t b ac k g r o u n d P a i n t = new G r a d i e n t P a i n t ( 0 , 0 ,
backgroundPaintOne , 6 0 0 , 6 0 0 , backgroundPaintTwo , t r u e ) ;
G e n e r a t o r . paintKnot ( g2 , o u t l i n e P a i n t , i n n e r P a i n t , backgroundPaint ,
outlineThickness , innerThickness ) ;

g2 . s e t S t r o k e ( o l d S t r o k e ) ;
} else {
p a i n t G r i d ( g2 ) ;

p a i n t V e r t i c i e s ( g2 ) ;

p a i n t E d g e s ( g2 ) ;
}

i f ( endPoint != n u l l && s t a r t P o i n t != n u l l ) {
g2 . drawLine ( s t a r t P o i n t . x , s t a r t P o i n t . y , e n d Poi n t . x , en d Po in t . y ) ;
}
}

p r i v a t e P o i n t getPointOnGrid ( MouseEvent me) {


P o i n t e v e n t P o i n t = me . g e t P o i n t ( ) ;
int x = eventPoint . x ;
int y = eventPoint . y ;

f l o a t g r i d P o s i t i o n X = ( new I n t e g e r ( x ) ) . f l o a t V a l u e ( )
/ ( new I n t e g e r (MAGIC NUMBER) ) . f l o a t V a l u e ( ) ;
i n t g r i d P o s i t i o n X i n t = x / MAGIC NUMBER;
i f ( Math . abs ( g r i d P o s i t i o n X − g r i d P o s i t i o n X i n t ) > 0 . 5 ) {
g r i d P o s i t i o n X i n t ++;
}

f l o a t g r i d P o s i t i o n Y = ( new I n t e g e r ( y ) ) . f l o a t V a l u e ( )
/ ( new I n t e g e r (MAGIC NUMBER) ) . f l o a t V a l u e ( ) ;
i n t g r i d P o s i t i o n Y i n t = y / MAGIC NUMBER;
i f ( Math . abs ( g r i d P o s i t i o n Y − g r i d P o s i t i o n Y i n t ) > 0 . 5 ) {
g r i d P o s i t i o n Y i n t ++;
}

P o i n t p = new P o i n t ( ( g r i d P o s i t i o n X i n t ∗ MAGIC NUMBER) ,


( g r i d P o s i t i o n Y i n t ∗ MAGIC NUMBER) ) ;

return p ;
}

p u b l i c s t a t i c Point g e t S t a r t P o i n t ( ) {
return startPoint ;
}

p u b l i c s t a t i c void s e t S t a r t P o i n t ( Point s t a r t P o i n t ) {
GridGraphView . s t a r t P o i n t = s t a r t P o i n t ;
}
APPENDIX B. SOURCE CODE 111

p u b l i c s t a t i c P o i n t getEndPoint ( ) {
r e t u r n endPoint ;
}

p u b l i c s t a t i c v o i d s e t E n d P o i n t ( P o i n t e nd Poi nt ) {
GridGraphView . endPoint = en dPoin t ;
}

p u b l i c s t a t i c Point g e t S e l e c t e d ( ) {
return selected ;
}

p u b l i c s t a t i c void s e t S e l e c t e d ( Point s e l e c t e d ) {
GridGraphView . s e l e c t e d = s e l e c t e d ;
}

p u b l i c s t a t i c A r r a y L i s t <Point> g e t V e r t i c i e s ( ) {
return v e r t i c i e s ;
}

p u b l i c s t a t i c v o i d s e t V e r t i c i e s ( A r r a y L i s t <Point> v e r t i c i e s ) {
GridGraphView . v e r t i c i e s = v e r t i c i e s ;
}

p u b l i c s t a t i c A r r a y L i s t <Point> g e t E d g e s S t a r t ( ) {
return edgesStart ;
}

p u b l i c s t a t i c v o i d s e t E d g e s S t a r t ( A r r a y L i s t <Point> e d g e s S t a r t ) {
GridGraphView . e d g e s S t a r t = e d g e s S t a r t ;
}

p u b l i c s t a t i c A r r a y L i s t <Point> getEdgesEnd ( ) {
r e t u r n edgesEnd ;
}

p u b l i c s t a t i c v o i d setEdgesEnd ( A r r a y L i s t <Point> edgesEnd ) {


GridGraphView . edgesEnd = edgesEnd ;
}

p u b l i c s t a t i c A r r a y L i s t <Point> g e t E d g e s M i d p o i n t ( ) {
return edgesMidpoint ;
}

p u b l i c s t a t i c v o i d s e t E d g e s M i d p o i n t ( A r r a y L i s t <Point> e d g e s M i d p o i n t ) {
GridGraphView . e d g e s M i d p o i n t = e d g e s M i d p o i n t ;
}

p r i v a t e Line2D . Double doesPointLieOnEdge ( P o i n t p o i n t ) {


f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Line2D . Double temp = new Line2D . Double ( e d g e s S t a r t . g e t ( i ) , edgesEnd
. get ( i ) ) ;
i f ( temp . p t S e g D i s t ( p o i n t ) < 5 ) {
r e t u r n temp ;
}
}

return null ;
}
APPENDIX B. SOURCE CODE 112

p r i v a t e boolean doesEdgeExist ( Point s , Point e ) {


boolean e x i s t s = f a l s e ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
i f ( s . e q u a l s ( e d g e s S t a r t . g e t ( i ) ) && e . e q u a l s ( edgesEnd . g e t ( i ) ) ) {
e x i s t s = true ;
} e l s e i f ( e . e q u a l s ( e d g e s S t a r t . g e t ( i ) ) && s . e q u a l s ( edgesEnd . g e t ( i ) ) ) {
e x i s t s = true ;
}
}

return e x i s t s ;
}

p r i v a t e boolean doesVertexExist ( Point p) {


boolean e x i s t s = f a l s e ;

f o r ( i n t i = 0 ; i < v e r t i c i e s . s i z e ( ) ; i ++) {
i f (p . equals ( v e r t i c i e s . get ( i ) ) ) {
e x i s t s = true ;
break ;
}
}

return e x i s t s ;
}

public s t a t i c void repaintView ( ) {


me . r e p a i n t ( ) ;
}

p r i v a t e b o o l e a n doesNewEdgeCrossExistingEdge ( P o i n t s t a r t , P o i n t end ) {
boolean i n t e r s e c t s = f a l s e ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Point tempStart = e d g e s S t a r t . get ( i ) ;
P o i n t tempEnd = edgesEnd . g e t ( i ) ;
i f ( ! t e m p S t a r t . e q u a l s ( s t a r t ) && ! t e m p S t a r t . e q u a l s ( end )
&& ! tempEnd . e q u a l s ( s t a r t ) && ! tempEnd . e q u a l s ( end ) ) {
i f ( Line2D . l i n e s I n t e r s e c t ( s t a r t . x , s t a r t . y , end . x , end . y ,
t e m p S t a r t . x , t e m p S t a r t . y , tempEnd . x , tempEnd . y )
&& ( Line2D . p t L i n e D i s t ( s t a r t . x , s t a r t . y , end . x , end . y ,
t e m p S t a r t . x , t e m p S t a r t . y ) != 0 . 0 )
&& ( Line2D . p t L i n e D i s t ( s t a r t . x , s t a r t . y , end . x , end . y ,
tempEnd . x , tempEnd . y ) != 0 . 0 ) ) {
i n t e r s e c t s = true ;
}
}
}

return i n t e r s e c t s ;
}

p r i v a t e P o i n t doesNewEdgeCrossVertex ( P o i n t s t a r t , P o i n t end ) {
Point i n t e r s e c t s = n u l l ;
Line2D tempLine = new Line2D . Double ( s t a r t , end ) ;
f o r ( i n t i = 0 ; i < v e r t i c i e s . s i z e ( ) ; i ++) {
Point p = v e r t i c i e s . get ( i ) ;
APPENDIX B. SOURCE CODE 113

if ( ! p . e q u a l s ( end ) && ! p . e q u a l s ( s t a r t )
&& tempLine . p t S e g D i s t ( p ) == 0 . 0 ) {
intersects = p;
break ;
}
}

return i n t e r s e c t s ;
}

p r i v a t e v o i d breakNewEdgeIfRequired ( P o i n t s t a r t , P o i n t end ) {
P o i n t i n t e r s e c t = doesNewEdgeCrossVertex ( s t a r t , end ) ;
i f ( i n t e r s e c t != n u l l ) {
breakNewEdgeIfRequired ( s t a r t , i n t e r s e c t ) ;
breakNewEdgeIfRequired ( i n t e r s e c t , end ) ;
} e l s e i f ( d o e s E d g e E x i s t ( s t a r t , end ) == f a l s e ) {
e d g e s S t a r t . add ( s t a r t ) ;
edgesEnd . add ( end ) ;
}
}

public s t a t i c void deleteVertex ( ) {


i f ( s e l e c t e d == n u l l ) {
return ;
}

v e r t i c i e s . remove ( s e l e c t e d ) ;
i f ( edgesStart . s i z e ( ) > 0) {
while ( edgesStart . contains ( select ed ) ) {
i n t p o s i t i o n = e d g e s S t a r t . indexOf ( s e l e c t e d ) ;
e d g e s S t a r t . remove ( p o s i t i o n ) ;
edgesEnd . remove ( p o s i t i o n ) ;
}
w h i l e ( edgesEnd . c o n t a i n s ( s e l e c t e d ) ) {
i n t p o s i t i o n = edgesEnd . i n d e xO f ( s e l e c t e d ) ;
e d g e s S t a r t . remove ( p o s i t i o n ) ;
edgesEnd . remove ( p o s i t i o n ) ;
}
}

if ( ! ( edgesStart . s i z e ( ) > 0) ) {
ButtonPanelGraphCreation . setEnabledForComponent ( ” g e n e r a t e K n o t ” ,
false ) ;
}
if ( ! ( v e r t i c i e s . s i z e ( ) > 0) ) {
ButtonPanelGraphCreation . setEnabledForComponent ( ” c l e a r A l l ” , f a l s e ) ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” saveGraph ” , f a l s e ) ;
}
}

public s t a t i c boolean isGeneratingKnot ( ) {


return generatingKnot ;
}

public s t a t i c void setGeneratingKnot ( boolean generatingKnot ) {


GridGraphView . g e n e r a t i n g K n o t = g e n e r a t i n g K n o t ;
}

public s t a t i c void deleteEdge ( ) {


APPENDIX B. SOURCE CODE 114

i f ( s e l e c t e d E d g e != n u l l ) {
f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
i f ( s e l e c t e d E d g e . p t S e g D i s t ( e d g e s S t a r t . g e t ( i ) ) == 0 . 0
&& s e l e c t e d E d g e . p t S e g D i s t ( edgesEnd . g e t ( i ) ) == 0 . 0 ) {
e d g e s S t a r t . remove ( i ) ;
edgesEnd . remove ( i ) ;
break ;
}
}
selectedEdge = null ;
}
i f ( ! ( edgesStart . s i z e ( ) > 0) ) {
ButtonPanelGraphCreation . setEnabledForComponent ( ” g e n e r a t e K n o t ” ,
false ) ;
}
}

public s t a t i c void clearGraph ( ) {


startPoint = null ;
endPoint = n u l l ;
selected = null ;

v e r t i c i e s = new A r r a y L i s t <Point >() ;

e d g e s S t a r t = new A r r a y L i s t <Point >() ;


edgesEnd = new A r r a y L i s t <Point >() ;
e d g e s M i d p o i n t = new A r r a y L i s t <Point >() ;

generatingKnot = f a l s e ;
}

p u b l i c s t a t i c Color getOutlinePaintOne ( ) {
return outlinePaintOne ;
}

p u b l i c s t a t i c void setOutlinePaintOne ( Color outlinePaintOne ) {


GridGraphView . o u t l i n e P a i n t O n e = o u t l i n e P a i n t O n e ;
}

p u b l i c s t a t i c Color getOutlinePaintTwo ( ) {
return outlinePaintTwo ;
}

p u b l i c s t a t i c void setOutlinePaintTwo ( Color outlinePaintTwo ) {


GridGraphView . o u t l i n e P a i n t T w o = o u t l i n e P a i n t T w o ;
}

p u b l i c s t a t i c Color getInnerPaintOne ( ) {
return innerPaintOne ;
}

p u b l i c s t a t i c void setInnerPaintOne ( Color innerPaintOne ) {


GridGraphView . i n n e r P a i n t O n e = i n n e r P a i n t O n e ;
}

p u b l i c s t a t i c Color getInnerPaintTwo ( ) {
r e t u r n innerPaintTwo ;
}

p u b l i c s t a t i c v o i d s e t I n n e r P a i n t T w o ( C o l o r innerPaintTwo ) {
GridGraphView . innerPaintTwo = innerPaintTwo ;
APPENDIX B. SOURCE CODE 115

p u b l i c s t a t i c C o l o r getBackgroundPaintOne ( ) {
r e t u r n backgroundPaintOne ;
}

p u b l i c s t a t i c v o i d setBackgroundPaintOne ( C o l o r backgroundPaintOne ) {
GridGraphView . backgroundPaintOne = backgroundPaintOne ;
}

p u b l i c s t a t i c C o l o r getBackgroundPaintTwo ( ) {
r e t u r n backgroundPaintTwo ;
}

p u b l i c s t a t i c v o i d setBackgroundPaintTwo ( C o l o r backgroundPaintTwo ) {
GridGraphView . backgroundPaintTwo = backgroundPaintTwo ;
}

public s t a t i c int getOutlineThickness () {


return outlineThickness ;
}

public s t a t i c void setOutlineThickness ( i n t outlineThickness ) {


GridGraphView . o u t l i n e T h i c k n e s s = o u t l i n e T h i c k n e s s ;
}

public s t a t i c int getInnerThickness () {


return innerThickness ;
}

public s t a t i c void setInnerThickness ( i n t innerThickness ) {


GridGraphView . i n n e r T h i c k n e s s = i n n e r T h i c k n e s s ;
}

p u b l i c s t a t i c i n t getMAGIC NUMBER( ) {
r e t u r n MAGIC NUMBER;
}
}

B.1.5 File: KnotFileFilter.java


package knot . g u i ;

import j a v a . i o . F i l e ;

import j a v a x . swing . f i l e c h o o s e r . F i l e F i l t e r ;

public c l a s s KnotFileFilter extends F i l e F i l t e r {


public String getDescription () {
r e t u r n ”Knot F i l e s ” ;
}

public boolean accept ( F i l e f ) {


i f ( f . isDirectory () ) {
return true ;
}

String f i l e E x t e n s i o n = getExtension ( f ) ;
i f ( f i l e E x t e n s i o n != n u l l && f i l e E x t e n s i o n . e q u a l s ( ” knot ” ) ) {
APPENDIX B. SOURCE CODE 116

return true ;
} else {
return f a l s e ;
}

public s t a t i c String getExtension ( File f ) {


String fileExtension = null ;
S t r i n g s = f . getName ( ) ;
int i = s . lastIndexOf ( ’ . ’ ) ;

i f ( i > 0 && i < s . l e n g t h ( ) − 1 ) {


f i l e E x t e n s i o n = s . s u b s t r i n g ( i + 1 ) . toLowerCase ( ) ;
}
return fileExtension ;
}
}

B.1.6 File: KnotFileReaderWriter.java


package knot . g u i ;

import j a v a . awt . C o l o r ;
import j a v a . awt . P o i n t ;
import java . io . BufferedWriter ;
import java . io . F i l e ;
import java . io . FileWriter ;
import j a v a . i o . RandomAccessFile ;
import java . u t i l . ArrayList ;

p u b l i c c l a s s KnotFileReaderWriter {

private static f i n a l S t r i n g s e c t i o n S e p e r a t o r = ”@@@@” ;

private static f i n a l S t r i n g e l e m e n t S e p e r a t o r = ”####” ;

public s t a t i c void s a v e F i l e ( F i l e fileToBeUsed ) {


A r r a y L i s t <Point> v e r t i c i e s = GridGraphView . g e t V e r t i c i e s ( ) ;
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;
C o l o r o u t l i n e P a i n t O n e = GridGraphView . g e t O u t l i n e P a i n t O n e ( ) ;
C o l o r o u t l i n e P a i n t T w o = GridGraphView . g e t O u t l i n e P a i n t T w o ( ) ;
C o l o r i n n e r P a i n t O n e = GridGraphView . g e t I n n e r P a i n t O n e ( ) ;
C o l o r innerPaintTwo = GridGraphView . g e t I n n e r P a i n t T w o ( ) ;
C o l o r backgroundPaintOne = GridGraphView . getBackgroundPaintOne ( ) ;
C o l o r backgroundPaintTwo = GridGraphView . getBackgroundPaintTwo ( ) ;
i n t o u t l i n e T h i c k n e s s = GridGraphView . g e t O u t l i n e T h i c k n e s s ( ) ;
i n t i n n e r T h i c k n e s s = GridGraphView . g e t I n n e r T h i c k n e s s ( ) ;

S t r i n g output = ” ” ;

f o r ( i n t i = 0 ; i < v e r t i c i e s . s i z e ( ) ; i ++) {
Point point = v e r t i c i e s . get ( i ) ;
output = output + e l e m e n t S e p e r a t o r + p o i n t . getX ( ) + ” , ”
+ p o i n t . getY ( ) ;
}
APPENDIX B. SOURCE CODE 117

output = output + s e c t i o n S e p e r a t o r ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Point point = e d g e s S t a r t . get ( i ) ;
output = output + e l e m e n t S e p e r a t o r + p o i n t . getX ( ) + ” , ”
+ p o i n t . getY ( ) ;
}

output = output + s e c t i o n S e p e r a t o r ;

f o r ( i n t i = 0 ; i < edgesEnd . s i z e ( ) ; i ++) {


P o i n t p o i n t = edgesEnd . g e t ( i ) ;
output = output + e l e m e n t S e p e r a t o r + p o i n t . getX ( ) + ” , ”
+ p o i n t . getY ( ) ;
}

output = output + s e c t i o n S e p e r a t o r ;

output = output + o u t l i n e P a i n t O n e . getRed ( ) + ” , ”


+ outlinePaintOne . getGreen ( ) + ” , ” + outlinePaintOne . getBlue ( )
+ ” , ” + o u t l i n e P a i n t O n e . getAlpha ( ) + s e c t i o n S e p e r a t o r ;

output = output + o u t l i n e P a i n t T w o . getRed ( ) + ” , ”


+ outlinePaintTwo . getGreen ( ) + ” , ” + outlinePaintTwo . getBlue ( )
+ ” , ” + o u t l i n e P a i n t T w o . getAlpha ( ) + s e c t i o n S e p e r a t o r ;

output = output + i n n e r P a i n t O n e . getRed ( ) + ” , ”


+ innerPaintOne . getGreen ( ) + ” , ” + innerPaintOne . getBlue ( )
+ ” , ” + i n n e r P a i n t O n e . getAlpha ( ) + s e c t i o n S e p e r a t o r ;

output = output + innerPaintTwo . getRed ( ) + ” , ”


+ innerPaintTwo . g e t G r e e n ( ) + ” , ” + innerPaintTwo . g e t B l u e ( )
+ ” , ” + innerPaintTwo . getAlpha ( ) + s e c t i o n S e p e r a t o r ;

output = output + backgroundPaintOne . getRed ( ) + ” , ”


+ backgroundPaintOne . g e t Gr e e n ( ) + ” , ”
+ backgroundPaintOne . g e t B l u e ( ) + ” , ”
+ backgroundPaintOne . getAlpha ( ) + s e c t i o n S e p e r a t o r ;

output = output + backgroundPaintTwo . getRed ( ) + ” , ”


+ backgroundPaintTwo . g e t Gr e e n ( ) + ” , ”
+ backgroundPaintTwo . g e t B l u e ( ) + ” , ”
+ backgroundPaintTwo . getAlpha ( ) + s e c t i o n S e p e r a t o r ;

output = output + o u t l i n e T h i c k n e s s + s e c t i o n S e p e r a t o r ;

output = output + i n n e r T h i c k n e s s ;

try {
F i l e W r i t e r f i l e W r i t e r = new F i l e W r i t e r ( f i l e T o B e U s e d ) ;
B u f f e r e d W r i t e r b u f f W r i t e r = new B u f f e r e d W r i t e r ( f i l e W r i t e r ) ;

b u f f W r i t e r . w r i t e ( o u tp u t ) ;

buffWriter . close () ;
fileWriter . flush () ;
fileWriter . close () ;
} catch ( Exception e ) {

}
}
APPENDIX B. SOURCE CODE 118

public s t a t i c void l o a d F i l e ( F i l e fileToBeUsed ) {


A r r a y L i s t <Point> v e r t i c i e s = new A r r a y L i s t <Point >() ;
A r r a y L i s t <Point> e d g e s S t a r t = new A r r a y L i s t <Point >() ;
A r r a y L i s t <Point> edgesEnd = new A r r a y L i s t <Point >() ;
Color outlinePaintOne = n u l l ;
Color outlinePaintTwo = n u l l ;
Color innerPaintOne = n u l l ;
C o l o r innerPaintTwo = n u l l ;
C o l o r backgroundPaintOne = n u l l ;
C o l o r backgroundPaintTwo = n u l l ;
i n t o u t l i n e T h i c k n e s s = −1;
i n t i n n e r T h i c k n e s s = −1;
boolean s e t C l e a r A l l = f a l s e ;
boolean setGenerateKnot = f a l s e ;

try {
RandomAccessFile i n p u t = new RandomAccessFile ( f i l e T o B e U s e d , ” r ” ) ;
b y t e [ ] i n p u t B y t e s = new b y t e [ new Long ( i n p u t . l e n g t h ( ) ) . i n t V a l u e ( ) ] ;
input . read ( inputBytes ) ;
S t r i n g i n p u t S t r i n g = new S t r i n g ( i n p u t B y t e s ) ;
String [ ] sections = inputString . s p l i t ( sectionSeperator ) ;

String [ ] verticesFromFile = s ec ti on s [ 0 ] . s p l i t ( elementSeperator ) ;


f o r ( i n t i = 0 ; i < v e r t i c e s F r o m F i l e . l e n g t h ; i ++) {
i f ( verticesFromFile [ i ] . length ( ) > 0) {
String [ ] values = verticesFromFile [ i ] . s p l i t (” ,”) ;
i n t xValue = new Double ( v a l u e s [ 0 ] ) . i n t V a l u e ( ) ;
i n t yValue = new Double ( v a l u e s [ 1 ] ) . i n t V a l u e ( ) ;
i f ( xValue % GridGraphView . getMAGIC NUMBER( ) == 0
&& yValue % GridGraphView . getMAGIC NUMBER( ) == 0 ) {
P o i n t v e r t e x = new P o i n t ( xValue , yValue ) ;
v e r t i c i e s . add ( v e r t e x ) ;
setClearAll = true ;
}
} else {
continue ;
}
}

String [ ] edgesStartFromFile = s e c t i o n s [ 1 ] . s p l i t ( elementSeperator ) ;


f o r ( i n t i = 0 ; i < e d g e s S t a r t F r o m F i l e . l e n g t h ; i ++) {
i f ( edgesStartFromFile [ i ] . length ( ) > 0) {
String [ ] values = edgesStartFromFile [ i ] . s p l i t ( ” , ” ) ;
i n t xValue = new Double ( v a l u e s [ 0 ] ) . i n t V a l u e ( ) ;
i n t yValue = new Double ( v a l u e s [ 1 ] ) . i n t V a l u e ( ) ;
i f ( xValue % GridGraphView . getMAGIC NUMBER( ) == 0
&& yValue % GridGraphView . getMAGIC NUMBER( ) == 0 ) {
P o i n t v e r t e x = new P o i n t ( xValue , yValue ) ;
e d g e s S t a r t . add ( v e r t e x ) ;
setGenerateKnot = true ;
}
} else {
continue ;
}
}

S t r i n g [ ] edgesEndFromFile = s e c t i o n s [ 2 ] . s p l i t ( e l e m e n t S e p e r a t o r ) ;
f o r ( i n t i = 0 ; i < edgesEndFromFile . l e n g t h ; i ++) {
i f ( edgesEndFromFile [ i ] . l e n g t h ( ) > 0 ) {
S t r i n g [ ] v a l u e s = edgesEndFromFile [ i ] . s p l i t ( ” , ” ) ;
i n t xValue = new Double ( v a l u e s [ 0 ] ) . i n t V a l u e ( ) ;
APPENDIX B. SOURCE CODE 119

i n t yValue = new Double ( v a l u e s [ 1 ] ) . i n t V a l u e ( ) ;


i f ( xValue % GridGraphView . getMAGIC NUMBER( ) == 0
&& yValue % GridGraphView . getMAGIC NUMBER( ) == 0 ) {
P o i n t v e r t e x = new P o i n t ( xValue , yValue ) ;
edgesEnd . add ( v e r t e x ) ;
}
} else {
continue ;
}
}

String [ ] outlinePaintOneFromFile = s e c t i o n s [ 3 ] . s p l i t ( ” , ” ) ;
o u t l i n e P a i n t O n e = new C o l o r ( new I n t e g e r ( o u t l i n e P a i n t O n e F r o m F i l e [ 0 ] )
. i n t V a l u e ( ) , new I n t e g e r ( o u t l i n e P a i n t O n e F r o m F i l e [ 1 ] )
. i n t V a l u e ( ) , new I n t e g e r ( o u t l i n e P a i n t O n e F r o m F i l e [ 2 ] )
. i n t V a l u e ( ) , new I n t e g e r ( o u t l i n e P a i n t O n e F r o m F i l e [ 3 ] )
. intValue () ) ;

S t r i n g [ ] outlinePaintTwoFromFile = s e c t i o n s [ 4 ] . s p l i t ( ” , ” ) ;
o u t l i n e P a i n t T w o = new C o l o r ( new I n t e g e r ( o u t l i n e P a i n t T w o F r o m F i l e [ 0 ] )
. i n t V a l u e ( ) , new I n t e g e r ( o u t l i n e P a i n t T w o F r o m F i l e [ 1 ] )
. i n t V a l u e ( ) , new I n t e g e r ( o u t l i n e P a i n t T w o F r o m F i l e [ 2 ] )
. i n t V a l u e ( ) , new I n t e g e r ( o u t l i n e P a i n t T w o F r o m F i l e [ 3 ] )
. intValue () ) ;

S t r i n g [ ] innerPaintOneFromFile = s e c t i o n s [ 5 ] . s p l i t ( ” , ” ) ;
i n n e r P a i n t O n e = new C o l o r ( new I n t e g e r ( i n n e r P a i n t O n e F r o m F i l e [ 0 ] )
. i n t V a l u e ( ) , new I n t e g e r ( i n n e r P a i n t O n e F r o m F i l e [ 1 ] )
. i n t V a l u e ( ) , new I n t e g e r ( i n n e r P a i n t O n e F r o m F i l e [ 2 ] )
. i n t V a l u e ( ) , new I n t e g e r ( i n n e r P a i n t O n e F r o m F i l e [ 3 ] )
. intValue () ) ;

S t r i n g [ ] innerPaintTwoFromFile = s e c t i o n s [ 6 ] . s p l i t ( ” , ” ) ;
innerPaintTwo = new C o l o r ( new I n t e g e r ( innerPaintTwoFromFile [ 0 ] )
. i n t V a l u e ( ) , new I n t e g e r ( innerPaintTwoFromFile [ 1 ] )
. i n t V a l u e ( ) , new I n t e g e r ( innerPaintTwoFromFile [ 2 ] )
. i n t V a l u e ( ) , new I n t e g e r ( innerPaintTwoFromFile [ 3 ] )
. intValue () ) ;

S t r i n g [ ] backgroundPaintOneFromFile = s e c t i o n s [ 7 ] . s p l i t (” ,”) ;
backgroundPaintOne = new C o l o r ( new I n t e g e r (
backgroundPaintOneFromFile [ 0 ] ) . i n t V a l u e ( ) , new Integer (
backgroundPaintOneFromFile [ 1 ] ) . i n t V a l u e ( ) , new Integer (
backgroundPaintOneFromFile [ 2 ] ) . i n t V a l u e ( ) , new Integer (
backgroundPaintOneFromFile [ 3 ] ) . i n t V a l u e ( ) ) ;

S t r i n g [ ] backgroundPaintTwoFromFile = s e c t i o n s [ 8 ] . s p l i t (” ,”) ;
backgroundPaintTwo = new C o l o r ( new I n t e g e r (
backgroundPaintTwoFromFile [ 0 ] ) . i n t V a l u e ( ) , new Integer (
backgroundPaintTwoFromFile [ 1 ] ) . i n t V a l u e ( ) , new Integer (
backgroundPaintTwoFromFile [ 2 ] ) . i n t V a l u e ( ) , new Integer (
backgroundPaintTwoFromFile [ 3 ] ) . i n t V a l u e ( ) ) ;

o u t l i n e T h i c k n e s s = new I n t e g e r ( s e c t i o n s [ 9 ] ) . i n t V a l u e ( ) ;
i n n e r T h i c k n e s s = new I n t e g e r ( s e c t i o n s [ 1 0 ] ) . i n t V a l u e ( ) ;

GridGraphView . s e t V e r t i c i e s ( v e r t i c i e s ) ;
GridGraphView . s e t E d g e s S t a r t ( e d g e s S t a r t ) ;
GridGraphView . setEdgesEnd ( edgesEnd ) ;
GridGraphView . s e t O u t l i n e P a i n t O n e ( o u t l i n e P a i n t O n e ) ;
GridGraphView . s e t O u t l i n e P a i n t T w o ( o u t l i n e P a i n t T w o ) ;
GridGraphView . s e t I n n e r P a i n t O n e ( i n n e r P a i n t O n e ) ;
APPENDIX B. SOURCE CODE 120

GridGraphView . s e t I n n e r P a i n t T w o ( innerPaintTwo ) ;
GridGraphView . setBackgroundPaintOne ( backgroundPaintOne ) ;
GridGraphView . setBackgroundPaintTwo ( backgroundPaintTwo ) ;
GridGraphView . s e t O u t l i n e T h i c k n e s s ( o u t l i n e T h i c k n e s s ) ;
GridGraphView . s e t I n n e r T h i c k n e s s ( i n n e r T h i c k n e s s ) ;
GridGraphView . s e t G e n e r a t i n g K n o t ( f a l s e ) ;

ButtonPanelColours . changePreviewPanelColour (
” outlineColourOneSample ” , ” o u t l i n e P a n e l ” , outlinePaintOne ) ;
ButtonPanelColours . changePreviewPanelColour (
” outlineColou rTw oSa mpl e ” , ” o u t l i n e P a n e l ” , o u t l i n e P a i n t T w o ) ;
B u t t o n P a n e l C o l o u r s . c h a n g e P r e v i e w P a n e l C o l o u r ( ” innerColourOneSample ” ,
” innerPanel ” , innerPaintOne ) ;
B u t t o n P a n e l C o l o u r s . c h a n g e P r e v i e w P a n e l C o l o u r ( ” innerColourTwoSample ” ,
” i n n e r P a n e l ” , innerPaintTwo ) ;
ButtonPanelColours . changePreviewPanelColour (
” backgroundColourOneSample ” , ” backgroundPanel ” ,
backgroundPaintOne ) ;
ButtonPanelColours . changePreviewPanelColour (
” backgroundColourTwoSample ” , ” backgroundPanel ” ,
backgroundPaintTwo ) ;
ButtonPanelColours
. setSliderValues ( outlineThickness , innerThickness ) ;

ButtonPanelGraphCreation . setEnabledForComponent ( ” c l e a r A l l ” ,
setClearAll ) ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” saveGraph ” ,
setClearAll ) ;
ButtonPanelGraphCreation . setEnabledForComponent ( ” g e n e r a t e K n o t ” ,
setGenerateKnot ) ;

ButtonPanelColours . repaintView ( ) ;
GridGraphView . r e p a i n t V i e w ( ) ;

} catch ( Exception e ) {

}
}
}

B.1.7 File: MainWindow.java


package knot . g u i ;

import j a v a . awt . BorderLayout ;


import j a v a . awt . Dimension ;
import j a v a . awt . G r i d B a g C o n s t r a i n t s ;
import j a v a . awt . GridBagLayout ;
import j a v a . awt . e v e n t . ComponentEvent ;

import j a v a x . swing . JFrame ;


import j a v a x . swing . JPanel ;

p u b l i c c l a s s MainWindow {
p r i v a t e s t a t i c v o i d createAndShowGUI ( ) {
JFrame frame = new JFrame ( ” C e l t i c Knot G e n e r a t o r ” ) ;
frame . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE) ;

G r i d B a g C o n s t r a i n t s c = new G r i d B a g C o n s t r a i n t s ( ) ;
APPENDIX B. SOURCE CODE 121

JPanel window = new JPanel ( new GridBagLayout ( ) ) ;

c . gridx = 0;
c . gridy = 0;
window . add ( setUpGridArea ( ) , c ) ;

c . gridx = 0;
c . gridy = 1;
window . add ( setUpGraphButtonArea ( ) , c ) ;

c . gridx = 0;
c . gridy = 1;
window . add ( setUpKnotButtonArea ( ) , c ) ;

c . gridx = 1;
c . gridy = 0;
c . gridheight = 2;
window . add ( setUpColourButtonArea ( ) , c ) ;

frame . s e t C o n t e n t P a n e ( window ) ;

frame . pack ( ) ;
frame . s e t V i s i b l e ( t r u e ) ;
l o c k I n M i n S i z e ( frame ) ;
frame . s e t R e s i z a b l e ( f a l s e ) ;
}

p r i v a t e s t a t i c v o i d l o c k I n M i n S i z e ( f i n a l JFrame frame ) {
f i n a l i n t o r i g X = frame . g e t S i z e ( ) . width ;
f i n a l i n t o r i g Y = frame . g e t S i z e ( ) . h e i g h t ;
frame . addComponentListener ( new j a v a . awt . e v e n t . ComponentAdapter ( ) {
p u b l i c v o i d componentResized ( ComponentEvent e v e n t ) {
frame . s e t S i z e ( ( frame . getWidth ( ) < o r i g X ) ? o r i g X : frame
. getWidth ( ) , ( frame . g e t H e i g h t ( ) < o r i g Y ) ? o r i g Y
: frame . g e t H e i g h t ( ) ) ;
}
}) ;
}

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
j a v a x . swing . S w i n g U t i l i t i e s . i n v o k e L a t e r ( new Runnable ( ) {
p u b l i c v o i d run ( ) {
createAndShowGUI ( ) ;
}
}) ;
}

p r i v a t e s t a t i c JPanel setUpGridArea ( ) {
JPanel g r i d P a n e l = new JPanel ( new BorderLayout ( ) ) ;
Dimension d = new Dimension ( 6 0 0 , 6 0 0 ) ;
g r i d P a n e l . setMinimumSize ( d ) ;
gridPanel . setPreferredSize (d) ;

g r i d P a n e l . add ( new GridGraphView ( ) , BorderLayout .CENTER) ;

return gridPanel ;
}

p r i v a t e s t a t i c JPanel setUpGraphButtonArea ( ) {
JPanel b u t t o n P a n e l = new JPanel ( new BorderLayout ( ) ) ;
b u t t o n P a n e l . add ( new ButtonPanelGraphCreation ( ) , BorderLayout .CENTER) ;
APPENDIX B. SOURCE CODE 122

return buttonPanel ;
}

p r i v a t e s t a t i c JPanel setUpKnotButtonArea ( ) {
JPanel b u t t o n P a n e l = new JPanel ( new BorderLayout ( ) ) ;
b u t t o n P a n e l . add ( new ButtonPanelKnotCreated ( ) , BorderLayout .CENTER) ;

return buttonPanel ;
}

p r i v a t e s t a t i c JPanel setUpColourButtonArea ( ) {
JPanel c o l o u r P a n e l = new JPanel ( new BorderLayout ( ) ) ;
c o l o u r P a n e l . add ( new B u t t o n P a n e l C o l o u r s ( ) , BorderLayout .CENTER) ;

return colourPanel ;
}
}

B.1.8 File: PictureFileFilter.java


package knot . g u i ;

import j a v a . i o . F i l e ;

import j a v a x . swing . f i l e c h o o s e r . F i l e F i l t e r ;

public c l a s s P i c t u r e F i l e F i l t e r extends F i l e F i l t e r {

public String getDescription () {


return ” Picture Files ” ;
}

public boolean accept ( F i l e f ) {


i f ( f . isDirectory () ) {
return true ;
}

String f i l e E x t e n s i o n = getExtension ( f ) ;
i f ( f i l e E x t e n s i o n != n u l l
&& ( f i l e E x t e n s i o n . e q u a l s ( ” j p g ” ) | | f i l e E x t e n s i o n . e q u a l s ( ” png ” ) ) ) {
return true ;
} else {
return f a l s e ;
}

public s t a t i c String getExtension ( File f ) {


String fileExtension = null ;
S t r i n g s = f . getName ( ) ;
int i = s . lastIndexOf ( ’ . ’ ) ;

i f ( i > 0 && i < s . l e n g t h ( ) − 1 ) {


f i l e E x t e n s i o n = s . s u b s t r i n g ( i + 1 ) . toLowerCase ( ) ;
}
return fileExtension ;
}
APPENDIX B. SOURCE CODE 123

B.2 Package: knot.structure

B.2.1 File: CelticKnot.java


package knot . s t r u c t u r e ;

import j a v a . awt . Graphics2D ;


import j a v a . awt . P a i n t ;
import j a v a . awt . geom . Line2D ;
import j a v a . awt . geom . Point2D ;
import java . u t i l . ArrayList ;
import j a v a . u t i l . HashSet ;
import java . u t i l . I t e r a t o r ;

public c l a s s CelticKnot {

p r i v a t e HashSet<KnotComponent> knotComponents ;

public CelticKnot () {
knotComponents = new HashSet<KnotComponent >() ;
}

p u b l i c v o i d addKnotComponent ( KnotComponent knotComponentToBeAdded ) {


knotComponents . add ( knotComponentToBeAdded ) ;
}

p u b l i c I t e r a t o r <KnotComponent> g e t C o m p o n e n t I t e r a t o r ( ) {
I t e r a t o r <KnotComponent> i = knotComponents . i t e r a t o r ( ) ;
return i ;
}

p u b l i c HashSet<KnotComponent> getKnotComponentUsingPoint ( Point2D p o i n t ) {


HashSet<KnotComponent> knotComponentsUsingPoint = new
HashSet<KnotComponent >() ;
I t e r a t o r <KnotComponent> i = knotComponents . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent temp = i . n e x t ( ) ;
i f ( ( temp . g e t F i r s t P o i n t ( ) != n u l l && p o i n t . e q u a l s ( temp
. getFirstPoint () ) )
| | ( temp . g e t S e c o n d P o i n t ( ) != n u l l && p o i n t . e q u a l s ( temp
. getSecondPoint ( ) ) ) ) {
knotComponentsUsingPoint . add ( temp ) ;
} e l s e i f ( temp . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
C r o s s i n g c r o s s i n g P o i n t = ( C r o s s i n g ) temp ;
i f ( crossingPoint . getFirstCrossingPoint () . equals ( point )
| | crossingPoint . getSecondCrossingPoint () . equals ( point )
| | crossingPoint . getThirdCrossingPoint () . equals ( point )
| | crossingPoint . getForthCrossingPoint () . equals ( point ) ) {
knotComponentsUsingPoint . add ( temp ) ;
}
}
APPENDIX B. SOURCE CODE 124

}
r e t u r n knotComponentsUsingPoint ;
}

p u b l i c A r r a y L i s t <Line2D . Double> g e t A l l C r o s s i n g L i n e s ( ) {
A r r a y L i s t <Line2D . Double> c r o s s i n g L i n e s = new A r r a y L i s t <Line2D . Double >() ;

I t e r a t o r <KnotComponent> i = knotComponents . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent temp = i . n e x t ( ) ;
i f ( temp . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
C r o s s i n g c r o s s i n g P o i n t = ( C r o s s i n g ) temp ;
c r o s s i n g L i n e s . add ( c r o s s i n g P o i n t . g e t F i r s t C r o s s i n g L i n e ( ) ) ;
c r o s s i n g L i n e s . add ( c r o s s i n g P o i n t . g e t S e c o n d C r o s s i n g L i n e ( ) ) ;
}
}

return crossingLines ;
}

p u b l i c A r r a y L i s t <Line2D . Double> g e t U n c o n n e c t e d C r o s s i n g P o i n t s I n L i n e F o r m ( ) {
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s = new
A r r a y L i s t <Line2D . Double >() ;
A r r a y L i s t <Line2D . Double> c r o s s i n g L i n e s = g e t A l l C r o s s i n g L i n e s ( ) ;

f o r ( i n t i = 0 ; i < c r o s s i n g L i n e s . s i z e ( ) ; i ++) {
Line2D . Double c r o s s i n g L i n e = c r o s s i n g L i n e s . g e t ( i ) ;
HashSet<KnotComponent> k n o t C o m p o n e n t s U s i n g F i r s t P o i n t =
getKnotComponentUsingPoint ( c r o s s i n g L i n e
. getP1 ( ) ) ;
HashSet<KnotComponent> knotComponentsUsingSecondPoint =
getKnotComponentUsingPoint ( c r o s s i n g L i n e
. getP2 ( ) ) ;

i f ( k n o t C o m p o n e n t s U s i n g F i r s t P o i n t . s i z e ( ) == 1 ) {
u n c o n n e c t e d C r o s s i n g P o i n t s . add ( c r o s s i n g L i n e ) ;
}

i f ( knotComponentsUsingSecondPoint . s i z e ( ) == 1 ) {
Line2D . Double r e v e r s e C r o s s i n g L i n e = new Line2D . Double (
c r o s s i n g L i n e . getP2 ( ) , c r o s s i n g L i n e . getP1 ( ) ) ;
u n c o n n e c t e d C r o s s i n g P o i n t s . add ( r e v e r s e C r o s s i n g L i n e ) ;
}
}

return unconnectedCrossingPoints ;
}

p r i v a t e HashSet<KnotComponent> getUnlinkedComponents ( ) {
HashSet<KnotComponent> unlinkedComponents = new HashSet<KnotComponent >() ;
I t e r a t o r <KnotComponent> i = knotComponents . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent component = i . n e x t ( ) ;
i f ( component . i s F u l l y C o n n e c t e d ( ) == f a l s e ) {
unlinkedComponents . add ( component ) ;
}
}
APPENDIX B. SOURCE CODE 125

r e t u r n unlinkedComponents ;
}

p r i v a t e HashSet<C r o s s i n g > g e t U n t r a c e d C r o s s i n g s ( ) {
HashSet<C r o s s i n g > u n t r a c e d C r o s s i n g s = new HashSet<C r o s s i n g >() ;
I t e r a t o r <KnotComponent> i = knotComponents . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent component = i . n e x t ( ) ;
i f ( component . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” )
&& ( ( C r o s s i n g ) component ) . i s F u l l y U s e d I n T r a c e ( ) == f a l s e ) {
u n t r a c e d C r o s s i n g s . add ( ( C r o s s i n g ) component ) ;
}
}

return untracedCrossings ;
}

private Crossing getFirstPartiallyTracedIfAvailable (


HashSet<C r o s s i n g > u n t r a c e d C r o s s i n g s ) {
I t e r a t o r <C r o s s i n g > i = u n t r a c e d C r o s s i n g s . i t e r a t o r ( ) ;
Crossing c r o s s i n g = i . next ( ) ;
w h i l e ( i . hasNext ( ) && c r o s s i n g . h a s F i r s t L i n e B e e n U s e d I n T r a c e ( ) == f a l s e
&& c r o s s i n g . h a s F i r s t L i n e B e e n U s e d I n T r a c e ( ) == f a l s e ) {
c r o s s i n g = i . next ( ) ;
}

return crossing ;
}

p u b l i c void calculateOverAndUnderPattern ( ) {
HashSet<C r o s s i n g > u n t r a c e d C r o s s i n g s = g e t U n t r a c e d C r o s s i n g s ( ) ;
while ( untracedCrossings . s i z e ( ) > 0) {
boolean firstAndSecondOver = true ;
Crossing currentCrossing =
getFirstPartiallyTracedIfAvailable ( untracedCrossings ) ;
i f ( c u r r e n t C r o s s i n g . isOverAndUnderSet ( ) == f a l s e ) {
c ur ren t Cr os sin g . setFirstAndSecondOver ( firstAndSecondOver ) ;
} else {
firstAndSecondOver = currentCrossing . isFirstAndSecondOver ( ) ;
}
Point2D pointToBreakOn = n u l l ;
Point2D c u r r e n t C o n n e c t i n g P o i n t = n u l l ;
KnotComponent nonCrossingComponent = n u l l ;
boolean wasFirstLineBeingUsedOnLastCrossing = f a l s e ;
i f ( c u r r e n t C r o s s i n g . h a s F i r s t L i n e B e e n U s e d I n T r a c e ( ) == f a l s e ) {
currentConnectingPoint = currentCrossing
. getFirstCrossingPoint () ;
nonCrossingComponent = c u r r e n t C r o s s i n g . getConnectedToFirstCP ( ) ;
pointToBreakOn = c u r r e n t C r o s s i n g . g e t S e c o n d C r o s s i n g P o i n t ( ) ;
wasFirstLineBeingUsedOnLastCrossing = true ;
currentCrossing . setHasFirstLineBeenUsedInTrace ( true ) ;
} else {
currentConnectingPoint = currentCrossing
. getThirdCrossingPoint () ;
nonCrossingComponent = c u r r e n t C r o s s i n g . getConnectedToThirdCP ( ) ;
pointToBreakOn = c u r r e n t C r o s s i n g . g e t F o r t h C r o s s i n g P o i n t ( ) ;
wasFirstLineBeingUsedOnLastCrossing = f a l s e ;
c u r r e n t C r o s s i n g . setHasSecondLineBeenUsedInTrace ( t r u e ) ;
APPENDIX B. SOURCE CODE 126

w h i l e ( ! nonCrossingComponent . g e t F i r s t P o i n t ( ) . e q u a l s ( pointToBreakOn )
&& ! nonCrossingComponent . g e t S e c o n d P o i n t ( ) . e q u a l s (
pointToBreakOn ) ) {
i f ( c u r r e n t C r o s s i n g . e q u a l s ( nonCrossingComponent
. getFromCrossingPoint ( ) ) ) {
c u r r e n t C r o s s i n g = nonCrossingComponent . g e t T o C r o s s i n g P o i n t ( ) ;
} else {
c u r r e n t C r o s s i n g = nonCrossingComponent
. getFromCrossingPoint ( ) ;
}
i f ( c u r r e n t C o n n e c t i n g P o i n t . e q u a l s ( nonCrossingComponent
. getFirstPoint () ) ) {
c u r r e n t C o n n e c t i n g P o i n t = nonCrossingComponent
. getSecondPoint ( ) ;
} else {
c u r r e n t C o n n e c t i n g P o i n t = nonCrossingComponent
. getFirstPoint () ;
}

if ( ( ( currentConnectingPoint . equals ( currentCrossing


. getFirstCrossingPoint () ) | | currentConnectingPoint
. e q u a l s ( c u r r e n t C r o s s i n g . g e t S e c o n d C r o s s i n g P o i n t ( ) ) ) &&
w a s F i r s t L i n e B e i n g U s e d O n L a s t C r o s s i n g == t r u e )
| | (( currentConnectingPoint . equals ( currentCrossing
. getThirdCrossingPoint () ) | | currentConnectingPoint
. equals ( currentCrossing . getForthCrossingPoint () ) )
&& w a s F i r s t L i n e B e i n g U s e d O n L a s t C r o s s i n g ==
false )) {
firstAndSecondOver = ! firstAndSecondOver ;
} e l s e i f ( ( ( currentConnectingPoint . equals ( currentCrossing
. getThirdCrossingPoint () ) | | currentConnectingPoint
. e q u a l s ( c u r r e n t C r o s s i n g . g e t F o r t h C r o s s i n g P o i n t ( ) ) ) &&
w a s F i r s t L i n e B e i n g U s e d O n L a s t C r o s s i n g == t r u e )
| | (( currentConnectingPoint . equals ( currentCrossing
. getFirstCrossingPoint () ) | | currentConnectingPoint
. equals ( currentCrossing
. g e t S e c o n d C r o s s i n g P o i n t ( ) ) ) &&
w a s F i r s t L i n e B e i n g U s e d O n L a s t C r o s s i n g ==
false )) {
wasFirstLineBeingUsedOnLastCrossing =
! wasFirstLineBeingUsedOnLastCrossing ;
}

i f ( c u r r e n t C r o s s i n g . isOverAndUnderSet ( ) == f a l s e ) {
c u r r e n t C r o s s i n g . setFirstAndSecondOver ( firstAndSecondOver ) ;
}

i f ( w a s F i r s t L i n e B e i n g U s e d O n L a s t C r o s s i n g == t r u e ) {
currentCrossing . setHasFirstLineBeenUsedInTrace ( true ) ;
} else {
c u r r e n t C r o s s i n g . setHasSecondLineBeenUsedInTrace ( t r u e ) ;
}

i f ( currentConnectingPoint . equals ( currentCrossing


. getFirstCrossingPoint () ) ) {
nonCrossingComponent = c u r r e n t C r o s s i n g
. getConnectedToSecondCP ( ) ;
currentConnectingPoint = currentCrossing
. getSecondCrossingPoint () ;
} e l s e i f ( currentConnectingPoint . equals ( currentCrossing
APPENDIX B. SOURCE CODE 127

. getSecondCrossingPoint () ) ) {
nonCrossingComponent = c u r r e n t C r o s s i n g
. getConnectedToFirstCP ( ) ;
currentConnectingPoint = currentCrossing
. getFirstCrossingPoint () ;
} e l s e i f ( currentConnectingPoint . equals ( currentCrossing
. getThirdCrossingPoint () ) ) {
nonCrossingComponent = c u r r e n t C r o s s i n g
. getConnectedToForthCP ( ) ;
currentConnectingPoint = currentCrossing
. getForthCrossingPoint () ;
} e l s e i f ( currentConnectingPoint . equals ( currentCrossing
. getForthCrossingPoint () ) ) {
nonCrossingComponent = c u r r e n t C r o s s i n g
. getConnectedToThirdCP ( ) ;
currentConnectingPoint = currentCrossing
. getThirdCrossingPoint () ;
}
}
untracedCrossings = getUntracedCrossings () ;
}

p u b l i c v o i d createLinksBetweenComponents ( ) {
HashSet<KnotComponent> unlinkedComponents = getUnlinkedComponents ( ) ;
w h i l e ( unlinkedComponents . s i z e ( ) > 0 ) {
I t e r a t o r <KnotComponent> i = unlinkedComponents . i t e r a t o r ( ) ;
Crossing startingCrossing = null ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent temp = i . n e x t ( ) ;
i f ( temp . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
s t a r t i n g C r o s s i n g = ( C r o s s i n g ) temp ;
break ;
}
}

HashSet<KnotComponent> k n o t C o m p o n e n t s U s i n g S t a r t i n g P o i n t = n u l l ;
KnotComponent currentComponent = n u l l ;
KnotComponent nextComponent = n u l l ;
Point2D c u r r e n t C o n n e c t i n g P o i n t = n u l l ;
Point2D pointToBreakOn = n u l l ;
i f ( s t a r t i n g C r o s s i n g . getConnectedToFirstCP ( ) == n u l l ) {
knotComponentsUsingStartingPoint =
getKnotComponentUsingPoint ( s t a r t i n g C r o s s i n g
. getFirstCrossingPoint () ) ;
currentConnectingPoint = startingCrossing
. getFirstCrossingPoint () ;
pointToBreakOn = s t a r t i n g C r o s s i n g . g e t S e c o n d C r o s s i n g P o i n t ( ) ;
} else {
knotComponentsUsingStartingPoint =
getKnotComponentUsingPoint ( s t a r t i n g C r o s s i n g
. getThirdCrossingPoint () ) ;
currentConnectingPoint = startingCrossing
. getThirdCrossingPoint () ;
pointToBreakOn = s t a r t i n g C r o s s i n g . g e t F o r t h C r o s s i n g P o i n t ( ) ;
}

i = knotComponentsUsingStartingPoint . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
APPENDIX B. SOURCE CODE 128

KnotComponent temp = i . n e x t ( ) ;
i f ( ! temp . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
currentComponent = temp ;
s t a r t i n g C r o s s i n g . connectPointToComponent (
c u r r e n t C o n n e c t i n g P o i n t , currentComponent ) ;
currentComponent . s e t F r o m C r o s s i n g P o i n t ( s t a r t i n g C r o s s i n g ) ;
break ;
}
}

w h i l e ( ! pointToBreakOn . e q u a l s ( currentComponent . g e t S e c o n d P o i n t ( ) )
&& ! pointToBreakOn . e q u a l s ( currentComponent . g e t F i r s t P o i n t ( ) ) ) {
i f ( nextComponent != n u l l ) {
currentComponent = nextComponent ;
nextComponent = n u l l ;
}
HashSet<KnotComponent>
knotComponentsUsingSecondPointOfCurrentComponent = n u l l ;
i f ( currentComponent . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
i f ( ( ( C r o s s i n g ) currentComponent ) . g e t F i r s t C r o s s i n g P o i n t ( )
. equals ( currentConnectingPoint ) ) {
knotComponentsUsingSecondPointOfCurrentComponent =
getKnotComponentUsingPoint ( ( ( C r o s s i n g ) currentComponent )
. getSecondCrossingPoint () ) ;
c u r r e n t C o n n e c t i n g P o i n t = ( ( C r o s s i n g ) currentComponent )
. getSecondCrossingPoint () ;
} e l s e i f ( ( ( C r o s s i n g ) currentComponent )
. getSecondCrossingPoint () . equals (
currentConnectingPoint ) ) {
knotComponentsUsingSecondPointOfCurrentComponent =
getKnotComponentUsingPoint ( ( ( C r o s s i n g ) currentComponent )
. getFirstCrossingPoint () ) ;
c u r r e n t C o n n e c t i n g P o i n t = ( ( C r o s s i n g ) currentComponent )
. getFirstCrossingPoint () ;
} e l s e i f ( ( ( C r o s s i n g ) currentComponent )
. getThirdCrossingPoint () . equals (
currentConnectingPoint ) ) {
knotComponentsUsingSecondPointOfCurrentComponent =
getKnotComponentUsingPoint ( ( ( C r o s s i n g ) currentComponent )
. getForthCrossingPoint () ) ;
c u r r e n t C o n n e c t i n g P o i n t = ( ( C r o s s i n g ) currentComponent )
. getForthCrossingPoint () ;
} else {
knotComponentsUsingSecondPointOfCurrentComponent =
getKnotComponentUsingPoint ( ( ( C r o s s i n g ) currentComponent )
. getThirdCrossingPoint () ) ;
c u r r e n t C o n n e c t i n g P o i n t = ( ( C r o s s i n g ) currentComponent )
. getThirdCrossingPoint () ;
}
} else {
i f ( currentComponent . g e t F i r s t P o i n t ( ) . e q u a l s (
currentConnectingPoint ) ) {
knotComponentsUsingSecondPointOfCurrentComponent =
getKnotComponentUsingPoint ( currentComponent
. getSecondPoint ( ) ) ;
c u r r e n t C o n n e c t i n g P o i n t = currentComponent
. getSecondPoint ( ) ;
} else {
knotComponentsUsingSecondPointOfCurrentComponent =
getKnotComponentUsingPoint ( currentComponent
APPENDIX B. SOURCE CODE 129

. getFirstPoint () ) ;
c u r r e n t C o n n e c t i n g P o i n t = currentComponent
. getFirstPoint () ;
}
}

i = knotComponentsUsingSecondPointOfCurrentComponent . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent temp = i . n e x t ( ) ;
i f ( ! currentComponent . e q u a l s ( temp ) ) {
nextComponent = temp ;
break ;
}
}
i f ( nextComponent . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
currentComponent
. s e t T o C r o s s i n g P o i n t ( ( C r o s s i n g ) nextComponent ) ;
( ( C r o s s i n g ) nextComponent ) . connectPointToComponent (
c u r r e n t C o n n e c t i n g P o i n t , currentComponent ) ;
} else {
( ( C r o s s i n g ) currentComponent ) . connectPointToComponent (
c u r r e n t C o n n e c t i n g P o i n t , nextComponent ) ;
nextComponent
. s e t F r o m C r o s s i n g P o i n t ( ( C r o s s i n g ) currentComponent ) ;
}
}
unlinkedComponents = getUnlinkedComponents ( ) ;
}
}

p u b l i c v o i d paintKnot ( Graphics2D g2 , P a i n t o u t l i n e P a i n t , P a i n t i n n e r P a i n t ,
P a i n t backgroundPaint , i n t o u t l i n e W i d t h , i n t innerWidth ) {
P a i n t o l d P a i n t = g2 . g e t P a i n t ( ) ;
g2 . s e t P a i n t ( b a ck gr o u nd P a i n t ) ;
g2 . f i l l R e c t ( 0 , 0 , 6 0 0 , 6 0 0 ) ;
g2 . s e t P a i n t ( o l d P a i n t ) ;

I t e r a t o r <KnotComponent> i = knotComponents . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent knotComponent = i . n e x t ( ) ;
i f ( ! knotComponent . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
knotComponent . paintComponent ( g2 , o u t l i n e P a i n t , i n n e r P a i n t ,
o u t l i n e W i d t h , innerWidth ) ;
}
}

i = knotComponents . i t e r a t o r ( ) ;
w h i l e ( i . hasNext ( ) ) {
KnotComponent knotComponent = i . n e x t ( ) ;
i f ( knotComponent . g e t C l a s s ( ) . getName ( ) . t o S t r i n g ( ) . e q u a l s (
” knot . s t r u c t u r e . C r o s s i n g ” ) ) {
( ( C r o s s i n g ) knotComponent ) . paintComponent ( g2 , o u t l i n e P a i n t ,
i n n e r P a i n t , o u t l i n e W i d t h , innerWidth ) ;
}
}
}
}
APPENDIX B. SOURCE CODE 130

B.2.2 File: Crossing.java


package knot . s t r u c t u r e ;

import j a v a . awt . B a s i c S t r o k e ;
import j a v a . awt . Graphics2D ;
import j a v a . awt . P a i n t ;
import j a v a . awt . P o i n t ;
import j a v a . awt . geom . Line2D ;
import j a v a . awt . geom . Point2D ;

p u b l i c c l a s s C r o s s i n g e x t e n d s KnotComponent {

p r i v a t e Point f i r s t C r o s s i n g P o i n t ;

p r i v a t e Point secondCrossingPoint ;

p r i v a t e Point t h i r d C r o s s i n g P o i n t ;

p r i v a t e Point f o r t h C r o s s i n g P o i n t ;

p r i v a t e KnotComponent connectedToFirstCP ;

p r i v a t e KnotComponent connectedToSecondCP ;

p r i v a t e KnotComponent connectedToThirdCP ;

p r i v a t e KnotComponent connectedToForthCP ;

p r i v a t e boolean isFirstAndSecondOver ;

p r i v a t e b o o l e a n isOverAndUnderSet ;

private boolean hasFirstLineBeenUsedInTrace ;

p r i v a t e b o o l e a n hasSecondLineBeenUsedInTrace ;

p u b l i c Crossing ( Point f i r s t C r o s s i n g P o i n t , Point secondCrossingPoint ,


Point thirdCrossingPoint , Point f o r t h C r o s s i n g P o i n t ) {
super ( null , null , null , n u l l ) ;
this . firstCrossingPoint = firstCrossingPoint ;
t h i s . secondCrossingPoint = secondCrossingPoint ;
this . thirdCrossingPoint = thirdCrossingPoint ;
this . forthCrossingPoint = forthCrossingPoint ;
connectedToFirstCP = n u l l ;
connectedToSecondCP = n u l l ;
connectedToThirdCP = n u l l ;
connectedToForthCP = n u l l ;
isFirstAndSecondOver = f a l s e ;
isOverAndUnderSet = f a l s e ;
hasFirstLineBeenUsedInTrace = f a l s e ;
hasSecondLineBeenUsedInTrace = f a l s e ;
}

p u b l i c v o i d connectPointToComponent ( Point2D p o i n t , KnotComponent component ) {


i f ( point . equals ( firstCrossingPoint ) ) {
connectedToFirstCP = component ;
} e l s e i f ( point . equals ( secondCrossingPoint ) ) {
connectedToSecondCP = component ;
APPENDIX B. SOURCE CODE 131

} e l s e i f ( point . equals ( thirdCrossingPoint ) ) {


connectedToThirdCP = component ;
} else {
connectedToForthCP = component ;
}
}

public boolean isFullyUsedInTrace () {


r e t u r n h a s F i r s t L i n e B e e n U s e d I n T r a c e && hasSecondLineBeenUsedInTrace ;
}

p u b l i c Line2D . Double g e t F i r s t C r o s s i n g L i n e ( ) {
r e t u r n new Line2D . Double ( f i r s t C r o s s i n g P o i n t , s e c o n d C r o s s i n g P o i n t ) ;
}

p u b l i c Line2D . Double g e t S e c o n d C r o s s i n g L i n e ( ) {
r e t u r n new Line2D . Double ( t h i r d C r o s s i n g P o i n t , f o r t h C r o s s i n g P o i n t ) ;
}

p u b l i c Point g e t F i r s t C r o s s i n g P o i n t ( ) {
return firstCrossingPoint ;
}

p u b l i c void s e t F i r s t C r o s s i n g P o i n t ( Point f i r s t C r o s s i n g P o i n t ) {
this . firstCrossingPoint = firstCrossingPoint ;
}

p u b l i c Point getSecondCrossingPoint ( ) {
return secondCrossingPoint ;
}

p u b l i c void setSecondCrossingPoint ( Point secondCrossingPoint ) {


t h i s . secondCrossingPoint = secondCrossingPoint ;
}

p u b l i c Point getThirdCrossingPoint ( ) {
return thirdCrossingPoint ;
}

p u b l i c void setThirdCrossingPoint ( Point t h i r d C r o s s i n g P o i n t ) {


this . thirdCrossingPoint = thirdCrossingPoint ;
}

p u b l i c Point getForthCrossingPoint ( ) {
return forthCrossingPoint ;
}

p u b l i c void setForthCrossingPoint ( Point f o r t h C r o s s i n g P o i n t ) {


this . forthCrossingPoint = forthCrossingPoint ;
}

p u b l i c KnotComponent getConnectedToFirstCP ( ) {
r e t u r n connectedToFirstCP ;
}

p u b l i c v o i d setConnectedToFirstCP ( KnotComponent connectedToFirstCP ) {


t h i s . connectedToFirstCP = connectedToFirstCP ;
}

p u b l i c KnotComponent getConnectedToSecondCP ( ) {
r e t u r n connectedToSecondCP ;
}
APPENDIX B. SOURCE CODE 132

p u b l i c v o i d setConnectedToSecondCP ( KnotComponent connectedToSecondCP ) {


t h i s . connectedToSecondCP = connectedToSecondCP ;
}

p u b l i c KnotComponent getConnectedToThirdCP ( ) {
r e t u r n connectedToThirdCP ;
}

p u b l i c v o i d setConnectedToThirdCP ( KnotComponent connectedToThirdCP ) {


t h i s . connectedToThirdCP = connectedToThirdCP ;
}

p u b l i c KnotComponent getConnectedToForthCP ( ) {
r e t u r n connectedToForthCP ;
}

p u b l i c v o i d setConnectedToForthCP ( KnotComponent connectedToForthCP ) {


t h i s . connectedToForthCP = connectedToForthCP ;
}

p u b l i c boolean isFirstAndSecondOver ( ) {
return isFirstAndSecondOver ;
}

p u b l i c void setFirstAndSecondOver ( boolean isFirstAndSecondOver ) {


t h i s . isFirstAndSecondOver = isFirstAndSecondOver ;
setOverAndUnderSet ( t r u e ) ;
}

public boolean hasFirstLineBeenUsedInTrace () {


return hasFirstLineBeenUsedInTrace ;
}

public void setHasFirstLineBeenUsedInTrace ( boolean hasFirstBeenUsedInTrace ) {


t h i s . hasFirstLineBeenUsedInTrace = hasFirstBeenUsedInTrace ;
}

p u b l i c b o o l e a n hasSecondLineBeenUsedInTrace ( ) {
r e t u r n hasSecondLineBeenUsedInTrace ;
}

p u b l i c v o i d s e t H a s S e c o n d L i n e B e e n U s e d I n T r a c e ( b o o l e a n hasSecondBeenUsedInTrace ) {
t h i s . hasSecondLineBeenUsedInTrace = hasSecondBeenUsedInTrace ;
}

p u b l i c v o i d paintComponent ( Graphics2D g2 , P a i n t o u t l i n e P a i n t ,
P a i n t i n n e r P a i n t , i n t o u t l i n e W i d t h , i n t innerWidth ) {
i f ( i s F i r s t A n d S e c o n d O v e r == f a l s e ) {
g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( f i r s t C r o s s i n g P o i n t , s e c o n d C r o s s i n g P o i n t ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( f i r s t C r o s s i n g P o i n t , s e c o n d C r o s s i n g P o i n t ) ) ;

g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP BUTT,
APPENDIX B. SOURCE CODE 133

B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( t h i r d C r o s s i n g P o i n t , f o r t h C r o s s i n g P o i n t ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( t h i r d C r o s s i n g P o i n t , f o r t h C r o s s i n g P o i n t ) ) ;
} else {
g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( t h i r d C r o s s i n g P o i n t , f o r t h C r o s s i n g P o i n t ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( t h i r d C r o s s i n g P o i n t , f o r t h C r o s s i n g P o i n t ) ) ;

g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( f i r s t C r o s s i n g P o i n t , s e c o n d C r o s s i n g P o i n t ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( f i r s t C r o s s i n g P o i n t , s e c o n d C r o s s i n g P o i n t ) ) ;
}
}

public boolean isFullyConnected ( ) {


boolean isFullyConnected = true ;

i f ( connectedToFirstCP == n u l l | | connectedToSecondCP == n u l l
| | connectedToThirdCP == n u l l | | connectedToForthCP == n u l l ) {
isFullyConnected = f a l s e ;
}

return isFullyConnected ;
}

p u b l i c b o o l e a n isOverAndUnderSet ( ) {
r e t u r n isOverAndUnderSet ;
}

p u b l i c v o i d setOverAndUnderSet ( b o o l e a n isOverAndUnderSet ) {
t h i s . isOverAndUnderSet = isOverAndUnderSet ;
}
}

B.2.3 File: CurvedLine.java


package knot . s t r u c t u r e ;

import j a v a . awt . B a s i c S t r o k e ;
import j a v a . awt . Graphics2D ;
import j a v a . awt . P a i n t ;
import j a v a . awt . P o i n t ;
import j a v a . awt . geom . CubicCurve2D ;
import j a v a . awt . geom . Point2D ;
APPENDIX B. SOURCE CODE 134

p u b l i c c l a s s CurvedLine e x t e n d s KnotComponent {

p r i v a t e Point2D f i r s t C o n t r o l P o i n t ;

p r i v a t e Point2D s e c o n d C o n t r o l P o i n t ;

p u b l i c CurvedLine ( C r o s s i n g f r o m C r o s s i n g P o i n t , C r o s s i n g t o C r o s s i n g P o i n t ,
P o i n t f i r s t P o i n t , P o i n t s e c o n d P o i n t , Point2D f i r s t C o n t r o l P o i n t ,
Point2D s e c o n d C o n t r o l P o i n t ) {
super ( fromCrossingPoint , toCrossingPoint , f i r s t P o i n t , secondPoint ) ;
this . firstControlPoint = firstControlPoint ;
t h i s . secondControlPoint = secondControlPoint ;
}

p u b l i c Point2D g e t F i r s t C o n t r o l P o i n t ( ) {
return firstControlPoint ;
}

p u b l i c v o i d s e t F i r s t C o n t r o l P o i n t ( Point2D f i r s t C o n t r o l P o i n t ) {
this . firstControlPoint = firstControlPoint ;
}

p u b l i c Point2D g e t S e c o n d C o n t r o l P o i n t ( ) {
return secondControlPoint ;
}

p u b l i c v o i d s e t S e c o n d C o n t r o l P o i n t ( Point2D s e c o n d C o n t r o l P o i n t ) {
t h i s . secondControlPoint = secondControlPoint ;
}

p u b l i c v o i d paintComponent ( Graphics2D g2 , P a i n t o u t l i n e P a i n t ,
P a i n t i n n e r P a i n t , i n t o u t l i n e W i d t h , i n t innerWidth ) {
g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP SQUARE,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new CubicCurve2D . Double ( g e t F i r s t P o i n t ( ) . getX ( ) , g e t F i r s t P o i n t ( )
. getY ( ) , f i r s t C o n t r o l P o i n t . getX ( ) , f i r s t C o n t r o l P o i n t . getY ( ) ,
s e c o n d C o n t r o l P o i n t . getX ( ) , s e c o n d C o n t r o l P o i n t . getY ( ) ,
g e t S e c o n d P o i n t ( ) . getX ( ) , g e t S e c o n d P o i n t ( ) . getY ( ) ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP SQUARE,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new CubicCurve2D . Double ( g e t F i r s t P o i n t ( ) . getX ( ) , g e t F i r s t P o i n t ( )
. getY ( ) , f i r s t C o n t r o l P o i n t . getX ( ) , f i r s t C o n t r o l P o i n t . getY ( ) ,
s e c o n d C o n t r o l P o i n t . getX ( ) , s e c o n d C o n t r o l P o i n t . getY ( ) ,
g e t S e c o n d P o i n t ( ) . getX ( ) , g e t S e c o n d P o i n t ( ) . getY ( ) ) ) ;
}

public boolean isFullyConnected ( ) {


boolean isFullyConnected = true ;

i f ( g e t F r o m C r o s s i n g P o i n t ( ) == n u l l | | g e t T o C r o s s i n g P o i n t ( ) == n u l l ) {
isFullyConnected = f a l s e ;
}

return isFullyConnected ;
}
APPENDIX B. SOURCE CODE 135

B.2.4 File: KnotComponent.java


package knot . s t r u c t u r e ;

import j a v a . awt . Graphics2D ;


import j a v a . awt . P a i n t ;
import j a v a . awt . P o i n t ;

p u b l i c a b s t r a c t c l a s s KnotComponent {

private Crossing fromCrossingPoint ;

private Crossing toCrossingPoint ;

p r i v a t e Point f i r s t P o i n t ;

p r i v a t e Point secondPoint ;

p u b l i c KnotComponent ( C r o s s i n g f r o m C r o s s i n g P o i n t , C r o s s i n g t o C r o s s i n g P o i n t ,
Point f i r s t P o i n t , Point secondPoint ) {
t h i s . fromCrossingPoint = fromCrossingPoint ;
this . toCrossingPoint = toCrossingPoint ;
t h i s . secondPoint = secondPoint ;
this . firstPoint = firstPoint ;
}

p u b l i c Crossing getFromCrossingPoint ( ) {
return fromCrossingPoint ;
}

public void setFromCrossingPoint ( Crossing fromCrossingPoint ) {


t h i s . fromCrossingPoint = fromCrossingPoint ;
}

public Crossing getToCrossingPoint () {


return toCrossingPoint ;
}

public void setToCrossingPoint ( Crossing toCrossingPoint ) {


this . toCrossingPoint = toCrossingPoint ;
}

p u b l i c Point g e t F i r s t P o i n t ( ) {
return f i r s t P o i n t ;
}

p u b l i c void s e t F i r s t P o i n t ( Point f i r s t P o i n t ) {
this . firstPoint = firstPoint ;
}

p u b l i c Point getSecondPoint ( ) {
return secondPoint ;
}

p u b l i c void setSecondPoint ( Point secondPoint ) {


t h i s . secondPoint = secondPoint ;
}
APPENDIX B. SOURCE CODE 136

p u b l i c a b s t r a c t v o i d paintComponent ( Graphics2D g2 , P a i n t o u t l i n e P a i n t ,
P a i n t i n n e r P a i n t , i n t o u t l i n e W i d t h , i n t innerWidth ) ;

public abstract boolean isFullyConnected ( ) ;

B.2.5 File: PointedReturn.java


package knot . s t r u c t u r e ;

import j a v a . awt . B a s i c S t r o k e ;
import j a v a . awt . Graphics2D ;
import j a v a . awt . P a i n t ;
import j a v a . awt . P o i n t ;
import j a v a . awt . geom . CubicCurve2D ;
import j a v a . awt . geom . GeneralPath ;
import j a v a . awt . geom . Point2D ;

p u b l i c c l a s s Po i n t e d Re t u r n e x t e n d s KnotComponent {

p r i v a t e Point2D f i r s t R e t u r n P o i n t ;

p r i v a t e Point2D commonReturnPoint ;

p r i v a t e Point2D s e c o n d R e t u r n P o i n t ;

p r i v a t e Point2D f i r s t C o n t r o l P o i n t ;

p r i v a t e Point2D s e c o n d C o n t r o l P o i n t ;

p r i v a t e Point2D t h i r d C o n t r o l P o i n t ;

p r i v a t e Point2D f o r t h C o n t r o l P o i n t ;

p u b l i c Po i n t e d Re t u r n ( C r o s s i n g f r o m C r o s s i n g P o i n t , C r o s s i n g t o C r o s s i n g P o i n t ,
P o i n t f i r s t P o i n t , P o i n t s e c o n d P o i n t , Point2D f i r s t R e t u r n P o i n t ,
Point2D commonReturnPoint , Point2D s e c o n d R e t u r n P o i n t ,
Point2D f i r s t C o n t r o l P o i n t , Point2D s e c o n d C o n t r o l P o i n t ,
Point2D t h i r d C o n t r o l P o i n t , Point2D f o r t h C o n t r o l P o i n t ) {
super ( fromCrossingPoint , toCrossingPoint , f i r s t P o i n t , secondPoint ) ;
this . firstReturnPoint = firstReturnPoint ;
t h i s . commonReturnPoint = commonReturnPoint ;
t h i s . secondReturnPoint = secondReturnPoint ;
this . firstControlPoint = firstControlPoint ;
t h i s . secondControlPoint = secondControlPoint ;
this . thirdControlPoint = thirdControlPoint ;
this . forthControlPoint = forthControlPoint ;
}

p u b l i c Point2D g e t F i r s t R e t u r n P o i n t ( ) {
return firstReturnPoint ;
}

p u b l i c v o i d s e t F i r s t R e t u r n P o i n t ( Point2D f i r s t R e t u r n P o i n t ) {
this . firstReturnPoint = firstReturnPoint ;
}
APPENDIX B. SOURCE CODE 137

p u b l i c Point2D getCommonReturnPoint ( ) {
r e t u r n commonReturnPoint ;
}

p u b l i c v o i d setCommonReturnPoint ( Point2D commonReturnPoint ) {


t h i s . commonReturnPoint = commonReturnPoint ;
}

p u b l i c Point2D g e t S e c o n d R e t u r n P o i n t ( ) {
return secondReturnPoint ;
}

p u b l i c v o i d s e t S e c o n d R e t u r n P o i n t ( Point2D s e c o n d R e t u r n P o i n t ) {
t h i s . secondReturnPoint = secondReturnPoint ;
}

p u b l i c Point2D g e t F i r s t C o n t r o l P o i n t ( ) {
return firstControlPoint ;
}

p u b l i c v o i d s e t F i r s t C o n t r o l P o i n t ( Point2D f i r s t C o n t r o l P o i n t ) {
this . firstControlPoint = firstControlPoint ;
}

p u b l i c Point2D g e t S e c o n d C o n t r o l P o i n t ( ) {
return secondControlPoint ;
}

p u b l i c v o i d s e t S e c o n d C o n t r o l P o i n t ( Point2D s e c o n d C o n t r o l P o i n t ) {
t h i s . secondControlPoint = secondControlPoint ;
}

p u b l i c Point2D g e t T h i r d C o n t r o l P o i n t ( ) {
return thirdControlPoint ;
}

p u b l i c v o i d s e t T h i r d C o n t r o l P o i n t ( Point2D t h i r d C o n t r o l P o i n t ) {
this . thirdControlPoint = thirdControlPoint ;
}

p u b l i c Point2D g e t F o r t h C o n t r o l P o i n t ( ) {
return forthControlPoint ;
}

p u b l i c v o i d s e t F o r t h C o n t r o l P o i n t ( Point2D f o r t h C o n t r o l P o i n t ) {
this . forthControlPoint = forthControlPoint ;
}

p u b l i c v o i d paintComponent ( Graphics2D g2 , P a i n t o u t l i n e P a i n t ,
P a i n t i n n e r P a i n t , i n t o u t l i n e W i d t h , i n t innerWidth ) {
g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
i n t xPointsOfOutlineReturn [ ] = {
new Double ( f i r s t R e t u r n P o i n t . getX ( ) ) . i n t V a l u e ( ) ,
new Double ( commonReturnPoint . getX ( ) ) . i n t V a l u e ( ) ,
new Double ( s e c o n d R e t u r n P o i n t . getX ( ) ) . i n t V a l u e ( ) } ;
i n t yPointsOfOutlineReturn [ ] = {
new Double ( f i r s t R e t u r n P o i n t . getY ( ) ) . i n t V a l u e ( ) ,
new Double ( commonReturnPoint . getY ( ) ) . i n t V a l u e ( ) ,
APPENDIX B. SOURCE CODE 138

new Double ( s e c o n d R e t u r n P o i n t . getY ( ) ) . i n t V a l u e ( ) } ;


GeneralPath p o l y l i n e O u t l i n e = new GeneralPath ( ) ;
p o l y l i n e O u t l i n e . moveTo ( x P o i n t s O f O u t l i n e R e t u r n [ 0 ] ,
yPointsOfOutlineReturn [ 0 ] ) ;
f o r ( i n t i n d e x = 1 ; i n d e x < x P o i n t s O f O u t l i n e R e t u r n . l e n g t h ; i n d e x++) {
p o l y l i n e O u t l i n e . lineTo ( xPointsOfOutlineReturn [ index ] ,
yPointsOfOutlineReturn [ index ] ) ;
}
g2 . draw ( p o l y l i n e O u t l i n e ) ;
g2 . draw ( new CubicCurve2D . Double ( g e t F i r s t P o i n t ( ) . getX ( ) , g e t F i r s t P o i n t ( )
. getY ( ) , f i r s t C o n t r o l P o i n t . getX ( ) , f i r s t C o n t r o l P o i n t . getY ( ) ,
s e c o n d C o n t r o l P o i n t . getX ( ) , s e c o n d C o n t r o l P o i n t . getY ( ) ,
f i r s t R e t u r n P o i n t . getX ( ) , f i r s t R e t u r n P o i n t . getY ( ) ) ) ;
g2 . draw ( new CubicCurve2D . Double ( g e t S e c o n d P o i n t ( ) . getX ( ) ,
g e t S e c o n d P o i n t ( ) . getY ( ) , t h i r d C o n t r o l P o i n t . getX ( ) ,
t h i r d C o n t r o l P o i n t . getY ( ) , f o r t h C o n t r o l P o i n t . getX ( ) ,
f o r t h C o n t r o l P o i n t . getY ( ) , s e c o n d R e t u r n P o i n t . getX ( ) ,
s e c o n d R e t u r n P o i n t . getY ( ) ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP BUTT,
B a s i c S t r o k e . JOIN MITER) ) ;
i n t xPointsOfInnerReturn [ ] = {
new Double ( f i r s t R e t u r n P o i n t . getX ( ) ) . i n t V a l u e ( ) ,
new Double ( commonReturnPoint . getX ( ) ) . i n t V a l u e ( ) ,
new Double ( s e c o n d R e t u r n P o i n t . getX ( ) ) . i n t V a l u e ( ) } ;
i n t yPointsOfInnerReturn [ ] = {
new Double ( f i r s t R e t u r n P o i n t . getY ( ) ) . i n t V a l u e ( ) ,
new Double ( commonReturnPoint . getY ( ) ) . i n t V a l u e ( ) ,
new Double ( s e c o n d R e t u r n P o i n t . getY ( ) ) . i n t V a l u e ( ) } ;
GeneralPath p o l y l i n e I n n e r = new GeneralPath ( ) ;
p o l y l i n e I n n e r . moveTo ( x P o i n t s O f I n n e r R e t u r n [ 0 ] , y P o i n t s O f I n n e r R e t u r n [ 0 ] ) ;
f o r ( i n t i n d e x = 1 ; i n d e x < x P o i n t s O f I n n e r R e t u r n . l e n g t h ; i n d e x++) {
p o l y l i n e I n n e r . lineTo ( xPointsOfInnerReturn [ index ] ,
yPointsOfInnerReturn [ index ] ) ;
}
g2 . draw ( p o l y l i n e I n n e r ) ;
g2 . draw ( new CubicCurve2D . Double ( g e t F i r s t P o i n t ( ) . getX ( ) , g e t F i r s t P o i n t ( )
. getY ( ) , f i r s t C o n t r o l P o i n t . getX ( ) , f i r s t C o n t r o l P o i n t . getY ( ) ,
s e c o n d C o n t r o l P o i n t . getX ( ) , s e c o n d C o n t r o l P o i n t . getY ( ) ,
f i r s t R e t u r n P o i n t . getX ( ) , f i r s t R e t u r n P o i n t . getY ( ) ) ) ;
g2 . draw ( new CubicCurve2D . Double ( g e t S e c o n d P o i n t ( ) . getX ( ) ,
g e t S e c o n d P o i n t ( ) . getY ( ) , t h i r d C o n t r o l P o i n t . getX ( ) ,
t h i r d C o n t r o l P o i n t . getY ( ) , f o r t h C o n t r o l P o i n t . getX ( ) ,
f o r t h C o n t r o l P o i n t . getY ( ) , s e c o n d R e t u r n P o i n t . getX ( ) ,
s e c o n d R e t u r n P o i n t . getY ( ) ) ) ;
}

public boolean isFullyConnected ( ) {


boolean isFullyConnected = true ;

i f ( g e t F r o m C r o s s i n g P o i n t ( ) == n u l l | | g e t T o C r o s s i n g P o i n t ( ) == n u l l ) {
isFullyConnected = f a l s e ;
}

return isFullyConnected ;
}
}

B.2.6 File: StraightLine.java


APPENDIX B. SOURCE CODE 139

package knot . s t r u c t u r e ;

import j a v a . awt . B a s i c S t r o k e ;
import j a v a . awt . Graphics2D ;
import j a v a . awt . P a i n t ;
import j a v a . awt . P o i n t ;
import j a v a . awt . geom . Line2D ;

p u b l i c c l a s s S t r a i g h t L i n e e x t e n d s KnotComponent {

public StraightLine ( Crossing fromCrossingPoint , Crossing toCrossingPoint ,


Point f i r s t P o i n t , Point secondPoint ) {
super ( fromCrossingPoint , toCrossingPoint , f i r s t P o i n t , secondPoint ) ;
}

p u b l i c v o i d paintComponent ( Graphics2D g2 , P a i n t o u t l i n e P a i n t ,
P a i n t i n n e r P a i n t , i n t o u t l i n e W i d t h , i n t innerWidth ) {
g2 . s e t P a i n t ( o u t l i n e P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( o u t l i n e W i d t h , B a s i c S t r o k e . CAP SQUARE,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( g e t F i r s t P o i n t ( ) , g e t S e c o n d P o i n t ( ) ) ) ;

g2 . s e t P a i n t ( i n n e r P a i n t ) ;
g2 . s e t S t r o k e ( new B a s i c S t r o k e ( innerWidth , B a s i c S t r o k e . CAP SQUARE,
B a s i c S t r o k e . JOIN MITER) ) ;
g2 . draw ( new Line2D . Double ( g e t F i r s t P o i n t ( ) , g e t S e c o n d P o i n t ( ) ) ) ;
}

public boolean isFullyConnected ( ) {


boolean isFullyConnected = true ;

i f ( g e t F r o m C r o s s i n g P o i n t ( ) == n u l l | | g e t T o C r o s s i n g P o i n t ( ) == n u l l ) {
isFullyConnected = f a l s e ;
}

return isFullyConnected ;
}
}

B.3 Package: knot.generation

B.3.1 File: Generator.java


package knot . g e n e r a t i o n ;

import j a v a . awt . Graphics2D ;


import j a v a . awt . P a i n t ;
import j a v a . awt . P o i n t ;
import j a v a . awt . Polygon ;
import j a v a . awt . geom . Line2D ;
import j a v a . awt . geom . Point2D ;
import java . u t i l . ArrayList ;
import java . u t i l . I t e r a t o r ;

import knot . g u i . GridGraphView ;


import knot . s t r u c t u r e . C e l t i c K n o t ;
import knot . s t r u c t u r e . C r o s s i n g ;
import knot . s t r u c t u r e . CurvedLine ;
APPENDIX B. SOURCE CODE 140

impo rt knot . s t r u c t u r e . KnotComponent ;


import knot . s t r u c t u r e . Po i n t e d R e t u r n ;
import knot . s t r u c t u r e . S t r a i g h t L i n e ;

p u b l i c c l a s s Generator {

private static f i n a l int crossingLength = 10;

private static f i n a l int pointedReturnDistance = 10;

private s t a t i c CelticKnot celticKnot ;

p riva te s t a t i c void calculateCrossingsFromEdgeMidpoints ( ) {


A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;
A r r a y L i s t <Point> e d g e s M i d p o i n t = new A r r a y L i s t <Point >() ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Point s t a r t = e d g e s S t a r t . get ( i ) ;
P o i n t end = edgesEnd . g e t ( i ) ;
i n t x = ( s t a r t . x + end . x ) / 2 ;
i n t y = ( s t a r t . y + end . y ) / 2 ;
P o i n t mid = new P o i n t ( x , y ) ;
e d g e s M i d p o i n t . add ( mid ) ;

i f ( s t a r t . x > end . x && s t a r t . y < end . y ) {


d o u b l e d i f f e r e n c e I n X A x i s V a l u e s = s t a r t . x − end . x ;
d o u b l e d i f f e r e n c e I n Y A x i s V a l u e s = end . y − s t a r t . y ;
double oppositeDividedByAdj = differenceInYAxisValues
/ differenceInXAxisValues ;
d o u b l e t h e t a = Math . atan ( o p p o s i t e D i v i d e d B y A d j ) ;
t h e t a = Math . t o D e g r e e s ( t h e t a ) ;
double d e l t a = theta − 4 5 . 0 ;
c a l c u l a t e C r o s s i n g P o i n t L o c a t i o n s ( mid , d e l t a ) ;
} e l s e i f ( s t a r t . x < end . x && s t a r t . y > end . y ) {
d o u b l e d i f f e r e n c e I n X A x i s V a l u e s = end . x − s t a r t . x ;
d o u b l e d i f f e r e n c e I n Y A x i s V a l u e s = s t a r t . y − end . y ;
double oppositeDividedByAdj = differenceInYAxisValues
/ differenceInXAxisValues ;
d o u b l e t h e t a = Math . atan ( o p p o s i t e D i v i d e d B y A d j ) ;
t h e t a = Math . t o D e g r e e s ( t h e t a ) ;
double d e l t a = theta − 4 5 . 0 ;
c a l c u l a t e C r o s s i n g P o i n t L o c a t i o n s ( mid , d e l t a ) ;
} e l s e i f ( s t a r t . x < end . x && s t a r t . y < end . y ) {
d o u b l e d i f f e r e n c e I n X A x i s V a l u e s = end . x − s t a r t . x ;
d o u b l e d i f f e r e n c e I n Y A x i s V a l u e s = end . y − s t a r t . y ;
double oppositeDividedByAdj = differenceInYAxisValues
/ differenceInXAxisValues ;
d o u b l e t h e t a = Math . atan ( o p p o s i t e D i v i d e d B y A d j ) ;
t h e t a = Math . t o D e g r e e s ( t h e t a ) ;
double d e l t a = 45.0 − theta ;
c a l c u l a t e C r o s s i n g P o i n t L o c a t i o n s ( mid , d e l t a ) ;
} e l s e i f ( s t a r t . x > end . x && s t a r t . y > end . y ) {
d o u b l e d i f f e r e n c e I n X A x i s V a l u e s = end . x − s t a r t . x ;
d o u b l e d i f f e r e n c e I n Y A x i s V a l u e s = end . y − s t a r t . y ;
double oppositeDividedByAdj = differenceInYAxisValues
/ differenceInXAxisValues ;
d o u b l e t h e t a = Math . atan ( o p p o s i t e D i v i d e d B y A d j ) ;
t h e t a = Math . t o D e g r e e s ( t h e t a ) ;
double d e l t a = 45.0 − theta ;
APPENDIX B. SOURCE CODE 141

c a l c u l a t e C r o s s i n g P o i n t L o c a t i o n s ( mid , d e l t a ) ;
} e l s e i f ( s t a r t . x == end . x | | s t a r t . y == end . y ) {
c a l c u l a t e C r o s s i n g P o i n t S p e c i a l C a s e T w o ( mid ) ;
}
}

GridGraphView . s e t E d g e s M i d p o i n t ( e d g e s M i d p o i n t ) ;
}

p r i v a t e s t a t i c v o i d c a l c u l a t e C r o s s i n g P o i n t L o c a t i o n s ( P o i n t mid , d o u b l e d e l t a ) {
i f ( d e l t a == 0 . 0 ) {
c a l c u l a t e C r o s s i n g P o i n t S p e c i a l C a s e C r o s s i n g L i n e s P a r a l l e l ( mid ) ;
return ;
}

d o u b l e l e n g t h 2 = c r o s s i n g L e n g t h / Math . c o s ( Math . t o R a d i a n s ( d e l t a ) ) ;
d o u b l e l e n g t h 3 = l e n g t h 2 ∗ Math . s i n ( Math . t o R a d i a n s ( d e l t a ) ) ;

Point2D . Double c r o s s i n g P o i n t 1 = new Point2D . Double ( mid . x + l e n g t h 2 ,


mid . y − l e n g t h 3 ) ;
Point2D . Double c r o s s i n g P o i n t 2 = new Point2D . Double ( mid . x − l e n g t h 2 ,
mid . y + l e n g t h 3 ) ;
Point2D . Double c r o s s i n g P o i n t 3 = new Point2D . Double ( mid . x + l e n g t h 3 ,
mid . y + l e n g t h 2 ) ;
Point2D . Double c r o s s i n g P o i n t 4 = new Point2D . Double ( mid . x − l e n g t h 3 ,
mid . y − l e n g t h 2 ) ;

addCrossingPointsToArrays ( crossingPoint1 , crossingPoint2 ,


crossingPoint3 , crossingPoint4 ) ;
}

p riva te s t a t i c void c a l c u l a t e C r o s s i n g P o i n t S p e c i a l C a s e C r o s s i n g L i n e s P a r a l l e l (
P o i n t mid ) {
Point2D . Double c r o s s i n g P o i n t 1 = new Point2D . Double ( mid . x
+ c r o s s i n g L e n g t h , mid . y ) ;
Point2D . Double c r o s s i n g P o i n t 2 = new Point2D . Double ( mid . x
− c r o s s i n g L e n g t h , mid . y ) ;
Point2D . Double c r o s s i n g P o i n t 3 = new Point2D . Double ( mid . x , mid . y
+ crossingLength ) ;
Point2D . Double c r o s s i n g P o i n t 4 = new Point2D . Double ( mid . x , mid . y
− crossingLength ) ;

addCrossingPointsToArrays ( crossingPoint1 , crossingPoint2 ,


crossingPoint3 , crossingPoint4 ) ;
}

p r i v a t e s t a t i c v o i d c a l c u l a t e C r o s s i n g P o i n t S p e c i a l C a s e T w o ( P o i n t mid ) {
d o u b l e l e n g t h = Math . s q r t ( ( ( c r o s s i n g L e n g t h ∗ c r o s s i n g L e n g t h ) / 2 ) ) ;

Point2D . Double c r o s s i n g P o i n t 1 = new Point2D . Double ( mid . x + l e n g t h ,


mid . y − l e n g t h ) ;
Point2D . Double c r o s s i n g P o i n t 2 = new Point2D . Double ( mid . x − l e n g t h ,
mid . y + l e n g t h ) ;
Point2D . Double c r o s s i n g P o i n t 3 = new Point2D . Double ( mid . x + l e n g t h ,
mid . y + l e n g t h ) ;
Point2D . Double c r o s s i n g P o i n t 4 = new Point2D . Double ( mid . x − l e n g t h ,
mid . y − l e n g t h ) ;

addCrossingPointsToArrays ( crossingPoint1 , crossingPoint2 ,


APPENDIX B. SOURCE CODE 142

crossingPoint3 , crossingPoint4 ) ;
}

p r i v a t e s t a t i c void addCrossingPointsToArrays (
Point2D . Double c r o s s i n g P o i n t 1 , Point2D . Double c r o s s i n g P o i n t 2 ,
Point2D . Double c r o s s i n g P o i n t 3 , Point2D . Double c r o s s i n g P o i n t 4 ) {
P o i n t c r o s s P o i n t 1 = new P o i n t ( new Double ( Math . f l o o r ( c r o s s i n g P o i n t 1 . x ) )
. i n t V a l u e ( ) , new Double ( Math . f l o o r ( c r o s s i n g P o i n t 1 . y ) )
. intValue () ) ;
P o i n t c r o s s P o i n t 2 = new P o i n t ( new Double ( Math . f l o o r ( c r o s s i n g P o i n t 2 . x ) )
. i n t V a l u e ( ) , new Double ( Math . f l o o r ( c r o s s i n g P o i n t 2 . y ) )
. intValue () ) ;
P o i n t c r o s s P o i n t 3 = new P o i n t ( new Double ( Math . f l o o r ( c r o s s i n g P o i n t 3 . x ) )
. i n t V a l u e ( ) , new Double ( Math . f l o o r ( c r o s s i n g P o i n t 3 . y ) )
. intValue () ) ;
P o i n t c r o s s P o i n t 4 = new P o i n t ( new Double ( Math . f l o o r ( c r o s s i n g P o i n t 4 . x ) )
. i n t V a l u e ( ) , new Double ( Math . f l o o r ( c r o s s i n g P o i n t 4 . y ) )
. intValue () ) ;

C r o s s i n g c r o s s i n g = new C r o s s i n g ( c r o s s P o i n t 1 , c r o s s P o i n t 2 , c r o s s P o i n t 3 ,
crossPoint4 ) ;
c e l t i c K n o t . addKnotComponent ( c r o s s i n g ) ;
}

public s t a t i c void generateKnotLines ( ) {


c e l t i c K n o t = new C e l t i c K n o t ( ) ;
GridGraphView . s e t G e n e r a t i n g K n o t ( t r u e ) ;
calculateCrossingsFromEdgeMidpoints () ;
calcStraightLinesBetweenCrossings () ;
calcCurvedLinesBetweenCrossings () ;
calcReturnSections () ;
}

p u b l i c s t a t i c v o i d createLinksBetweenComponents ( ) {
c e l t i c K n o t . createLinksBetweenComponents ( ) ;
c e l t i c K n o t . calculateOverAndUnderPattern ( ) ;
}

p riva te s t a t i c void calcStraightLinesBetweenCrossings ( ) {


A r r a y L i s t <Line2D . Double> c r o s s i n g L i n e s = c e l t i c K n o t
. getAllCrossingLines () ;
A r r a y L i s t <Point> e d g e s M i d p o i n t = GridGraphView . g e t E d g e s M i d p o i n t ( ) ;
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;

f o r ( i n t i = 0 ; i < c r o s s i n g L i n e s . s i z e ( ) ; i ++) {
A r r a y L i s t <Line2D . Double> c o m p l e t l y O v e r l a p p i n g C r o s s i n g L i n e s = new
A r r a y L i s t <Line2D . Double >() ;
Line2D . Double c u r r e n t C r o s s i n g L i n e = c r o s s i n g L i n e s . g e t ( i ) ;
P o i n t pointOne = new P o i n t ( new Double ( c u r r e n t C r o s s i n g L i n e . getX1 ( ) )
. i n t V a l u e ( ) , new Double ( c u r r e n t C r o s s i n g L i n e . getY1 ( ) )
. intValue () ) ;
P o i n t pointTwo = new P o i n t ( new Double ( c u r r e n t C r o s s i n g L i n e . getX2 ( ) )
. i n t V a l u e ( ) , new Double ( c u r r e n t C r o s s i n g L i n e . getY2 ( ) )
. intValue () ) ;
P o i n t midPoint = new P o i n t ( ( pointOne . x + pointTwo . x ) / 2 ,
( pointOne . y + pointTwo . y ) / 2 ) ;
i n t p o s i t i o n I n A r r a y = findEdgeMidPointFromCrossingMidPoint (
APPENDIX B. SOURCE CODE 143

midPoint , e d g e s M i d p o i n t ) ;
P o i n t edgeVertexOne = e d g e s S t a r t . g e t ( p o s i t i o n I n A r r a y ) ;
P o i n t edgeVertexTwo = edgesEnd . g e t ( p o s i t i o n I n A r r a y ) ;
f o r ( i n t j = 0 ; j < c r o s s i n g L i n e s . s i z e ( ) ; j ++) {
Line2D . Double c r o s s i n g L i n e T o C o m p a r e = c r o s s i n g L i n e s . g e t ( j ) ;
i f ( j != i
&& c u r r e n t C r o s s i n g L i n e . p t L i n e D i s t ( c r o s s i n g Li n e T o C o m p a r e
. getP1 ( ) ) == 0 . 0
&& c u r r e n t C r o s s i n g L i n e . p t L i n e D i s t ( c r o s s i n g Li n e T o C o m p a r e
. getP2 ( ) ) == 0 . 0 ) {
P o i n t crossingLineToComparePointOne = new P o i n t ( new Double (
cr o s s i n g L i n e T o C o m p a r e . getX1 ( ) ) . i n t V a l u e ( ) ,
new Double ( c r o s s i n g L i n e T o C o m p a r e . getY1 ( ) )
. intValue () ) ;
P o i n t crossingLineToComparePointTwo = new P o i n t ( new Double (
cr o s s i n g L i n e T o C o m p a r e . getX2 ( ) ) . i n t V a l u e ( ) ,
new Double ( c r o s s i n g L i n e T o C o m p a r e . getY2 ( ) )
. intValue () ) ;
P o i n t crossingLineToCompareMidPoint = new P o i n t (
( crossingLineToComparePointOne . x +
crossingLineToComparePointTwo . x ) / 2 ,
( crossingLineToComparePointOne . y +
crossingLineToComparePointTwo . y ) / 2 ) ;
i n t crossingLineToComparePositionInArray =
findEdgeMidPointFromCrossingMidPoint (
crossingLineToCompareMidPoint , e d g e s M i d p o i n t ) ;
P o i n t crossingLineToCompareEdgeVertexOne = e d g e s S t a r t
. get ( crossingLineToComparePositionInArray ) ;
P o i n t crossingLineToComparedgeVertexTwo = edgesEnd
. get ( crossingLineToComparePositionInArray ) ;
i f ( edgeVertexOne
. e q u a l s ( crossingLineToCompareEdgeVertexOne )
| | edgeVertexOne
. e q u a l s ( crossingLineToComparedgeVertexTwo )
| | edgeVertexTwo
. e q u a l s ( crossingLineToCompareEdgeVertexOne )
| | edgeVertexTwo
. e q u a l s ( crossingLineToComparedgeVertexTwo ) ) {
completlyOverlappingCrossingLines
. add ( c r o s s i n g L i n e T o C o m p a r e ) ;
}
}
}
int crossingLinesSize = crossingLines . size () ;
crossingLines = calcClosestCrossingLineAndStoreInKnot (
completlyOverlappingCrossingLines , currentCrossingLine ,
crossingLines ) ;
i f ( c r o s s i n g L i n e s S i z e != c r o s s i n g L i n e s . s i z e ( ) ) {
i = −1;
}
}
}

p r i v a t e s t a t i c A r r a y L i s t <Line2D . Double> c a l c C l o s e s t C r o s s i n g L i n e A n d S t o r e I n K n o t (
A r r a y L i s t <Line2D . Double> c o m p l e t l y O v e r l a p p i n g C r o s s i n g L i n e s ,
Line2D . Double l i n e , A r r a y L i s t <Line2D . Double> c r o s s i n g L i n e s ) {
i f ( c o m p l e t l y O v e r l a p p i n g C r o s s i n g L i n e s . s i z e ( ) == 0 ) {
return crossingLines ;
}

Point2D . Double s t a r t D o u b l e = ( Point2D . Double ) l i n e . getP1 ( ) ;


APPENDIX B. SOURCE CODE 144

P o i n t s t a r t = new P o i n t ( new Double ( s t a r t D o u b l e . getX ( ) ) . i n t V a l u e ( ) ,


new Double ( s t a r t D o u b l e . getY ( ) ) . i n t V a l u e ( ) ) ;
Point2D . Double endDouble = ( Point2D . Double ) l i n e . getP2 ( ) ;
P o i n t end = new P o i n t ( new Double ( endDouble . getX ( ) ) . i n t V a l u e ( ) ,
new Double ( endDouble . getY ( ) ) . i n t V a l u e ( ) ) ;

Line2D . Double c l o s e s t T o S t a r t P o i n t = n u l l ;
Line2D . Double c l o s e s t T o E n d P o i n t = n u l l ;

f o r ( i n t i = 0 ; i < c o m p l e t l y O v e r l a p p i n g C r o s s i n g L i n e s . s i z e ( ) ; i ++) {
Line2D . Double temp = c o m p l e t l y O v e r l a p p i n g C r o s s i n g L i n e s . g e t ( i ) ;

i f ( temp . p t S e g D i s t ( s t a r t ) < temp . p t S e g D i s t ( end )


&& ( c l o s e s t T o S t a r t P o i n t == n u l l | | temp . p t S e g D i s t ( s t a r t ) <
closestToStartPoint
. ptSegDist ( s t a r t ) ) ) {
i f ( l i n e . p t S e g D i s t ( temp . getP1 ( ) ) < l i n e . p t S e g D i s t ( temp . getP2 ( ) )
&& c a n P o i n t s B e C o n n e c t e d W i t h O u t I n t e r s e c t i n g E x i s t i n g E d g e (
s t a r t , temp . getP1 ( ) ) ) {
c l o s e s t T o S t a r t P o i n t = new Line2D . Double ( s t a r t , temp . getP1 ( ) ) ;
} e l s e i f ( l i n e . p t S e g D i s t ( temp . getP1 ( ) ) > l i n e . p t S e g D i s t ( temp
. getP2 ( ) )
&& c a n P o i n t s B e C o n n e c t e d W i t h O u t I n t e r s e c t i n g E x i s t i n g E d g e (
s t a r t , temp . getP2 ( ) ) ) {
c l o s e s t T o S t a r t P o i n t = new Line2D . Double ( s t a r t , temp . getP2 ( ) ) ;
}
} e l s e i f ( temp . p t S e g D i s t ( s t a r t ) > temp . p t S e g D i s t ( end )
&& ( c l o s e s t T o E n d P o i n t == n u l l | | temp . p t S e g D i s t ( end ) <
closestToEndPoint
. p t S e g D i s t ( end ) ) ) {
i f ( l i n e . p t S e g D i s t ( temp . getP1 ( ) ) < l i n e . p t S e g D i s t ( temp . getP2 ( ) )
&& c a n P o i n t s B e C o n n e c t e d W i t h O u t I n t e r s e c t i n g E x i s t i n g E d g e (
end , temp . getP1 ( ) ) ) {
c l o s e s t T o E n d P o i n t = new Line2D . Double ( end , temp . getP1 ( ) ) ;
} e l s e i f ( l i n e . p t S e g D i s t ( temp . getP1 ( ) ) > l i n e . p t S e g D i s t ( temp
. getP2 ( ) )
&& c a n P o i n t s B e C o n n e c t e d W i t h O u t I n t e r s e c t i n g E x i s t i n g E d g e (
end , temp . getP2 ( ) ) ) {
c l o s e s t T o E n d P o i n t = new Line2D . Double ( end , temp . getP2 ( ) ) ;
}
}
}

i f ( c l o s e s t T o S t a r t P o i n t != n u l l ) {
P o i n t l i n e P o i n t O n e = new P o i n t ( new Double ( c l o s e s t T o S t a r t P o i n t
. getX1 ( ) ) . i n t V a l u e ( ) , new Double ( c l o s e s t T o S t a r t P o i n t
. getY1 ( ) ) . i n t V a l u e ( ) ) ;
P o i n t l i n e P o i n t T w o = new P o i n t ( new Double ( c l o s e s t T o S t a r t P o i n t
. getX2 ( ) ) . i n t V a l u e ( ) , new Double ( c l o s e s t T o S t a r t P o i n t
. getY2 ( ) ) . i n t V a l u e ( ) ) ;
S t r a i g h t L i n e s t r a i g h t L i n e O n e = new S t r a i g h t L i n e ( n u l l , n u l l ,
linePointOne , linePointTwo ) ;
c e l t i c K n o t . addKnotComponent ( s t r a i g h t L i n e O n e ) ;
}
i f ( c l o s e s t T o E n d P o i n t != n u l l ) {
P o i n t l i n e P o i n t O n e = new P o i n t (
new Double ( c l o s e s t T o E n d P o i n t . getX1 ( ) ) . i n t V a l u e ( ) ,
new Double ( c l o s e s t T o E n d P o i n t . getY1 ( ) ) . i n t V a l u e ( ) ) ;
P o i n t l i n e P o i n t T w o = new P o i n t (
new Double ( c l o s e s t T o E n d P o i n t . getX2 ( ) ) . i n t V a l u e ( ) ,
new Double ( c l o s e s t T o E n d P o i n t . getY2 ( ) ) . i n t V a l u e ( ) ) ;
S t r a i g h t L i n e s t r a i g h t L i n e T w o = new S t r a i g h t L i n e ( n u l l , n u l l ,
APPENDIX B. SOURCE CODE 145

linePointOne , linePointTwo ) ;
c e l t i c K n o t . addKnotComponent ( s t r a i g h t L i n e T w o ) ;
}

f o r ( i n t i = 0 ; i < c r o s s i n g L i n e s . s i z e ( ) ; i ++) {
Line2D . Double c r o s s i n g L i n e = c r o s s i n g L i n e s . g e t ( i ) ;
i f ( crossingLine . equals ( closestToStartPoint )
| | c r o s s i n g L i n e . equals ( closestToEndPoint )
| | crossingLine . equals ( l i n e ) ) {
c r o s s i n g L i n e s . remove ( i ) ;
i −−;
}
}

return crossingLines ;
}

p riva te s t a t i c void calcCurvedLinesBetweenCrossings ( ) {


A r r a y L i s t <Point> e d g e s M i d p o i n t = GridGraphView . g e t E d g e s M i d p o i n t ( ) ;
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;

A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s = c e l t i c K n o t
. getUnconnectedCrossingPointsInLineForm ( ) ;

f o r ( i n t i = 0 ; i < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; i ++) {
Line2D . Double c r o s s i n g L i n e = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( i ) ;
P o i n t u n c o n n e c t e d C r o s s i n g P o i n t = new P o i n t ( new Double ( c r o s s i n g L i n e
. getX1 ( ) ) . i n t V a l u e ( ) , new Double ( c r o s s i n g L i n e . getY1 ( ) )
. intValue () ) ;
P o i n t o t h e r P o i n t O n L i n e = new P o i n t ( new Double ( c r o s s i n g L i n e . getX2 ( ) )
. i n t V a l u e ( ) , new Double ( c r o s s i n g L i n e . getY2 ( ) ) . i n t V a l u e ( ) ) ;
P o i n t midPoint = new P o i n t (
( unconnectedCrossingPoint . x + otherPointOnLine . x ) / 2 ,
( unconnectedCrossingPoint . y + otherPointOnLine . y ) / 2) ;
i n t p o s i t i o n I n A r r a y = findEdgeMidPointFromCrossingMidPoint (
midPoint , e d g e s M i d p o i n t ) ;
P o i n t edgeVertexOne = e d g e s S t a r t . g e t ( p o s i t i o n I n A r r a y ) ;
P o i n t edgeVertexTwo = edgesEnd . g e t ( p o s i t i o n I n A r r a y ) ;
Line2D . Double c l o s e s t E d g e = n u l l ;
Point c l o s e s t V e r t e x = n u l l ;
i f ( u n c o n n e c t e d C r o s s i n g P o i n t . d i s t a n c e ( edgeVertexOne ) <
unconnectedCrossingPoint
. d i s t a n c e ( edgeVertexTwo ) ) {
c l o s e s t V e r t e x = edgeVertexOne ;
closestEdge = getClosestEdgeToPointOnCrossing (
u n c o n n e c t e d C r o s s i n g P o i n t , edgeVertexOne , edgeVertexTwo ) ;
} else {
c l o s e s t V e r t e x = edgeVertexTwo ;
closestEdge = getClosestEdgeToPointOnCrossing (
u n c o n n e c t e d C r o s s i n g P o i n t , edgeVertexTwo , edgeVertexOne ) ;
}

i f ( c l o s e s t E d g e == n u l l ) {
continue ;
}

Point closestPointToUnconnectedCrossingPoint = n u l l ;

i f ( c l o s e s t E d g e . p t L i n e D i s t ( edgeVertexOne ) == 0 . 0
&& c l o s e s t E d g e . p t L i n e D i s t ( edgeVertexTwo ) == 0 . 0 ) {
APPENDIX B. SOURCE CODE 146

A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s O n C l o s e s t E d g e =
findUnconnectedCrossingPointsOnEdge (
u n c o n n e c t e d C r o s s i n g P o i n t s , new P o i n t ( new Double (
c l o s e s t E d g e . getX1 ( ) ) . i n t V a l u e ( ) , new Double (
c l o s e s t E d g e . getY1 ( ) ) . i n t V a l u e ( ) ) , new P o i n t (
new Double ( c l o s e s t E d g e . getX2 ( ) ) . i n t V a l u e ( ) ,
new Double ( c l o s e s t E d g e . getY2 ( ) ) . i n t V a l u e ( ) ) ) ;
closestPointToUnconnectedCrossingPoint =
findUnconnectedCrossingPointClosestToPoint (
unconnectedCrossingPointsOnClosestEdge ,
unconnectedCrossingPoint ) ;
} else {
Polygon s e a r c h A r e a = c a l c u l a t e S e a c h A r e a F r o m U n c o n n e c t e d C r o s s i n g P o i n t (
unconnectedCrossingPoint , closestEdge ) ;
closestPointToUnconnectedCrossingPoint =
getClosestPointToUnconnectedPointInSearchArea (
unconnectedCrossingPoints , searchArea ,
unconnectedCrossingPoint , c l o s e s t V e r t e x ) ;
}

i f ( c l o s e s t P o i n t T o U n c o n n e c t e d C r o s s i n g P o i n t == n u l l ) {
continue ;
}

Line2D . Double o t h e r C r o s s i n g L i n e =
findCrossingLineInUnconnectedCrossingsFromCrossingPoint (
unconnectedCrossingPoints ,
closestPointToUnconnectedCrossingPoint ) ;
calculateCurveBetweenTwoCrossingPoints ( unconnectedCrossingPoint ,
closestPointToUnconnectedCrossingPoint , crossingLine ,
otherCrossingLine ) ;

unconnectedCrossingPoints =
removeConnectingPairFromUnconnectedCrossingPoints (
unconnectedCrossingPoints , unconnectedCrossingPoint ,
closestPointToUnconnectedCrossingPoint ) ;
i = −1;
}
}

p r i v a t e s t a t i c A r r a y L i s t <Line2D . Double>
removeConnectingPairFromUnconnectedCrossingPoints (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s ,
Point unconnectedCrossingPoint ,
Point closestPointToUnconnectedCrossingPoint ) {
f o r ( i n t i = 0 ; i < 2 ; i ++) {
f o r ( i n t j = 0 ; j < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; j ++) {
Line2D . Double temp = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( j ) ;
i f ( temp . getP1 ( ) . e q u a l s ( u n c o n n e c t e d C r o s s i n g P o i n t )
| | temp . getP1 ( ) . e q u a l s (
closestPointToUnconnectedCrossingPoint ) ) {
temp = n u l l ;
u n c o n n e c t e d C r o s s i n g P o i n t s . remove ( j ) ;
break ;
}
}
}
return unconnectedCrossingPoints ;
}
APPENDIX B. SOURCE CODE 147

p r i v a t e s t a t i c i n t findEdgeMidPointFromCrossingMidPoint (
P o i n t m i d P o i n t O f C r o s s i n g , A r r a y L i s t <Point> e d g e s M i d p o i n t ) {
i n t p o s i t i o n I n A r r a y = −1;

i f ( e d g e s M i d p o i n t . i nd ex O f ( m i d P o i n t O f C r o s s i n g ) != −1) {
p o s i t i o n I n A r r a y = edgesMidpoint . indexOf ( midPointOfCrossing ) ;
} else {
Point c urre ntNea rest = edgesMidpoint . get ( 0 ) ;
f o r ( i n t i = 1 ; i < e d g e s M i d p o i n t . s i z e ( ) ; i ++) {
i f ( edgesMidpoint . get ( i ) . d i s t a n c e ( midPointOfCrossing ) <
currentNearest
. d i s t a n c e ( midPointOfCrossing ) ) {
currentNearest = edgesMidpoint . get ( i ) ;
}
}

p o s i t i o n I n A r r a y = e d g e s M i d p o i n t . i nd e x O f ( c u r r e n t N e a r e s t ) ;
}

return positionInArray ;
}

p r i v a t e s t a t i c void calculateCurveBetweenTwoCrossingPoints ( Point s t a r t ,


P o i n t end , Line2D . Double c r o s s i n g L i n e O n e ,
Line2D . Double c r o s s i n g L i n e T w o ) {
A r r a y L i s t <Point2D . Double> c o n t r o l P o i n t s = c a l c u l a t e C o n t r o l P o i n t s F o r C u r v e (
c r o s s i n g L i n e O n e , c r o s s i n g L i n e T w o , s t a r t , end ) ;
CurvedLine c u r v e = n u l l ;
i f ( c o n t r o l P o i n t s . s i z e ( ) == 1 ) {
Point2D . Double c o n t r o l P o i n t = c o n t r o l P o i n t s . g e t ( 0 ) ;
c u r v e = new CurvedLine ( n u l l , n u l l , s t a r t , end , c o n t r o l P o i n t ,
controlPoint ) ;
} else {
Point2D . Double c o n t r o l P o i n t O n e = c o n t r o l P o i n t s . g e t ( 0 ) ;
Point2D . Double c o n t r o l P o i n t T w o = c o n t r o l P o i n t s . g e t ( 1 ) ;
c u r v e = new CurvedLine ( n u l l , n u l l , s t a r t , end , c o n t r o l P o i n t O n e ,
controlPointTwo ) ;
}
c e l t i c K n o t . addKnotComponent ( c u r v e ) ;
}

p r i v a t e s t a t i c Point getClosestPointToUnconnectedPointInSearchArea (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s ,
Polygon s e a r c h A r e a , P o i n t u n c o n n e c t e d C r o s s i n g P o i n t ,
Point closestVertexToUnconnectedCrossingPoint ) {
A r r a y L i s t <Point> p o s s i b l e P o i n t s T o C o n n e c t T o = new A r r a y L i s t <Point >() ;
f o r ( i n t j = 0 ; j < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; j ++) {
Line2D . Double t e m p C r o s s i n g L i n e = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( j ) ;
P o i n t t e m p C r o s s i n g P o i n t = new P o i n t ( new Double ( t e m p C r o s s i n g L i n e
. getX1 ( ) ) . i n t V a l u e ( ) , new Double ( t e m p C r o s s i n g L i n e . getY1 ( ) )
. intValue () ) ;
i f ( searchArea . contains ( tempCrossingPoint ) ) {
p o s s i b l e P o i n t s T o C o n n e c t T o . add ( t e m p C r o s s i n g P o i n t ) ;
}
}

p o s s i b l e P o i n t s T o C o n n e c t T o . remove ( u n c o n n e c t e d C r o s s i n g P o i n t ) ;

Point closestPointToUnconnectedCrossingPoint = n u l l ;
f o r ( i n t k = 0 ; k < p o s s i b l e P o i n t s T o C o n n e c t T o . s i z e ( ) ; k++) {
APPENDIX B. SOURCE CODE 148

P o i n t pointToCompare = p o s s i b l e P o i n t s T o C o n n e c t T o . g e t ( k ) ;
i f ( c l o s e s t P o i n t T o U n c o n n e c t e d C r o s s i n g P o i n t == n u l l
&& c a n P o i n t s B e C o n n e c t e d W i t h O u t I n t e r s e c t i n g E x i s t i n g E d g e (
pointToCompare , u n c o n n e c t e d C r o s s i n g P o i n t ) ) {
c l o s e s t P o i n t T o U n c o n n e c t e d C r o s s i n g P o i n t = pointToCompare ;
} e l s e i f ( c l o s e s t P o i n t T o U n c o n n e c t e d C r o s s i n g P o i n t != n u l l
&& pointToCompare
. distance ( closestVertexToUnconnectedCrossingPoint ) <
closestPointToUnconnectedCrossingPoint
. distance ( closestVertexToUnconnectedCrossingPoint )
&& c a n P o i n t s B e C o n n e c t e d W i t h O u t I n t e r s e c t i n g E x i s t i n g E d g e (
pointToCompare , u n c o n n e c t e d C r o s s i n g P o i n t ) ) {
c l o s e s t P o i n t T o U n c o n n e c t e d C r o s s i n g P o i n t = pointToCompare ;
}
}
return closestPointToUnconnectedCrossingPoint ;
}

private s t a t i c boolean canPointsBeConnectedWithOutIntersectingExistingEdge (


Point2D f i r s t P o i n t , Point2D s e c o n d P o i n t ) {
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;

return canPointsBeConnectedWithOutIntersectingExistingGivenEdges (
f i r s t P o i n t , s e c o n d P o i n t , e d g e s S t a r t , edgesEnd ) ;
}

private s t a t i c boolean
canPointsBeConnectedWithOutIntersectingExistingEdgeMinusCurrentEdge (
Point2D f i r s t P o i n t , Point2D s e c o n d P o i n t , Line2D . Double edge ) {
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;

A r r a y L i s t <Point> edgesStartToBeUsed = new A r r a y L i s t <Point >() ;


A r r a y L i s t <Point> edgesEndToBeUsed = new A r r a y L i s t <Point >() ;

P o i n t vertexOne = new P o i n t ( new Double ( edge . getX1 ( ) ) . i n t V a l u e ( ) ,


new Double ( edge . getY1 ( ) ) . i n t V a l u e ( ) ) ;
P o i n t vertexTwo = new P o i n t ( new Double ( edge . getX2 ( ) ) . i n t V a l u e ( ) ,
new Double ( edge . getY2 ( ) ) . i n t V a l u e ( ) ) ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
i f ( ! ( e d g e s S t a r t . g e t ( i ) . e q u a l s ( vertexOne ) && edgesEnd . g e t ( i )
. e q u a l s ( vertexTwo ) )
&& ! ( edgesEnd . g e t ( i ) . e q u a l s ( vertexOne ) && e d g e s S t a r t . g e t ( i )
. e q u a l s ( vertexTwo ) ) ) {
edgesStartToBeUsed . add ( e d g e s S t a r t . g e t ( i ) ) ;
edgesEndToBeUsed . add ( edgesEnd . g e t ( i ) ) ;
}
}

return canPointsBeConnectedWithOutIntersectingExistingGivenEdges (
f i r s t P o i n t , s e c o n d P o i n t , edgesStartToBeUsed , edgesEndToBeUsed ) ;
}

private s t a t i c boolean
canPointsBeConnectedWithOutIntersectingExistingGivenEdges (
Point2D f i r s t P o i n t , Point2D s e c o n d P o i n t ,
A r r a y L i s t <Point> e d g e s S t a r t , A r r a y L i s t <Point> edgesEnd ) {
APPENDIX B. SOURCE CODE 149

boolean i n t e r s e c t s = f a l s e ;
Line2D . Double p r o p o s e d L i n e = new Line2D . Double ( f i r s t P o i n t , s e c o n d P o i n t ) ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Line2D . Double edge = new Line2D . Double ( e d g e s S t a r t . g e t ( i ) , edgesEnd
. get ( i ) ) ;
i f ( p r o p o s e d L i n e . i n t e r s e c t s L i n e ( edge )
&& ! p r o p o s e d L i n e . getP1 ( ) . e q u a l s ( edge . getP1 ( ) )
&& ! p r o p o s e d L i n e . getP1 ( ) . e q u a l s ( edge . getP2 ( ) )
&& ! p r o p o s e d L i n e . getP2 ( ) . e q u a l s ( edge . getP1 ( ) )
&& ! p r o p o s e d L i n e . getP2 ( ) . e q u a l s ( edge . getP2 ( ) ) ) {
i n t e r s e c t s = true ;
break ;
}
}

return ! i n t e r s e c t s ;
}

p r i v a t e s t a t i c Polygon c a l c u l a t e S e a c h A r e a F r o m U n c o n n e c t e d C r o s s i n g P o i n t (
P o i n t u n c o n n e c t e d C r o s s i n g P o i n t , Line2D . Double c l o s e s t E d g e ) {
i n t [ ] x P o i n t s = new i n t [ 3 ] ;
i n t [ ] y P o i n t s = new i n t [ 3 ] ;
xPoints [ 0 ] = unconnectedCrossingPoint . x ;
yPoints [ 0 ] = unconnectedCrossingPoint . y ;
x P o i n t s [ 1 ] = new Double ( c l o s e s t E d g e . getX1 ( ) ) . i n t V a l u e ( ) ;
y P o i n t s [ 1 ] = new Double ( c l o s e s t E d g e . getY1 ( ) ) . i n t V a l u e ( ) ;
x P o i n t s [ 2 ] = new Double ( c l o s e s t E d g e . getX2 ( ) ) . i n t V a l u e ( ) ;
y P o i n t s [ 2 ] = new Double ( c l o s e s t E d g e . getY2 ( ) ) . i n t V a l u e ( ) ;
Polygon s e a r c h A r e a = new Polygon ( x P o i n t s , y P o i n t s , 3 ) ;

return searchArea ;
}

p r i v a t e s t a t i c A r r a y L i s t <Line2D . Double> g e t A l l E d g e s U s i n g V e r t e x ( P o i n t v e r t e x ) {
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;
A r r a y L i s t <Line2D . Double> e d g e s = new A r r a y L i s t <Line2D . Double >() ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
Point s t a r t = e d g e s S t a r t . get ( i ) ;
P o i n t end = edgesEnd . g e t ( i ) ;
i f ( v e r t e x . e q u a l s ( s t a r t ) | | v e r t e x . e q u a l s ( end ) ) {
Line2D . Double edge = new Line2D . Double ( s t a r t , end ) ;
e d g e s . add ( edge ) ;
}
}

return edges ;
}

p r i v a t e s t a t i c A r r a y L i s t <Line2D . Double> g e t A l l E d g e s U s i n g V e r t e x M i n u s C u r r e n t E d g e (
P o i n t s t a r t V e r t e x , P o i n t endVertex ) {
A r r a y L i s t <Line2D . Double> e d g e s = g e t A l l E d g e s U s i n g V e r t e x ( s t a r t V e r t e x ) ;

f o r ( i n t i = 0 ; i < e d g e s . s i z e ( ) ; i ++) {
Line2D . Double edge = e d g e s . g e t ( i ) ;
P o i n t s t a r t = new P o i n t ( new Double ( edge . getX1 ( ) ) . i n t V a l u e ( ) ,
new Double ( edge . getY1 ( ) ) . i n t V a l u e ( ) ) ;
APPENDIX B. SOURCE CODE 150

P o i n t end = new P o i n t ( new Double ( edge . getX2 ( ) ) . i n t V a l u e ( ) ,


new Double ( edge . getY2 ( ) ) . i n t V a l u e ( ) ) ;
i f ( endVertex . e q u a l s ( s t a r t ) | | endVertex . e q u a l s ( end ) ) {
e d g e s . remove ( i ) ;
break ;
}
}

return edges ;
}

p r i v a t e s t a t i c Line2D . Double g e t C l o s e s t E d g e T o P o i n t O n C r o s s i n g (
Point pointOnCrossing , Point closestEdgeVertex ,
Point otherEdgeVertex ) {
A r r a y L i s t <Line2D . Double> e d g e s = g e t A l l E d g e s U s i n g V e r t e x M i n u s C u r r e n t E d g e (
closestEdgeVertex , otherEdgeVertex ) ;

Line2D . Double c l o s e s t E d g e = n u l l ;
f o r ( i n t i = 0 ; i < e d g e s . s i z e ( ) ; i ++) {
Line2D . Double edge = e d g e s . g e t ( i ) ;
Point f u r t h e s t V e r t e x = n u l l ;
i f ( p o i n t O n C r o s s i n g . d i s t a n c e ( edge . getP1 ( ) ) < p o i n t O n C r o s s i n g
. d i s t a n c e ( edge . getP2 ( ) ) ) {
f u r t h e s t V e r t e x = new P o i n t ( new Double ( edge . getX2 ( ) ) . i n t V a l u e ( ) ,
new Double ( edge . getY2 ( ) ) . i n t V a l u e ( ) ) ;
} else {
f u r t h e s t V e r t e x = new P o i n t ( new Double ( edge . getX1 ( ) ) . i n t V a l u e ( ) ,
new Double ( edge . getY1 ( ) ) . i n t V a l u e ( ) ) ;
}

i f ( canPointsBeConnectedWithOutIntersectingExistingEdgeMinusCurrentEdge (
p o i n t O n C r o s s i n g , f u r t h e s t V e r t e x , edge ) == f a l s e ) {
continue ;
}

i f ( c l o s e s t E d g e == n u l l ) {
c l o s e s t E d g e = edge ;
} e l s e i f ( edge . p t L i n e D i s t ( p o i n t O n C r o s s i n g ) < c l o s e s t E d g e
. ptLineDist ( pointOnCrossing ) ) {
c l o s e s t E d g e = edge ;
}
}

return closestEdge ;
}

p riva te s t a t i c void calcReturnSections ( ) {


A r r a y L i s t <Point> e d g e s M i d p o i n t = GridGraphView . g e t E d g e s M i d p o i n t ( ) ;
A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;

A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s = c e l t i c K n o t
. getUnconnectedCrossingPointsInLineForm ( ) ;
f o r ( i n t i = 0 ; i < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; i ++) {
Line2D . Double c r o s s i n g L i n e = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( i ) ;
P o i n t u n c o n n e c t e d C r o s s i n g P o i n t = new P o i n t ( new Double ( c r o s s i n g L i n e
. getX1 ( ) ) . i n t V a l u e ( ) , new Double ( c r o s s i n g L i n e . getY1 ( ) )
. intValue () ) ;
P o i n t o t h e r P o i n t O n L i n e = new P o i n t ( new Double ( c r o s s i n g L i n e . getX2 ( ) )
. i n t V a l u e ( ) , new Double ( c r o s s i n g L i n e . getY2 ( ) ) . i n t V a l u e ( ) ) ;
APPENDIX B. SOURCE CODE 151

P o i n t midPoint = new P o i n t (
( unconnectedCrossingPoint . x + otherPointOnLine . x ) / 2 ,
( unconnectedCrossingPoint . y + otherPointOnLine . y ) / 2) ;
i n t p o s i t i o n I n A r r a y = findEdgeMidPointFromCrossingMidPoint (
midPoint , e d g e s M i d p o i n t ) ;
P o i n t edgeVertexOne = e d g e s S t a r t . g e t ( p o s i t i o n I n A r r a y ) ;
P o i n t edgeVertexTwo = edgesEnd . g e t ( p o s i t i o n I n A r r a y ) ;
Point crossingPointToConnectTo = n u l l ;
i f ( u n c o n n e c t e d C r o s s i n g P o i n t . d i s t a n c e ( edgeVertexOne ) <
unconnectedCrossingPoint
. d i s t a n c e ( edgeVertexTwo ) ) {
crossingPointToConnectTo = findOtherCrossingPointForReturn (
unconnectedCrossingPoints , unconnectedCrossingPoint ,
edgeVertexOne , edgeVertexTwo ) ;
i f ( c r o s s i n g P o i n t T o C o n n e c t T o == n u l l ) {
continue ;
}
Line2D . Double o t h e r C r o s s i n g L i n e =
findCrossingLineInUnconnectedCrossingsFromCrossingPoint (
unconnectedCrossingPoints , crossingPointToConnectTo ) ;
i n t positionInArrayOfOtherEdge =
findEdgeMidPointFromCrossingMidPoint (
new P o i n t (
new Double (
Math
. f l o o r (( otherCrossingLine
. getX1 ( ) + o t h e r C r o s s i n g L i n e
. getX1 ( ) ) / 2 ) )
. intValue () ,
new Double (
Math
. f l o o r (( otherCrossingLine
. getY1 ( ) + o t h e r C r o s s i n g L i n e
. getY1 ( ) ) / 2 ) )
. intValue ( ) ) , edgesMidpoint ) ;
P o i n t otherEdgeVertexOne = e d g e s S t a r t
. get ( positionInArrayOfOtherEdge ) ;
P o i n t otherEdgeVertexTwo = edgesEnd
. get ( positionInArrayOfOtherEdge ) ;
P o i n t otherEdgeVertexToUse = n u l l ;
i f ( edgeVertexOne . e q u a l s ( otherEdgeVertexOne ) ) {
otherEdgeVertexToUse = otherEdgeVertexTwo ;
} else {
otherEdgeVertexToUse = otherEdgeVertexOne ;
}
calculateReturnPartPoint ( unconnectedCrossingPoint ,
c r o s s i n g P o i n t T o C o n n e c t T o , edgeVertexTwo , edgeVertexOne ,
otherEdgeVertexToUse , c r o s s i n g L i n e , o t h e r C r o s s i n g L i n e ) ;
} else {
crossingPointToConnectTo = findOtherCrossingPointForReturn (
unconnectedCrossingPoints , unconnectedCrossingPoint ,
edgeVertexTwo , edgeVertexOne ) ;
i f ( c r o s s i n g P o i n t T o C o n n e c t T o == n u l l ) {
continue ;
}
Line2D . Double o t h e r C r o s s i n g L i n e =
findCrossingLineInUnconnectedCrossingsFromCrossingPoint (
unconnectedCrossingPoints , crossingPointToConnectTo ) ;
i n t positionInArrayOfOtherEdge =
findEdgeMidPointFromCrossingMidPoint (
new P o i n t (
new Double (
APPENDIX B. SOURCE CODE 152

Math
. f l o o r (( otherCrossingLine
. getX1 ( ) + o t h e r C r o s s i n g L i n e
. getX1 ( ) ) / 2 ) )
. intValue () ,
new Double (
Math
. f l o o r (( otherCrossingLine
. getY1 ( ) + o t h e r C r o s s i n g L i n e
. getY1 ( ) ) / 2 ) )
. intValue ( ) ) , edgesMidpoint ) ;
P o i n t otherEdgeVertexOne = e d g e s S t a r t
. get ( positionInArrayOfOtherEdge ) ;
P o i n t otherEdgeVertexTwo = edgesEnd
. get ( positionInArrayOfOtherEdge ) ;
P o i n t otherEdgeVertexToUse = n u l l ;
i f ( edgeVertexTwo . e q u a l s ( otherEdgeVertexOne ) ) {
otherEdgeVertexToUse = otherEdgeVertexTwo ;
} else {
otherEdgeVertexToUse = otherEdgeVertexOne ;
}
calculateReturnPartPoint ( unconnectedCrossingPoint ,
c r o s s i n g P o i n t T o C o n n e c t T o , edgeVertexOne , edgeVertexTwo ,
otherEdgeVertexToUse , c r o s s i n g L i n e , o t h e r C r o s s i n g L i n e ) ;
}

unconnectedCrossingPoints =
removeConnectingPairFromUnconnectedCrossingPoints (
unconnectedCrossingPoints , unconnectedCrossingPoint ,
crossingPointToConnectTo ) ;
i = −1;
}
}

p r i v a t e s t a t i c i n t numberOfEdgesInvolvingVertex ( Point v e r t e x ) {
i n t count = 0 ;

A r r a y L i s t <Point> e d g e s S t a r t = GridGraphView . g e t E d g e s S t a r t ( ) ;
A r r a y L i s t <Point> edgesEnd = GridGraphView . getEdgesEnd ( ) ;

f o r ( i n t i = 0 ; i < e d g e s S t a r t . s i z e ( ) ; i ++) {
i f ( vertex . equals ( edgesStart . get ( i ) )
| | v e r t e x . e q u a l s ( edgesEnd . g e t ( i ) ) ) {
count++;
}
}

r e t u r n count ;
}

p r i v a t e s t a t i c Point findOtherCrossingPointForReturn (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s ,
Point c r o s s i n g P o i n t , Point closestVertexInEdge ,
Point otherEdgeVertex ) {
P o i n t vertexOfEdgeToConnectTo = g e t O t h e r E d g e V e r t e x F o r R e t u r n C r o s s i n g P o i n t (
closestVertexInEdge , otherEdgeVertex ) ;
Point crossingPointToConnectTo =
findUnconnectedCrossingPointOnEdgeClosestToVertex (
unconnectedCrossingPoints , closestVertexInEdge ,
vertexOfEdgeToConnectTo , c r o s s i n g P o i n t ) ;
APPENDIX B. SOURCE CODE 153

return crossingPointToConnectTo ;
}

p r i v a t e s t a t i c Point findUnconnectedCrossingPointOnEdgeClosestToVertex (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s , P o i n t v e r t e x ,
P o i n t otherEdgeVer t e x , P o i n t c r o s s i n g P o i n t ) {
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e =
findUnconnectedCrossingPointsOnEdgeMinusCrossingPointInUse (
u n c o n n e c t e d C r o s s i n g P o i n t s , v e r t e x , o t he r E dg e V e rt e x ,
crossingPoint ) ;
Point closestCrossingPointToVertex =
findUnconnectedCrossingPointClosestToPoint (
unconnectedCrossingPointsOnThisEdge , v e r t e x ) ;

return closestCrossingPointToVertex ;
}

p r i v a t e s t a t i c Point findUnconnectedCrossingPointClosestToPoint (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s , P o i n t p o i n t ) {
Point cl o se st C ro ss in g Poi n tT o Poi n t = n u l l ;
f o r ( i n t i = 0 ; i < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; i ++) {
Line2D . Double t e m p C r o s s i n g L i n e = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( i ) ;
P o i n t t e m p C r o s s i n g P o i n t = new P o i n t ( new Double ( t e m p C r o s s i n g L i n e
. getX1 ( ) ) . i n t V a l u e ( ) , new Double ( t e m p C r o s s i n g L i n e . getY1 ( ) )
. intValue () ) ;
i f ( c l o s e s t C r o s s i n g P o i n t T o P o i n t == n u l l
| | closestCrossingPointToPoint . distance ( point ) >
tempCrossingPoint
. distance ( point ) ) {
closestCrossingPointToPoint = tempCrossingPoint ;
}
}

return closestCrossingPointToPoint ;
}

p r i v a t e s t a t i c A r r a y L i s t <Line2D . Double>
findUnconnectedCrossingPointsOnEdgeMinusCrossingPointInUse (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s , P o i n t v e r t e x ,
P o i n t otherEdgeVert e x , P o i n t c r o s s i n g P o i n t ) {
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e =
findUnconnectedCrossingPointsOnEdge (
unconnectedCrossingPoints , vertex , otherEdgeVertex ) ;

f o r ( i n t i = 0 ; i < u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e . s i z e ( ) ; i ++) {
i f ( u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e . g e t ( i ) . getP1 ( ) . e q u a l s (
crossingPoint ) ) {
u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e . remove ( i ) ;
break ;
}
}

return unconnectedCrossingPointsOnThisEdge ;
}

p r i v a t e s t a t i c A r r a y L i s t <Line2D . Double> f i n d U n c o n n e c t e d C r o s s i n g P o i n t s O n E d g e (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s , P o i n t v e r t e x ,
Point otherEdgeVertex ) {
APPENDIX B. SOURCE CODE 154

A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e = new


A r r a y L i s t <Line2D . Double >() ;
P o i n t midPoint = new P o i n t ( ( v e r t e x . x + o t h e r E d g e V e r t e x . x ) / 2 ,
( vertex . y + otherEdgeVertex . y ) / 2) ;

f o r ( i n t i = 0 ; i < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; i ++) {
Line2D . Double t e m p C r o s s i n g P o i n t = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( i ) ;
P o i n t s t a r t = new P o i n t ( new Double ( Math . f l o o r ( t e m p C r o s s i n g P o i n t
. getX1 ( ) ) ) . i n t V a l u e ( ) , new Double ( Math
. f l o o r ( t e m p C r o s s i n g P o i n t . getY1 ( ) ) ) . i n t V a l u e ( ) ) ;
P o i n t end = new P o i n t ( new Double ( Math . f l o o r ( t e m p C r o s s i n g P o i n t
. getX2 ( ) ) ) . i n t V a l u e ( ) , new Double ( Math
. f l o o r ( t e m p C r o s s i n g P o i n t . getY2 ( ) ) ) . i n t V a l u e ( ) ) ;
P o i n t m i d P o i n t O f C r o s s i n g = new P o i n t ( ( s t a r t . x + end . x ) / 2 ,
( s t a r t . y + end . y ) / 2 ) ;
i f ( m i d P o i n t O f C r o s s i n g . d i s t a n c e S q ( midPoint ) < 3 . 0 ) {
u n c o n n e c t e d C r o s s i n g P o i n t s O n T h i s E d g e . add ( t e m p C r o s s i n g P o i n t ) ;
}
}
return unconnectedCrossingPointsOnThisEdge ;
}

p r i v a t e s t a t i c Point getOtherEdgeVertexForReturnCrossingPoint ( Point vertex ,


P o i n t otherKnownEdgeVertex ) {
i f ( n u m b e r O f E d g e s I n v o l v i n g V e r t e x ( v e r t e x ) == 1 ) {
r e t u r n otherKnownEdgeVertex ;
} else {
A r r a y L i s t <Line2D . Double> e d g e s U s i n g C u r r e n t V e r t e x =
getAllEdgesUsingVertexMinusCurrentEdge (
v e r t e x , otherKnownEdgeVertex ) ;

d o u b l e lengthOfMainEdge = v e r t e x . d i s t a n c e ( otherKnownEdgeVertex ) ;
d o u b l e normalisingValOfMainEdge = 1 / lengthOfMainEdge ;
d o u b l e normalisedXValOfMainEdge = normalisingValOfMainEdge
∗ ( otherKnownEdgeVertex . x − v e r t e x . x ) ;
d o u b l e normalisedYValOfMainEdge = normalisingValOfMainEdge
∗ ( otherKnownEdgeVertex . y − v e r t e x . y ) ;

P o i n t vertexOfOuterMostEdge = n u l l ;
d o u b l e angleBetweenMainEdgeAndOuterMostEdge = 0 . 0 ;
f o r ( i n t i = 0 ; i < e d g e s U s i n g C u r r e n t V e r t e x . s i z e ( ) ; i ++) {
Line2D . Double tempLine = e d g e s U s i n g C u r r e n t V e r t e x . g e t ( i ) ;
P o i n t s t a r t P o i n t = new P o i n t ( new Double ( tempLine . getX1 ( ) )
. i n t V a l u e ( ) , new Double ( tempLine . getY1 ( ) ) . i n t V a l u e ( ) ) ;
P o i n t endPoint = new P o i n t ( new Double ( tempLine . getX2 ( ) )
. i n t V a l u e ( ) , new Double ( tempLine . getY2 ( ) ) . i n t V a l u e ( ) ) ;
P o i n t pointToUse = n u l l ;
i f ( vertex . equals ( startPoint ) ) {
pointToUse = e n dPo i nt ;
} else {
pointToUse = s t a r t P o i n t ;
}

d o u b l e lengthOfEd g e = v e r t e x . d i s t a n c e ( pointToUse ) ;
d o u b l e n o r m a l i s i n g V a l O f E d g e = 1 / le ng th OfEd ge ;
d o u b l e normalisedXValOfEdge = n o r m a l i s i n g V a l O f E d g e
∗ ( pointToUse . x − v e r t e x . x ) ;
d o u b l e normalisedYValOfEdge = n o r m a l i s i n g V a l O f E d g e
∗ ( pointToUse . y − v e r t e x . y ) ;
APPENDIX B. SOURCE CODE 155

d o u b l e d o t P r o d u c t O f V e c t o r s = ( normalisedXValOfMainEdge ∗
normalisedXValOfEdge )
+ ( normalisedYValOfMainEdge ∗ normalisedYValOfEdge ) ;
d o u b l e angleBetweenEdges = Math . t o D e g r e e s ( Math
. acos ( dotProductOfVectors ) ) ;

i f ( vertexOfOuterMostEdge == n u l l
| | angleBetweenMainEdgeAndOuterMostEdge <
angleBetweenEdges ) {
vertexOfOuterMostEdge = pointToUse ;
angleBetweenMainEdgeAndOuterMostEdge = angleBetweenEdges ;
}
}

r e t u r n vertexOfOuterMostEdge ;
}
}

p r i v a t e s t a t i c Line2D . Double
findCrossingLineInUnconnectedCrossingsFromCrossingPoint (
A r r a y L i s t <Line2D . Double> u n c o n n e c t e d C r o s s i n g P o i n t s ,
Point c r o s s i n g P o i n t ) {
f o r ( i n t i = 0 ; i < u n c o n n e c t e d C r o s s i n g P o i n t s . s i z e ( ) ; i ++) {
Line2D . Double tempLine = u n c o n n e c t e d C r o s s i n g P o i n t s . g e t ( i ) ;
i f ( tempLine . getP1 ( ) . e q u a l s ( c r o s s i n g P o i n t ) ) {
r e t u r n tempLine ;
}
}

return null ;
}

p r i v a t e s t a t i c A r r a y L i s t <Point2D . Double> c a l c u l a t e C o n t r o l P o i n t s F o r C u r v e (
Line2D . Double l i n e O n e , Line2D . Double lineTwo ,
Point crossingPointOne , Point crossingPointTwo ) {
A r r a y L i s t <Point2D . Double> c o n t r o l P o i n t s = new A r r a y L i s t <Point2D . Double >() ;

double firstEdgeYDiff = 0.0;


double firstEdgeXDiff = 0.0;
double secondEdgeYDiff = 0 . 0 ;
double secondEdgeXDiff = 0 . 0 ;

Point otherPointOnCrossingLineOne = n u l l ;
P o i n t otherPointOnCrossingLineTwo = n u l l ;
i f ( c r o s s i n g P o i n t O n e . e q u a l s ( l i n e O n e . getP1 ( ) ) ) {
o t h e r P o i n t O n C r o s s i n g L i n e O n e = new P o i n t ( new Double ( l i n e O n e . getX2 ( ) )
. i n t V a l u e ( ) , new Double ( l i n e O n e . getY2 ( ) ) . i n t V a l u e ( ) ) ;
} else {
o t h e r P o i n t O n C r o s s i n g L i n e O n e = new P o i n t ( new Double ( l i n e O n e . getX1 ( ) )
. i n t V a l u e ( ) , new Double ( l i n e O n e . getY1 ( ) ) . i n t V a l u e ( ) ) ;
}

i f ( c r o s s i n g P o i n t T w o . e q u a l s ( lineTwo . getP1 ( ) ) ) {
otherPointOnCrossingLin eTwo = new P o i n t ( new Double ( lineTwo . getX2 ( ) )
. i n t V a l u e ( ) , new Double ( lineTwo . getY2 ( ) ) . i n t V a l u e ( ) ) ;
} else {
otherPointOnCrossingLin eTwo = new P o i n t ( new Double ( lineTwo . getX1 ( ) )
. i n t V a l u e ( ) , new Double ( lineTwo . getY1 ( ) ) . i n t V a l u e ( ) ) ;
}
APPENDIX B. SOURCE CODE 156

double multiplingFactorOne = crossingPointOne


. d i s t a n c e ( crossingPointTwo )
/ crossingPointOne . d i s t a n c e ( otherPointOnCrossingLineOne ) ;
double multiplingFactorTwo = crossingPointOne
. d i s t a n c e ( crossingPointTwo )
/ c r o s s i n g P o i n t T w o . d i s t a n c e ( ot herPointO nCrossingLineTwo ) ;

i f ( multiplingFactorOne > 2.0) {


multiplingFactorOne = 2.0;
}
i f ( multiplingFactorTwo > 2.0) {
multiplingFactorTwo = 2.0;
}

f i r s t E d g e X D i f f = ( c r o s s i n g P o i n t O n e . getX ( ) − o t h e r P o i n t O n C r o s s i n g L i n e O n e
. getX ( ) )
∗ multiplingFactorOne ;
f i r s t E d g e Y D i f f = ( c r o s s i n g P o i n t O n e . getY ( ) − o t h e r P o i n t O n C r o s s i n g L i n e O n e
. getY ( ) )
∗ multiplingFactorOne ;
s e c o n d E d g e X D i f f = ( c r o s s i n g P o i n t T w o . getX ( ) − otherPointOnC rossingLin eTwo
. getX ( ) )
∗ multiplingFactorTwo ;
s e c o n d E d g e Y D i f f = ( c r o s s i n g P o i n t T w o . getY ( ) − otherPointOnC rossingLin eTwo
. getY ( ) )
∗ multiplingFactorTwo ;

Point2D . Double c o n t r o l P o i n t O n e = new Point2D . Double ( c r o s s i n g P o i n t O n e


. getX ( )
+ f i r s t E d g e X D i f f , c r o s s i n g P o i n t O n e . getY ( ) + f i r s t E d g e Y D i f f ) ;
Point2D . Double c o n t r o l P o i n t T w o = new Point2D . Double ( c r o s s i n g P o i n t T w o
. getX ( )
+ secondEdgeXDiff , c r o s s i n g P o i n t T w o . getY ( ) + s e c o n d E d g e Y D i f f ) ;

c o n t r o l P o i n t s . add ( c o n t r o l P o i n t O n e ) ;
c o n t r o l P o i n t s . add ( c o n t r o l P o i n t T w o ) ;
return controlPoints ;
}

p r i v a t e s t a t i c void calculateReturnPartPoint ( Point crossingPointOne ,


P o i n t c r o s s i n g P o i n t T w o , P o i n t edgeVertexOne ,
P o i n t edgeVertexCommon , P o i n t edgeVertexTwo ,
Line2D . Double c r o s s i n g L i n e O n e , Line2D . Double c r o s s i n g L i n e T w o ) {
P o i n t commonReturnPoint = n u l l ;
Point f i r s t R e t u r n P o i n t = n u l l ;
Point secondReturnPoint = n u l l ;
i f ( edgeVertexOne . e q u a l s ( edgeVertexTwo ) ) {
commonReturnPoint = calculateCommonPointForPointedReturn (
edgeVertexOne , edgeVertexCommon ) ;
i f ( edgeVertexOne . x < edgeVertexCommon . x
&& edgeVertexOne . y < edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexCommon . y − edgeVertexOne . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexCommon . x − edgeVertexOne . x ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;
d o u b l e a l p h a = 4 5 . 0 − gamma ;
double o f f s e t I n X = pointedReturnDistance
∗ Math . c o s ( Math . t o R a d i a n s ( a l p h a ) ) ;
double o f f s e t I n Y = pointedReturnDistance
∗ Math . s i n ( Math . t o R a d i a n s ( a l p h a ) ) ;
APPENDIX B. SOURCE CODE 157

f i r s t R e t u r n P o i n t = new P o i n t ( new Double ( Math


. f l o o r ( commonReturnPoint . x − o f f s e t I n X ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t I n Y ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t ( new Double ( Math
. f l o o r ( commonReturnPoint . x − o f f s e t I n Y ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t I n X ) )
. intValue () ) ;
} e l s e i f ( edgeVertexOne . x > edgeVertexCommon . x
&& edgeVertexOne . y < edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexCommon . y − edgeVertexOne . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexOne . x − edgeVertexCommon . x ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;
d o u b l e a l p h a = 4 5 . 0 − gamma ;
double o f f s e t I n X = pointedReturnDistance
∗ Math . c o s ( Math . t o R a d i a n s ( a l p h a ) ) ;
double o f f s e t I n Y = pointedReturnDistance
∗ Math . s i n ( Math . t o R a d i a n s ( a l p h a ) ) ;

f i r s t R e t u r n P o i n t = new P o i n t ( new Double ( Math


. f l o o r ( commonReturnPoint . x + o f f s e t I n X ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t I n Y ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t ( new Double ( Math
. f l o o r ( commonReturnPoint . x + o f f s e t I n Y ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t I n X ) )
. intValue () ) ;
} e l s e i f ( edgeVertexOne . x < edgeVertexCommon . x
&& edgeVertexOne . y > edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexOne . y − edgeVertexCommon . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexCommon . x − edgeVertexOne . x ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;
d o u b l e a l p h a = 4 5 . 0 − gamma ;
double o f f s e t I n X = pointedReturnDistance
∗ Math . c o s ( Math . t o R a d i a n s ( a l p h a ) ) ;
double o f f s e t I n Y = pointedReturnDistance
∗ Math . s i n ( Math . t o R a d i a n s ( a l p h a ) ) ;

f i r s t R e t u r n P o i n t = new P o i n t ( new Double ( Math


. f l o o r ( commonReturnPoint . x − o f f s e t I n X ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t I n Y ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t ( new Double ( Math
. f l o o r ( commonReturnPoint . x − o f f s e t I n Y ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t I n X ) )
. intValue () ) ;
} e l s e i f ( edgeVertexOne . x > edgeVertexCommon . x
&& edgeVertexOne . y > edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexOne . y − edgeVertexCommon . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexOne . x − edgeVertexCommon . x ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;
d o u b l e a l p h a = 4 5 . 0 − gamma ;
double o f f s e t I n X = pointedReturnDistance
∗ Math . c o s ( Math . t o R a d i a n s ( a l p h a ) ) ;
double o f f s e t I n Y = pointedReturnDistance
∗ Math . s i n ( Math . t o R a d i a n s ( a l p h a ) ) ;

f i r s t R e t u r n P o i n t = new P o i n t ( new Double ( Math


. f l o o r ( commonReturnPoint . x + o f f s e t I n X ) ) . i n t V a l u e ( ) ,
APPENDIX B. SOURCE CODE 158

new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t I n Y ) )


. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t ( new Double ( Math
. f l o o r ( commonReturnPoint . x + o f f s e t I n Y ) ) . i n t V a l u e ( ) ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t I n X ) )
. intValue () ) ;
} e l s e i f ( edgeVertexOne . x == edgeVertexCommon . x ) {
double o f f s e t = pointedReturnDistance
∗ Math . s i n ( Math . t o R a d i a n s ( 4 5 ) ) ;
i f ( edgeVertexOne . y < edgeVertexCommon . y ) {
f i r s t R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x − o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x + o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t ) )
. intValue () ) ;
} else {
f i r s t R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x − o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x + o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t ) )
. intValue () ) ;
}
} e l s e i f ( edgeVertexOne . y == edgeVertexCommon . y ) {
double o f f s e t = pointedReturnDistance
∗ Math . s i n ( Math . t o R a d i a n s ( 4 5 ) ) ;
i f ( edgeVertexOne . x < edgeVertexCommon . x ) {
f i r s t R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x − o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x − o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t ) )
. intValue () ) ;
} else {
f i r s t R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x + o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y − o f f s e t ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x + o f f s e t ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y + o f f s e t ) )
. intValue () ) ;
}
}
} else {
P o i n t commonReturnPointForFirstEdge =
calculateCommonPointForPointedReturn (
APPENDIX B. SOURCE CODE 159

edgeVertexOne , edgeVertexCommon ) ;
P o i n t commonReturnPointForSecondEdge =
calculateCommonPointForPointedReturn (
edgeVertexTwo , edgeVertexCommon ) ;
P o i n t midPointBetweenEdgeReturnPoints = new P o i n t (
( commonReturnPointForFirstEdge . x +
commonReturnPointForSecondEdge . x ) / 2 ,
( commonReturnPointForFirstEdge . y +
commonReturnPointForSecondEdge . y ) / 2 ) ;
commonReturnPoint = calculateCommonPointForPointedReturn (
edgeVertexCommon , midPointBetweenEdgeReturnPoints ) ;

d o u b l e l e n g t h O f F i r s t E d g e = edgeVertexOne . d i s t a n c e ( edgeVertexCommon ) ;
d o u b l e lengthOfSecondEdge = edgeVertexTwo
. d i s t a n c e ( edgeVertexCommon ) ;
double f i r s t E d g e R a t i o = pointedReturnDistance / lengthOfFirstEdge ;
d o u b l e s e c o n d E d g e R a t i o = p o i n t e d R e t u r n D i s t a n c e / lengthOfSecondEdge ;

double firstEdgeYDiff = 0.0;


double firstEdgeXDiff = 0.0;
double secondEdgeYDiff = 0 . 0 ;
double secondEdgeXDiff = 0 . 0 ;

f i r s t E d g e X D i f f = edgeVertexCommon . x − edgeVertexOne . x ;
f i r s t E d g e Y D i f f = edgeVertexCommon . y − edgeVertexOne . y ;
s e c o n d E d g e X D i f f = edgeVertexCommon . x − edgeVertexTwo . x ;
s e c o n d E d g e Y D i f f = edgeVertexCommon . y − edgeVertexTwo . y ;

firstEdgeYDiff = firstEdgeYDiff ∗ firstEdgeRatio ;


firstEdgeXDiff = firstEdgeXDiff ∗ firstEdgeRatio ;
secondEdgeYDiff = secondEdgeYDiff ∗ secondEdgeRatio ;
secondEdgeXDiff = secondEdgeXDiff ∗ secondEdgeRatio ;

f i r s t R e t u r n P o i n t = new P o i n t (
new Double ( Math . f l o o r ( commonReturnPoint . x − f i r s t E d g e X D i f f ) )
. intValue () ,
new Double ( Math . f l o o r ( commonReturnPoint . y − f i r s t E d g e Y D i f f ) )
. intValue () ) ;
s e c o n d R e t u r n P o i n t = new P o i n t ( new Double ( Math
. f l o o r ( commonReturnPoint . x − s e c o n d E d g e X D i f f ) ) . i n t V a l u e ( ) ,
new Double ( Math
. f l o o r ( commonReturnPoint . y − s e c o n d E d g e Y D i f f ) )
. intValue () ) ;
}

i f ( commonReturnPoint != n u l l && f i r s t R e t u r n P o i n t != n u l l
&& s e c o n d R e t u r n P o i n t != n u l l ) {
Po i n t e d Re t u r n p o i n t e d R e t u r n = n u l l ;
A r r a y L i s t <Point2D . Double> f i r s t S e t O f C o n t r o l P o i n t s = n u l l ;
A r r a y L i s t <Point2D . Double> s e c o n d S e t O f C o n t r o l P o i n t s = n u l l ;
Point2D . Double f i r s t C o n t r o l P o i n t = n u l l ;
Point2D . Double s e c o n d C o n t r o l P o i n t = n u l l ;
Point2D . Double t h i r d C o n t r o l P o i n t = n u l l ;
Point2D . Double f o r t h C o n t r o l P o i n t = n u l l ;
Line2D . Double a = new Line2D . Double ( f i r s t R e t u r n P o i n t ,
commonReturnPoint ) ;
Line2D . Double b = new Line2D . Double ( s e c o n d R e t u r n P o i n t ,
commonReturnPoint ) ;

if ( ( edgeVertexOne . e q u a l s ( edgeVertexTwo ) &&


canPointsBeConnectedWithOutIntersectingExistingEdge (
crossingPointOne , firstReturnPoint ) )
APPENDIX B. SOURCE CODE 160

||
( ! edgeVertexOne . e q u a l s ( edgeVertexTwo ) && c r o s s i n g P o i n t O n e
. distance ( firstReturnPoint ) < crossingPointOne
. d i s t a n c e ( secondReturnPoint ) ) ) {
firstSetOfControlPoints = calculateControlPointsForCurve (
crossingLineOne , a , crossingPointOne , firstReturnPoint ) ;
firstSetOfControlPoints =
assignControlPointsToArrayPositions ( firstSetOfControlPoints ) ;
firstControlPoint = firstSetOfControlPoints . get (0) ;
secondControlPoint = firstSetOfControlPoints . get (1) ;

secondSetOfControlPoints = calculateControlPointsForCurve (
crossingLineTwo , b , crossingPointTwo , secondReturnPoint ) ;
secondSetOfControlPoints =
assignControlPointsToArrayPositions ( secondSetOfControlPoints ) ;
thirdControlPoint = secondSetOfControlPoints . get (0) ;
forthControlPoint = secondSetOfControlPoints . get (1) ;

p o i n t e d R e t u r n = new P o i n t e d R e t u r n ( n u l l , n u l l , c r o s s i n g P o i n t O n e ,
c r o s s i n g P o i n t T w o , f i r s t R e t u r n P o i n t , commonReturnPoint ,
secondReturnPoint , f i r s t C o n t r o l P o i n t ,
secondControlPoint , thirdControlPoint ,
forthControlPoint ) ;
} else {
firstSetOfControlPoints = calculateControlPointsForCurve (
crossingLineOne , b , crossingPointOne , secondReturnPoint ) ;
firstSetOfControlPoints =
assignControlPointsToArrayPositions ( firstSetOfControlPoints ) ;
firstControlPoint = firstSetOfControlPoints . get (0) ;
secondControlPoint = firstSetOfControlPoints . get (1) ;

secondSetOfControlPoints = calculateControlPointsForCurve (
crossingLineTwo , a , crossingPointTwo , f i r s t R e t u r n P o i n t ) ;
secondSetOfControlPoints =
assignControlPointsToArrayPositions ( secondSetOfControlPoints ) ;
thirdControlPoint = secondSetOfControlPoints . get (0) ;
forthControlPoint = secondSetOfControlPoints . get (1) ;

p o i n t e d R e t u r n = new P o i n t e d R e t u r n ( n u l l , n u l l , c r o s s i n g P o i n t O n e ,
c r o s s i n g P o i n t T w o , s e c o n d R e t u r n P o i n t , commonReturnPoint ,
firstReturnPoint , firstControlPoint ,
secondControlPoint , thirdControlPoint ,
forthControlPoint ) ;
}

c e l t i c K n o t . addKnotComponent ( p o i n t e d R e t u r n ) ;
}

p r i v a t e s t a t i c A r r a y L i s t <Point2D . Double> a s s i g n C o n t r o l P o i n t s T o A r r a y P o s i t i o n s (
A r r a y L i s t <Point2D . Double> c a l c u l a t e d C o n t r o l P o i n t s ) {
A r r a y L i s t <Point2D . Double> c o n t r o l P o i n t s T o A s s i g n = new
A r r a y L i s t <Point2D . Double >() ;

i f ( c a l c u l a t e d C o n t r o l P o i n t s . s i z e ( ) == 1 ) {
c o n t r o l P o i n t s T o A s s i g n . add ( 0 , c a l c u l a t e d C o n t r o l P o i n t s . get (0) ) ;
c o n t r o l P o i n t s T o A s s i g n . add ( 1 , c a l c u l a t e d C o n t r o l P o i n t s . get (0) ) ;
} else {
c o n t r o l P o i n t s T o A s s i g n . add ( 0 , c a l c u l a t e d C o n t r o l P o i n t s . get (0) ) ;
c o n t r o l P o i n t s T o A s s i g n . add ( 1 , c a l c u l a t e d C o n t r o l P o i n t s . get (1) ) ;
}
APPENDIX B. SOURCE CODE 161

return controlPointsToAssign ;
}

p r i v a t e s t a t i c P o i n t calculateCommonPointForPointedReturn (
P o i n t edgeVertexOne , P o i n t edgeVertexCommon ) {
P o i n t commonReturnPoint = n u l l ;
i n t distanceToUse = pointedReturnDistance ∗ 2 ;
i f ( edgeVertexOne . x < edgeVertexCommon . x
&& edgeVertexOne . y < edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexCommon . y − edgeVertexOne . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexCommon . x − edgeVertexOne . x ;
d o u b l e edgeLength = Math . s q r t ( ( d i f f e r e n c e I n X ∗ d i f f e r e n c e I n X )
+ ( differenceInY ∗ differenceInY ) ) ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;

d o u b l e l e n g t h T o R e t u r n P o i n t = edgeLength + d i s t a n c e T o U s e ;
double newDifferenceInY = lengthToReturnPoint
∗ Math . s i n ( Math . t o R a d i a n s (gamma) ) ;
double newDifferenceInX = lengthToReturnPoint
∗ Math . c o s ( Math . t o R a d i a n s (gamma) ) ;
commonReturnPoint = new P o i n t ( new Double (
( edgeVertexOne . x + n e w D i f f e r e n c e I n X ) ) . i n t V a l u e ( ) ,
new Double ( ( edgeVertexOne . y + n e w D i f f e r e n c e I n Y ) ) . i n t V a l u e ( ) ) ;
} e l s e i f ( edgeVertexOne . x > edgeVertexCommon . x
&& edgeVertexOne . y < edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexCommon . y − edgeVertexOne . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexOne . x − edgeVertexCommon . x ;
d o u b l e edgeLength = Math . s q r t ( ( d i f f e r e n c e I n X ∗ d i f f e r e n c e I n X )
+ ( differenceInY ∗ differenceInY ) ) ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;

d o u b l e l e n g t h T o R e t u r n P o i n t = edgeLength + d i s t a n c e T o U s e ;
double newDifferenceInY = lengthToReturnPoint
∗ Math . s i n ( Math . t o R a d i a n s (gamma) ) ;
double newDifferenceInX = lengthToReturnPoint
∗ Math . c o s ( Math . t o R a d i a n s (gamma) ) ;
commonReturnPoint = new P o i n t ( new Double (
( edgeVertexOne . x − n e w D i f f e r e n c e I n X ) ) . i n t V a l u e ( ) ,
new Double ( ( edgeVertexOne . y + n e w D i f f e r e n c e I n Y ) ) . i n t V a l u e ( ) ) ;
} e l s e i f ( edgeVertexOne . x < edgeVertexCommon . x
&& edgeVertexOne . y > edgeVertexCommon . y ) {
d o u b l e d i f f e r e n c e I n Y = edgeVertexOne . y − edgeVertexCommon . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexCommon . x − edgeVertexOne . x ;
d o u b l e edgeLength = Math . s q r t ( ( d i f f e r e n c e I n X ∗ d i f f e r e n c e I n X )
+ ( differenceInY ∗ differenceInY ) ) ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;

d o u b l e l e n g t h T o R e t u r n P o i n t = edgeLength + d i s t a n c e T o U s e ;
double newDifferenceInY = lengthToReturnPoint
∗ Math . s i n ( Math . t o R a d i a n s (gamma) ) ;
double newDifferenceInX = lengthToReturnPoint
∗ Math . c o s ( Math . t o R a d i a n s (gamma) ) ;
commonReturnPoint = new P o i n t ( new Double (
( edgeVertexOne . x + n e w D i f f e r e n c e I n X ) ) . i n t V a l u e ( ) ,
new Double ( ( edgeVertexOne . y − n e w D i f f e r e n c e I n Y ) ) . i n t V a l u e ( ) ) ;
} e l s e i f ( edgeVertexOne . x > edgeVertexCommon . x
&& edgeVertexOne . y > edgeVertexCommon . y ) {
APPENDIX B. SOURCE CODE 162

d o u b l e d i f f e r e n c e I n Y = edgeVertexOne . y − edgeVertexCommon . y ;
d o u b l e d i f f e r e n c e I n X = edgeVertexOne . x − edgeVertexCommon . x ;
d o u b l e edgeLength = Math . s q r t ( ( d i f f e r e n c e I n X ∗ d i f f e r e n c e I n X )
+ ( differenceInY ∗ differenceInY ) ) ;
d o u b l e gamma = Math . t o D e g r e e s ( Math . atan ( d i f f e r e n c e I n Y
/ differenceInX ) ) ;

d o u b l e l e n g t h T o R e t u r n P o i n t = edgeLength + d i s t a n c e T o U s e ;
double newDifferenceInY = lengthToReturnPoint
∗ Math . s i n ( Math . t o R a d i a n s (gamma) ) ;
double newDifferenceInX = lengthToReturnPoint
∗ Math . c o s ( Math . t o R a d i a n s (gamma) ) ;
commonReturnPoint = new P o i n t ( new Double (
( edgeVertexOne . x − n e w D i f f e r e n c e I n X ) ) . i n t V a l u e ( ) ,
new Double ( ( edgeVertexOne . y − n e w D i f f e r e n c e I n Y ) ) . i n t V a l u e ( ) ) ;
} e l s e i f ( edgeVertexOne . x == edgeVertexCommon . x ) {
i f ( edgeVertexOne . y < edgeVertexCommon . y ) {
commonReturnPoint = new P o i n t ( new Double ( ( edgeVertexCommon . x ) )
. i n t V a l u e ( ) , new Double (
( edgeVertexCommon . y + d i s t a n c e T o U s e ) ) . i n t V a l u e ( ) ) ;
} else {
commonReturnPoint = new P o i n t ( new Double ( ( edgeVertexCommon . x ) )
. i n t V a l u e ( ) , new Double (
( edgeVertexCommon . y − d i s t a n c e T o U s e ) ) . i n t V a l u e ( ) ) ;
}
} e l s e i f ( edgeVertexOne . y == edgeVertexCommon . y ) {
i f ( edgeVertexOne . x < edgeVertexCommon . x ) {
commonReturnPoint = new P o i n t ( new Double (
( edgeVertexCommon . x + d i s t a n c e T o U s e ) ) . i n t V a l u e ( ) ,
new Double ( ( edgeVertexCommon . y ) ) . i n t V a l u e ( ) ) ;
} else {
commonReturnPoint = new P o i n t ( new Double (
( edgeVertexCommon . x − d i s t a n c e T o U s e ) ) . i n t V a l u e ( ) ,
new Double ( ( edgeVertexCommon . y ) ) . i n t V a l u e ( ) ) ;
}
}

r e t u r n commonReturnPoint ;
}

p u b l i c s t a t i c I t e r a t o r <KnotComponent> g e t C e l t i c K n o t C o m p o n e n t I t e r a t o r ( ) {
return celticKnot . getComponentIterator ( ) ;
}

p u b l i c s t a t i c v o i d paintKnot ( Graphics2D g2 , P a i n t o u t l i n e P a i n t ,
P a i n t i n n e r P a i n t , P a i n t backgroundPaint , i n t o u t l i n e W i d t h ,
i n t innerWidth ) {
c e l t i c K n o t . paintKnot ( g2 , o u t l i n e P a i n t , i n n e r P a i n t , backgroundPaint ,
o u t l i n e W i d t h , innerWidth ) ;
}
}

You might also like