1#############################################################################
2# Part of Graph::Easy.
3#
4#############################################################################
5
6package Graph::Easy::Edge::Cell;
7
8use strict;
9use warnings;
10use Graph::Easy::Edge;
11use Graph::Easy::Attributes;
12require Exporter;
13
14use vars qw/$VERSION @EXPORT_OK @ISA/;
15@ISA = qw/Exporter Graph::Easy::Edge/;
16
17$VERSION = '0.76';
18
19use Scalar::Util qw/weaken/;
20
21#############################################################################
22
23# The different cell types:
24use constant {
25  EDGE_CROSS	=> 0,		# +	crossing lines
26  EDGE_HOR	=> 1,	 	# --	horizontal line
27  EDGE_VER	=> 2,	 	# |	vertical line
28
29  EDGE_N_E	=> 3,		# |_	corner (N to E)
30  EDGE_N_W	=> 4,		# _|	corner (N to W)
31  EDGE_S_E	=> 5,		# ,-	corner (S to E)
32  EDGE_S_W	=> 6,		# -,	corner (S to W)
33
34# Joints:
35  EDGE_S_E_W	=> 7,		# -,-	three-sided corner (S to W/E)
36  EDGE_N_E_W	=> 8,		# -'-	three-sided corner (N to W/E)
37  EDGE_E_N_S	=> 9,		#  |-   three-sided corner (E to S/N)
38  EDGE_W_N_S	=> 10,		# -|	three-sided corner (W to S/N)
39
40  EDGE_HOLE	=> 11,		# 	a hole (placeholder for the "other"
41				#	edge in a crossing section
42				#	Holes are inserted in the layout stage
43				#	and removed in the optimize stage, before
44				#	rendering occurs.
45
46# these loop types must come last
47  EDGE_N_W_S	=> 12,		# v--+  loop, northwards
48  EDGE_S_W_N	=> 13,		# ^--+  loop, southwards
49  EDGE_E_S_W	=> 14,		# [_    loop, westwards
50  EDGE_W_S_E	=> 15,		# _]    loop, eastwards
51
52  EDGE_MAX_TYPE		=> 15, 	# last valid type
53  EDGE_LOOP_TYPE	=> 12, 	# first LOOP type
54
55# Flags:
56  EDGE_START_E		=> 0x0100,	# start from East	(sorted ESWN)
57  EDGE_START_S		=> 0x0200,	# start from South
58  EDGE_START_W		=> 0x0400,	# start from West
59  EDGE_START_N		=> 0x0800,	# start from North
60
61  EDGE_END_W		=> 0x0010,	# end points to West	(sorted WNES)
62  EDGE_END_N		=> 0x0020,	# end points to North
63  EDGE_END_E		=> 0x0040,	# end points to East
64  EDGE_END_S		=> 0x0080,	# end points to South
65
66  EDGE_LABEL_CELL	=> 0x1000,	# this cell carries the label
67  EDGE_SHORT_CELL	=> 0x2000,	# a short edge pice (for filling)
68
69  EDGE_ARROW_MASK	=> 0x0FF0,	# mask out the end/start type
70  EDGE_START_MASK	=> 0x0F00,	# mask out the start type
71  EDGE_END_MASK		=> 0x00F0,	# mask out the end type
72  EDGE_TYPE_MASK	=> 0x000F,	# mask out the basic cell type
73  EDGE_FLAG_MASK	=> 0xFFF0,	# mask out the flags
74  EDGE_MISC_MASK	=> 0xF000,	# mask out the misc. flags
75  EDGE_NO_M_MASK	=> 0x0FFF,	# anything except the misc. flags
76
77  ARROW_RIGHT	=> 0,
78  ARROW_LEFT	=> 1,
79  ARROW_UP	=> 2,
80  ARROW_DOWN	=> 3,
81  };
82
83use constant {
84  EDGE_ARROW_HOR	=> EDGE_END_E() + EDGE_END_W(),
85  EDGE_ARROW_VER	=> EDGE_END_N() + EDGE_END_S(),
86
87# shortcuts to not need to write EDGE_HOR + EDGE_START_W + EDGE_END_E
88  EDGE_SHORT_E => EDGE_HOR + EDGE_END_E + EDGE_START_W,		# |-> start/end at this cell
89  EDGE_SHORT_S => EDGE_VER + EDGE_END_S + EDGE_START_N,		# v   start/end at this cell
90  EDGE_SHORT_W => EDGE_HOR + EDGE_END_W + EDGE_START_E,		# <-| start/end at this cell
91  EDGE_SHORT_N => EDGE_VER + EDGE_END_N + EDGE_START_S,		# ^   start/end at this cell
92
93  EDGE_SHORT_BD_EW => EDGE_HOR + EDGE_END_E + EDGE_END_W,	# <-> start/end at this cell
94  EDGE_SHORT_BD_NS => EDGE_VER + EDGE_END_S + EDGE_END_N,	# ^
95								# | start/end at this cell
96								# v
97  EDGE_SHORT_UN_EW => EDGE_HOR + EDGE_START_E + EDGE_START_W,	# --
98  EDGE_SHORT_UN_NS => EDGE_VER + EDGE_START_S + EDGE_START_N,   # |
99
100  EDGE_LOOP_NORTH  => EDGE_N_W_S + EDGE_END_S + EDGE_START_N + EDGE_LABEL_CELL,
101  EDGE_LOOP_SOUTH  => EDGE_S_W_N + EDGE_END_N + EDGE_START_S + EDGE_LABEL_CELL,
102  EDGE_LOOP_WEST   => EDGE_W_S_E + EDGE_END_E + EDGE_START_W + EDGE_LABEL_CELL,
103  EDGE_LOOP_EAST   => EDGE_E_S_W + EDGE_END_W + EDGE_START_E + EDGE_LABEL_CELL,
104  };
105
106#############################################################################
107
108@EXPORT_OK = qw/
109  EDGE_START_E
110  EDGE_START_W
111  EDGE_START_N
112  EDGE_START_S
113
114  EDGE_END_E
115  EDGE_END_W
116  EDGE_END_N
117  EDGE_END_S
118
119  EDGE_SHORT_E
120  EDGE_SHORT_W
121  EDGE_SHORT_N
122  EDGE_SHORT_S
123
124  EDGE_SHORT_BD_EW
125  EDGE_SHORT_BD_NS
126
127  EDGE_SHORT_UN_EW
128  EDGE_SHORT_UN_NS
129
130  EDGE_HOR
131  EDGE_VER
132  EDGE_CROSS
133  EDGE_HOLE
134
135  EDGE_N_E
136  EDGE_N_W
137  EDGE_S_E
138  EDGE_S_W
139
140  EDGE_S_E_W
141  EDGE_N_E_W
142  EDGE_E_N_S
143  EDGE_W_N_S
144
145  EDGE_LOOP_NORTH
146  EDGE_LOOP_SOUTH
147  EDGE_LOOP_EAST
148  EDGE_LOOP_WEST
149
150  EDGE_N_W_S
151  EDGE_S_W_N
152  EDGE_E_S_W
153  EDGE_W_S_E
154
155  EDGE_TYPE_MASK
156  EDGE_FLAG_MASK
157  EDGE_ARROW_MASK
158
159  EDGE_START_MASK
160  EDGE_END_MASK
161  EDGE_MISC_MASK
162
163  EDGE_LABEL_CELL
164  EDGE_SHORT_CELL
165
166  EDGE_NO_M_MASK
167
168  ARROW_RIGHT
169  ARROW_LEFT
170  ARROW_UP
171  ARROW_DOWN
172  /;
173
174my $edge_types = {
175  EDGE_HOR() => 'horizontal',
176  EDGE_VER() => 'vertical',
177
178  EDGE_CROSS() => 'crossing',
179
180  EDGE_N_E() => 'north/east corner',
181  EDGE_N_W() => 'north/west corner',
182  EDGE_S_E() => 'south/east corner',
183  EDGE_S_W() => 'south/west corner',
184
185  EDGE_S_E_W() => 'joint south to east/west',
186  EDGE_N_E_W() => 'joint north to east/west',
187  EDGE_E_N_S() => 'joint east to north/south',
188  EDGE_W_N_S() => 'joint west to north/south',
189
190  EDGE_N_W_S() => 'selfloop, northwards',
191  EDGE_S_W_N() => 'selfloop, southwards',
192  EDGE_E_S_W() => 'selfloop, eastwards',
193  EDGE_W_S_E() => 'selfloop, westwards',
194  };
195
196my $flag_types = {
197  EDGE_LABEL_CELL() => 'labeled',
198  EDGE_SHORT_CELL() => 'short',
199
200  EDGE_START_E() => 'starting east',
201  EDGE_START_W() => 'starting west',
202  EDGE_START_N() => 'starting north',
203  EDGE_START_S() => 'starting south',
204
205  EDGE_END_E() => 'ending east',
206  EDGE_END_W() => 'ending west',
207  EDGE_END_N() => 'ending north',
208  EDGE_END_S() => 'ending south',
209  };
210
211use constant isa_cell => 1;
212
213sub edge_type
214  {
215  # convert edge type number to some descriptive text
216  my $type = shift;
217
218  my $flags = $type & EDGE_FLAG_MASK;
219  $type &= EDGE_TYPE_MASK;
220
221  my $t = $edge_types->{$type} || ('unknown edge type #' . $type);
222
223  $flags &= EDGE_FLAG_MASK;
224
225  my $mask = 0x0010;
226  while ($mask < 0xFFFF)
227    {
228    my $tf = $flags & $mask; $mask <<= 1;
229    $t .= ", $flag_types->{$tf}" if $tf != 0;
230    }
231
232  $t;
233  }
234
235#############################################################################
236
237sub _init
238  {
239  # generic init, override in subclasses
240  my ($self,$args) = @_;
241
242  $self->{type} = EDGE_SHORT_E();	# -->
243  $self->{style} = 'solid';
244
245  $self->{x} = 0;
246  $self->{y} = 0;
247  $self->{w} = undef;
248  $self->{h} = 3;
249
250  foreach my $k (sort keys %$args)
251    {
252    # don't store "after" and "before"
253    next unless $k =~ /^(graph|edge|x|y|type)\z/;
254    $self->{$k} = $args->{$k};
255    }
256
257  $self->_croak("Creating edge cell without a parent edge object")
258    unless defined $self->{edge};
259  $self->_croak("Creating edge cell without a type")
260    unless defined $self->{type};
261
262  # take over settings from edge
263  $self->{style} = $self->{edge}->style();
264  $self->{class} = $self->{edge}->class();
265  $self->{graph} = $self->{edge}->{graph};
266  $self->{group} = $self->{edge}->{group};
267  weaken($self->{graph});
268  weaken($self->{group});
269  $self->{att} = $self->{edge}->{att};
270
271  # register ourselves at this edge
272  $self->{edge}->_add_cell ($self, $args->{after}, $args->{before});
273
274  $self;
275  }
276
277sub arrow_count
278  {
279  # return 0, 1 or 2, depending on the number of end points
280  my $self = shift;
281
282  return 0 if $self->{edge}->{undirected};
283
284  my $count = 0;
285  my $type = $self->{type};
286  $count ++ if ($type & EDGE_END_N) != 0;
287  $count ++ if ($type & EDGE_END_S) != 0;
288  $count ++ if ($type & EDGE_END_W) != 0;
289  $count ++ if ($type & EDGE_END_E) != 0;
290  if ($self->{edge}->{bidirectional})
291    {
292    $count ++ if ($type & EDGE_START_N) != 0;
293    $count ++ if ($type & EDGE_START_S) != 0;
294    $count ++ if ($type & EDGE_START_W) != 0;
295    $count ++ if ($type & EDGE_START_E) != 0;
296    }
297  $count;
298  }
299
300sub _make_cross
301  {
302  # Upgrade us to a cross-section.
303  my ($self, $edge, $flags) = @_;
304
305  my $type = $self->{type} & EDGE_TYPE_MASK;
306
307  $self->_croak("Trying to cross non hor/ver piece at $self->{x},$self->{y}")
308    if (($type != EDGE_HOR) && ($type != EDGE_VER));
309
310  $self->{color} = $self->get_color_attribute('color');
311  $self->{style_ver} = $edge->style();
312  $self->{color_ver} = $edge->get_color_attribute('color');
313
314  # if we are the VER piece, switch styles around
315  if ($type == EDGE_VER)
316    {
317    ($self->{style_ver}, $self->{style}) = ($self->{style},$self->{style_ver});
318    ($self->{color_ver}, $self->{color}) = ($self->{color},$self->{color});
319    }
320
321  $self->{type} = EDGE_CROSS + ($flags || 0);
322
323  $self;
324  }
325
326sub _make_joint
327  {
328  # Upgrade us to a joint
329  my ($self, $edge, $new_type) = @_;
330
331  my $type = $self->{type} & EDGE_TYPE_MASK;
332
333  $self->_croak("Trying to join non hor/ver piece (type: $type) at $self->{x},$self->{y}")
334     if $type >= EDGE_S_E_W;
335
336  $self->{color} = $self->get_color_attribute('color');
337  $self->{style_ver} = $edge->style();
338  $self->{color_ver} = $edge->get_color_attribute('color');
339
340  # if we are the VER piece, switch styles around
341  if ($type == EDGE_VER)
342    {
343    ($self->{style_ver}, $self->{style}) = ($self->{style},$self->{style_ver});
344    ($self->{color_ver}, $self->{color}) = ($self->{color},$self->{color});
345    }
346
347  print STDERR "# creating joint at $self->{x}, $self->{y} with new type $new_type (old $type)\n"
348    if $self->{graph}->{debug};
349
350  $self->{type} = $new_type;
351
352  $self;
353  }
354
355#############################################################################
356# conversion to HTML
357
358my $edge_end_north =
359   ' <td colspan=2 class="##class## eb" style="##bg####ec##">&nbsp;</td>' . "\n" .
360   ' <td colspan=2 class="##class## eb" style="##bg####ec##"><span class="su">^</span></td>' . "\n";
361my $edge_end_south =
362   ' <td colspan=2 class="##class## eb" style="##bg####ec##">&nbsp;</td>' . "\n" .
363   ' <td colspan=2 class="##class## eb" style="##bg####ec##"><span class="sv">v</span></td>' . "\n";
364
365my $edge_empty_row =
366   ' <td colspan=4 class="##class## eb"></td>';
367
368my $edge_arrow_west_upper =
369   '<td rowspan=2 class="##class## eb" style="##ec####bg##"><span class="shl">&lt;</span></td>' . "\n";
370my $edge_arrow_west_lower =
371   '<td rowspan=2 class="##class## eb">&nbsp;</td>' . "\n";
372
373my $edge_arrow_east_upper =
374   '<td rowspan=2 class="##class## eb" style="##ec####bg##"><span class="sh">&gt;</span></td>' . "\n";
375my $edge_arrow_east_lower =
376   '<td rowspan=2 class="##class## eb"></td>' . "\n";
377
378my $edge_html = {
379
380  # The "&nbsp;" in empty table cells with borders are here to make IE display
381  # the border. I so hate browser bugs :-(
382
383  EDGE_S_E() => [
384    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
385    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>',
386    '',
387    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
388    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
389    '',
390   ],
391
392  EDGE_S_E() + EDGE_START_E() + EDGE_END_S() => [
393    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
394    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
395    ' <td rowspan=4 class="##class## el"></td>',
396    '',
397    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
398    ' <td class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
399    $edge_end_south,
400   ],
401
402  EDGE_S_E() + EDGE_START_E() => [
403    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
404    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
405    ' <td rowspan=4 class="##class## el"></td>',
406    '',
407    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
408    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
409    '',
410   ],
411
412  EDGE_S_E() + EDGE_END_E() => [
413    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
414    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
415    ' <td rowspan=4 class="##class##"##edgecolor##><span class="sa">&gt;</span></td>',
416    '',
417    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
418    ' <td rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
419    '',
420   ],
421
422  EDGE_S_E() + EDGE_START_S() => [
423    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
424    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>',
425    '',
426    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
427    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>' . "\n",
428    $edge_empty_row,
429   ],
430
431  EDGE_S_E() + EDGE_START_S() + EDGE_END_E() => [
432    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
433    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>'.
434    ' <td rowspan=4 class="##class##"##edgecolor##><span class="sa">&gt;</span></td>',
435    '',
436    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
437    ' <td class="##class## eb" style="border-left: ##border##;">&nbsp;</td>' . "\n",
438    ' <td class="##class## eb"></td>',
439   ],
440
441  EDGE_S_E() + EDGE_END_S() => [
442    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
443    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>',
444    '',
445    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
446    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>' . "\n",
447    $edge_end_south,
448   ],
449
450  EDGE_S_E() + EDGE_END_S() + EDGE_END_E() => [
451    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
452    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
453    ' <td rowspan=4 class="##class## ha"##edgecolor##><span class="sa">&gt;</span></td>',
454    '',
455    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
456    ' <td class="##class## eb" style="border-left: ##border##;">&nbsp;</td>' . "\n",
457    ' <td colspan=3 class="##class## v"##edgecolor##>v</td>',
458   ],
459
460  ###########################################################################
461  ###########################################################################
462  # S_W
463
464  EDGE_S_W() => [
465    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
466    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
467    '',
468    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
469    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
470    '',
471   ],
472
473  EDGE_S_W() + EDGE_START_W() => [
474    ' <td rowspan=2 class="##class## el"></td>' . "\n" .
475    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
476    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
477    '',
478    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
479    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
480    '',
481   ],
482
483  EDGE_S_W() + EDGE_END_W() => [
484    ' <td rowspan=2 class="##class## va"##edgecolor##><span class="shl">&lt;</span></td>' . "\n" .
485    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
486    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
487    '',
488    ' <td colspan=2 rowspan=2 class="##class## eb"></td>'. "\n" .
489    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
490    '',
491   ],
492
493  EDGE_S_W() + EDGE_START_S() => [
494    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
495    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
496    '',
497    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
498    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
499    $edge_empty_row,
500   ],
501
502  EDGE_S_W() + EDGE_END_S() => [
503    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
504    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
505    '',
506    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
507    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
508    $edge_end_south,
509   ],
510
511  EDGE_S_W() + EDGE_START_W() + EDGE_END_S() => [
512    ' <td rowspan=2 class="##class## el"></td>' . "\n" .
513    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
514    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
515    '',
516    ' <td colspan=2 class="##class## eb"></td>'. "\n" .
517    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
518    $edge_end_south,
519   ],
520
521  EDGE_S_W() + EDGE_START_S() + EDGE_END_W() => [
522    ' <td rowspan=3 class="##class## sh"##edgecolor##>&lt;</td>' . "\n" .
523    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
524    ' <td colspan=2 rowspan=2 class="##class## eb"></td>',
525    '',
526    ' <td class="##class## eb"></td>'. "\n" .
527    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
528    $edge_empty_row,
529   ],
530
531  ###########################################################################
532  ###########################################################################
533  # N_W
534
535  EDGE_N_W() => [
536    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
537    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
538    '',
539    ' <td colspan=4 rowspan=2 class="##class## eb"></td>',
540    '',
541   ],
542
543  EDGE_N_W() + EDGE_START_N() => [
544    $edge_empty_row,
545    ' <td colspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
546    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
547    '',
548    ' <td colspan=4 rowspan=2 class="##class## eb"></td>',
549   ],
550
551  EDGE_N_W() + EDGE_END_N() => [
552    $edge_end_north,
553    ' <td colspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
554    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
555    ' <td colspan=4 rowspan=2 class="##class## eb"></td>',
556    '',
557   ],
558
559  EDGE_N_W() + EDGE_END_N() + EDGE_START_W() => [
560    $edge_end_north,
561    ' <td rowspan=3 class="##class## eb"></td>'.
562    ' <td class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
563    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>',
564    ' <td colspan=4 rowspan=2 class="##class## eb"></td>',
565    '',
566   ],
567
568  EDGE_N_W() + EDGE_START_W() => [
569    ' <td rowspan=2 class="##class## el"></td>' . "\n" .
570    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
571    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>' . "\n",
572    '',
573    ' <td colspan=4 rowspan=2 class="##class## eb"></td>',
574    '',
575   ],
576
577  EDGE_N_W() + EDGE_END_W() => [
578    ' <td rowspan=4 class="##class## sh"##edgecolor##>&lt;</td>' . "\n" .
579    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##;">&nbsp;</td>' . "\n" .
580    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##border##;">&nbsp;</td>' . "\n",
581    '',
582    ' <td colspan=3 rowspan=2 class="##class## eb"></td>',
583    '',
584   ],
585
586  ###########################################################################
587  ###########################################################################
588  # N_E
589
590  EDGE_N_E() => [
591    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
592    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>',
593    '',
594    ' <td colspan=4 rowspan=2 class="##class## eb"></td>',
595    '',
596   ],
597
598  EDGE_N_E() + EDGE_START_E() => [
599    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
600    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>' . "\n" .
601    ' <td rowspan=4 class="##class## el"></td>',
602    '',
603    ' <td colspan=3 rowspan=2 class="##class## eb"></td>',
604    '',
605   ],
606
607  EDGE_N_E() + EDGE_END_E() => [
608    ' <td colspan=2 rowspan=2 class="##class## eb"></td>' . "\n" .
609    ' <td rowspan=2 class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>' . "\n" .
610    ' <td rowspan=4 class="##class## va"##edgecolor##><span class="sa">&gt;</span></td>',
611    '',
612    ' <td colspan=3 rowspan=2 class="##class## eb"></td>',
613    '',
614   ],
615
616  EDGE_N_E() + EDGE_END_E() + EDGE_START_N() => [
617    $edge_empty_row,
618    ' <td colspan=2 class="##class## eb"></td>' . "\n" .
619    ' <td class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>' . "\n" .
620    ' <td rowspan=3 class="##class## va"##edgecolor##><span class="sa">&gt;</span></td>',
621    ' <td colspan=3 rowspan=2 class="##class## eb"></td>',
622    '',
623   ],
624
625  EDGE_N_E() + EDGE_START_E() + EDGE_END_N() => [
626    $edge_end_north,
627    ' <td colspan=2 class="##class## eb"></td>' . "\n" .
628    ' <td class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>' . "\n" .
629    ' <td rowspan=3 class="##class## eb">&nbsp;</td>',
630    ' <td colspan=3 rowspan=2 class="##class## eb"></td>',
631    '',
632   ],
633
634  EDGE_N_E() + EDGE_START_N() => [
635    $edge_empty_row,
636    ' <td colspan=2 rowspan=3 class="##class## eb"></td>' . "\n" .
637    ' <td colspan=2 class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>',
638    ' <td colspan=2 class="##class## eb"></td>',
639    '',
640   ],
641
642  EDGE_N_E() + EDGE_END_N() => [
643    $edge_end_north,
644    ' <td colspan=2 rowspan=3 class="##class## eb"></td>' . "\n" .
645    ' <td colspan=2 class="##class## eb" style="border-bottom: ##border##; border-left: ##border##;">&nbsp;</td>',
646    '',
647    ' <td colspan=2 class="##class## eb"></td>',
648   ],
649
650  ###########################################################################
651  ###########################################################################
652  # self loops
653
654  EDGE_LOOP_NORTH() - EDGE_LABEL_CELL() => [
655    '<td rowspan=2 class="##class## eb">&nbsp;</td>' . "\n".
656    ' <td colspan=2 rowspan=2 class="##class## lh" style="border-bottom: ##border##;##lc####bg##">##label##</td>' . "\n" .
657    ' <td rowspan=2 class="##class## eb">&nbsp;</td>',
658    '',
659    '<td class="##class## eb">&nbsp;</td>' . "\n".
660    ' <td colspan=2 class="##class## eb" style="border-left: ##border##;##bg##">&nbsp;</td>'."\n".
661    ' <td class="##class## eb" style="border-left: ##border##;##bg##">&nbsp;</td>',
662
663    '<td colspan=2 class="##class## v" style="##bg##"##edgecolor##>v</td>' . "\n" .
664    ' <td colspan=2 class="##class## eb">&nbsp;</td>',
665
666   ],
667
668  EDGE_LOOP_SOUTH() - EDGE_LABEL_CELL() => [
669    '<td colspan=2 class="##class## v" style="##bg##"##edgecolor##>^</td>' . "\n" .
670    ' <td colspan=2 class="##class## eb">&nbsp;</td>',
671
672    '<td rowspan=2 class="##class## eb">&nbsp;</td>' . "\n".
673    ' <td colspan=2 rowspan=2 class="##class## lh" style="border-left:##border##;border-bottom:##border##;##lc####bg##">##label##</td>'."\n".
674    ' <td rowspan=2 class="##class## eb" style="border-left:##border##;##bg##">&nbsp;</td>',
675
676    '',
677
678    '<td colspan=4 class="##class## eb">&nbsp;</td>',
679
680   ],
681
682  EDGE_LOOP_WEST() - EDGE_LABEL_CELL() => [
683    $edge_empty_row.
684    ' <td colspan=2 rowspan=2 class="##class## lh" style="border-bottom: ##border##;##lc####bg##">##label##</td>'."\n".
685    ' <td rowspan=2 class="##class## eb">&nbsp;</td>',
686
687    '',
688
689    '<td colspan=2 class="##class## eb" style="border-left: ##border##; border-bottom: ##border##;##bg##">&nbsp;</td>' . "\n".
690    ' <td rowspan=2 class="##class## va" style="##bg##"##edgecolor##><span class="sa">&gt;</span></td>',
691
692    '<td colspan=2 class="##class## eb">&nbsp;</td>',
693   ],
694
695  EDGE_LOOP_EAST() - EDGE_LABEL_CELL() => [
696
697    '<td rowspan=2 class="##class## eb">&nbsp;</td>' . "\n" .
698    ' <td colspan=2 rowspan=2 class="##class## lh" style="border-bottom: ##border##;##lc####bg##">##label##</td>' ."\n".
699    ' <td rowspan=2 class="##class## eb">&nbsp;</td>',
700
701    '',
702
703    '<td rowspan=2 class="##class## va" style="##bg##"##edgecolor##><span class="sh">&lt;</span></td>' ."\n".
704    ' <td colspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>'."\n".
705    ' <td class="##class## eb" style="border-left: ##border##;##bg##">&nbsp;</td>',
706
707    '<td colspan=3 class="##class## eb">&nbsp;</td>',
708   ],
709
710  ###########################################################################
711  ###########################################################################
712  # joints
713
714  ###########################################################################
715  # E_N_S
716
717  EDGE_E_N_S() => [
718    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' . "\n" .
719    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left:##borderv##;border-bottom:##border##;##bg##">&nbsp;</td>',
720    '',
721    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' ."\n".
722    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
723    '',
724   ],
725
726  EDGE_E_N_S() + EDGE_END_E() => [
727    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' . "\n" .
728    ' <td rowspan=2 class="##class## eb" style="border-left: ##borderv##; border-bottom: ##border##;##bg##">&nbsp;</td>' . "\n" .
729    ' <td rowspan=4 class="##class## va"##edgecolor##><span class="sa">&gt;</span></td>',
730    '',
731    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' ."\n".
732    ' <td rowspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
733    '',
734   ],
735
736  ###########################################################################
737  # W_N_S
738
739  EDGE_W_N_S() => [
740    '<td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>' . "\n" .
741    ' <td colspan=2 rowspan=4 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
742    '',
743    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>',
744    '',
745   ],
746
747  ###########################################################################
748  # S_E_W
749
750  EDGE_S_E_W() => [
751    '<td colspan=4 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>',
752    '',
753    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' ."\n".
754    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
755    '',
756   ],
757
758  EDGE_S_E_W() + EDGE_END_S() => [
759    '<td colspan=4 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>',
760    '',
761    '<td colspan=2 class="##class## eb">&nbsp;</td>' ."\n".
762    ' <td colspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
763    $edge_end_south,
764   ],
765
766  EDGE_S_E_W() + EDGE_START_S() => [
767    '<td colspan=4 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>',
768    '',
769    '<td colspan=2 class="##class## eb">&nbsp;</td>' ."\n".
770    ' <td colspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
771    ' <td colspan=4 class="##class## eb"></td>',
772   ],
773
774  EDGE_S_E_W() + EDGE_START_W() => [
775    '<td rowspan=4 class="##class## el"></td>' . "\n" .
776    '<td colspan=3 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>',
777    '',
778    '<td rowspan=2 class="##class## eb">&nbsp;</td>' ."\n".
779    ' <td rowspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
780    '',
781
782   ],
783
784  EDGE_S_E_W() + EDGE_END_E() => [
785    '<td colspan=3 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>' . "\n" .
786    ' <td rowspan=4 class="##class## va"##edgecolor##><span class="sa">&gt;</span></td>',
787    '',
788    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' ."\n".
789    ' <td rowspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
790    '',
791   ],
792
793  EDGE_S_E_W() + EDGE_END_W() => [
794    $edge_arrow_west_upper .
795    '<td colspan=3 rowspan=2 class="##class## eb" style="border-bottom: ##border##;##bg##">&nbsp;</td>' . "\n" ,
796    '',
797    '<td colspan=2 rowspan=2 class="##class## eb">&nbsp;</td>' ."\n" .
798    '<td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##borderv##;##bg##">&nbsp;</td>',
799   ],
800
801  ###########################################################################
802  # N_E_W
803
804  EDGE_N_E_W() => [
805    ' <td colspan=2 rowspan=2 class="##class## eb" style="border-bottom: ##borderv##;##bg##">&nbsp;</td>' ."\n".
806    '<td colspan=2 rowspan=2 class="##class## eb" style="border-left: ##borderv##; border-bottom: ##border##;##bg##">&nbsp;</td>',
807    '',
808    '<td colspan=4 rowspan=2 class="##class## eb">&nbsp;</td>',
809    '',
810   ],
811
812  EDGE_N_E_W() + EDGE_END_N() => [
813    $edge_end_north,
814    ' <td colspan=2 class="##class## eb" style="border-bottom: ##borderv##;##bg##">&nbsp;</td>' ."\n".
815    '<td colspan=2 class="##class## eb" style="border-left: ##borderv##; border-bottom: ##border##;##bg##">&nbsp;</td>',
816    '',
817    '<td colspan=4 rowspan=2 class="##class## eb">&nbsp;</td>',
818    '',
819   ],
820
821  EDGE_N_E_W() + EDGE_START_N() => [
822    $edge_empty_row,
823    ' <td colspan=2 class="##class## eb" style="border-bottom: ##borderv##;##bg##">&nbsp;</td>' ."\n".
824    '<td colspan=2 class="##class## eb" style="border-left: ##borderv##; border-bottom: ##border##;##bg##">&nbsp;</td>',
825    '',
826    '<td colspan=4 rowspan=2 class="##class## eb">&nbsp;</td>',
827    '',
828   ],
829
830  };
831
832sub _html_edge_hor
833  {
834  # Return HTML code for a horizontal edge (with all start/end combinations)
835  # as [], with code for each table row.
836  my ($self, $as) = @_;
837
838  my $s_flags = $self->{type} & EDGE_START_MASK;
839  my $e_flags = $self->{type} & EDGE_END_MASK;
840
841  $e_flags = 0 if $as eq 'none';
842
843  # XXX TODO: we could skip the output of "eb" parts when this edge doesn't belong
844  # to a group.
845
846  my $rc = [
847    ' <td colspan=##mod## rowspan=2 class="##class## lh" style="border-bottom: ##border##;##lc####bg##">##label##</td>',
848    '',
849    '<td colspan=##mod## rowspan=2 class="##class## eb">&nbsp;</td>',
850    '',
851    ];
852
853  # This assumes that only 2 end/start flags are set at the same time:
854
855  my $mod = 4;							# modifier
856  if ($s_flags & EDGE_START_W)
857    {
858    $mod--;
859    $rc->[0] = '<td rowspan=4 class="##class## el"></td>' . "\n" . $rc->[0];
860    };
861  if ($s_flags & EDGE_START_E)
862    {
863    $mod--;
864    $rc->[0] .= "\n " . '<td rowspan=4 class="##class## el"></td>';
865    };
866  if ($e_flags & EDGE_END_W)
867    {
868    $mod--;
869    $rc->[0] = $edge_arrow_west_upper . $rc->[0];
870    $rc->[2] = $edge_arrow_west_lower . $rc->[2];
871    }
872  if ($e_flags & EDGE_END_E)
873    {
874    $mod--;
875    $rc->[0] .= "\n " . $edge_arrow_east_upper;
876    $rc->[2] .= "\n " . $edge_arrow_east_lower;
877    };
878
879  # cx == 1: mod = 2..4, cx == 2: mod = 6..8, etc.
880  $self->{cx} ||= 1;
881  $mod = $self->{cx} * 4 - 4 + $mod;
882
883  for my $e (@$rc)
884    {
885    $e =~ s/##mod##/$mod/g;
886    }
887
888  $rc;
889  }
890
891sub _html_edge_ver
892  {
893  # Return HTML code for a vertical edge (with all start/end combinations)
894  # as [], with code for each table row.
895  my ($self, $as) = @_;
896
897  my $s_flags = $self->{type} & EDGE_START_MASK;
898  my $e_flags = $self->{type} & EDGE_END_MASK;
899
900  $e_flags = 0 if $as eq 'none';
901
902  my $mod = 4; 							# modifier
903
904  # normal vertical edge with no start/end flags
905  my $rc = [
906    '<td colspan=2 rowspan=##mod## class="##class## el">&nbsp;</td>' . "\n " .
907    '<td colspan=2 rowspan=##mod## class="##class## lv" style="border-left: ##border##;##lc####bg##">##label##</td>' . "\n",
908    '',
909    '',
910    '',
911    ];
912
913  # flag north
914  if ($s_flags & EDGE_START_N)
915    {
916    $mod--;
917    unshift @$rc, '<td colspan=4 class="##class## eb"></td>' . "\n";
918    delete $rc->[-1];
919    }
920  elsif ($e_flags & EDGE_END_N)
921    {
922    $mod--;
923    unshift @$rc, $edge_end_north;
924    delete $rc->[-1];
925    }
926
927  # flag south
928  if ($s_flags & EDGE_START_S)
929    {
930    $mod--;
931    $rc->[3] = '<td colspan=4 class="##class## eb"></td>' . "\n"
932    }
933
934  if ($e_flags & EDGE_END_S)
935    {
936    $mod--;
937    $rc->[3] = $edge_end_south;
938    }
939
940  $self->{cy} ||= 1;
941  $mod = $self->{cy} * 4 - 4 + $mod;
942
943  for my $e (@$rc)
944    {
945    $e =~ s/##mod##/$mod/g;
946    }
947
948  $rc;
949  }
950
951sub _html_edge_cross
952  {
953  # Return HTML code for a crossingedge (with all start/end combinations)
954  # as [], with code for each table row.
955  my ($self, $N, $S, $E, $W) = @_;
956
957#  my $s_flags = $self->{type} & EDGE_START_MASK;
958#  my $e_flags = $self->{type} & EDGE_END_MASK;
959
960  my $rc = [
961    ' <td colspan=2 rowspan=2 class="##class## eb el" style="border-bottom: ##border##">&nbsp;</td>' . "\n" .
962    ' <td colspan=2 rowspan=2 class="##class## eb el" style="border-left: ##borderv##; border-bottom: ##border##">&nbsp;</td>' . "\n",
963    '',
964    ' <td colspan=2 rowspan=2 class="##class## eb el"></td>' . "\n" .
965    ' <td colspan=2 rowspan=2 class="##class## eb el" style="border-left: ##borderv##">&nbsp;</td>' . "\n",
966    '',
967    ];
968
969  $rc;
970  }
971
972sub as_html
973  {
974  my ($self) = shift;
975
976  my $type = $self->{type} & EDGE_NO_M_MASK;
977  my $style = $self->{style};
978
979  # none, open, filled, closed
980  my $as; $as = 'none' if $self->{edge}->{undirected};
981  $as = $self->attribute('arrowstyle') unless $as;
982
983  # triangle, box, dot, inv, diamond, line etc.
984  my $ashape; $ashape = 'triangle' if $self->{edge}->{undirected};
985  $ashape = $self->attribute('arrowshape') unless $ashape;
986
987  my $code = $edge_html->{$type};
988
989  if (!defined $code)
990    {
991    my $t = $self->{type} & EDGE_TYPE_MASK;
992
993    if ($style ne 'invisible')
994      {
995      $code = $self->_html_edge_hor($as) if $t == EDGE_HOR;
996      $code = $self->_html_edge_ver($as) if $t == EDGE_VER;
997      $code = $self->_html_edge_cross($as) if $t == EDGE_CROSS;
998      }
999    else
1000      {
1001      $code = [ ' <td colspan=4 rowspan=4 class="##class##">&nbsp;</td>' ];
1002      }
1003
1004    if (!defined $code)
1005      {
1006      $code = [ ' <td colspan=4 rowspan=4 class="##class##">???</td>' ];
1007      warn ("as_html: Unimplemented edge type $self->{type} ($type) at $self->{x},$self->{y} "
1008	. edge_type($self->{type}));
1009      }
1010    }
1011
1012  my $id = $self->{graph}->{id};
1013
1014  my $color = $self->get_color_attribute('color');
1015  my $label = '';
1016  my $label_style = '';
1017
1018  # only include the label if we are the label cell
1019  if ($style ne 'invisible' && ($self->{type} & EDGE_LABEL_CELL))
1020    {
1021    my $switch_to_center;
1022    ($label,$switch_to_center) = $self->_label_as_html();
1023
1024    # replace linebreaks by <br>, but remove extra spaces
1025    $label =~ s/\s*\\n\s*/<br \/>/g;
1026
1027    my $label_color = $self->raw_color_attribute('labelcolor') || $color;
1028    $label_color = '' if $label_color eq '#000000';
1029    $label_style = "color: $label_color;" if $label_color;
1030
1031    my $font = $self->attribute('font') || '';
1032    $font = '' if $font eq ($self->default_attribute('font') || '');
1033    $label_style = "font-family: $font;" if $font;
1034
1035    $label_style .= $self->text_styles_as_css(1,1) unless $label eq '';
1036
1037    $label_style =~ s/^\s*//;
1038
1039    my $link = $self->link();
1040    if ($link ne '')
1041      {
1042      # encode critical entities
1043      $link =~ s/\s/\+/g;			# space
1044      $link =~ s/'/%27/g;			# single-quote
1045
1046      # put the style on the link
1047      $label_style = " style='$label_style'" if $label_style;
1048      $label = "<a href='$link'$label_style>$label</a>";
1049      $label_style = '';
1050      }
1051
1052    }
1053  # without &nbsp;, IE doesn't draw the cell-border nec. for edges
1054  $label = '&nbsp;' unless $label ne '';
1055
1056  ###########################################################################
1057  # get the border styles/colors:
1058
1059  # width for the edge is "2px"
1060  my $bow = '2';
1061  my $border = Graph::Easy::_border_attribute_as_html( $self->{style}, $bow, $color);
1062  my $border_v = $border;
1063
1064  if (($self->{type} & EDGE_TYPE_MASK) == EDGE_CROSS)
1065   {
1066   $border_v = Graph::Easy::_border_attribute_as_html( $self->{style_ver}, $bow, $self->{color_ver});
1067   }
1068
1069  ###########################################################################
1070  my $edge_color = ''; $edge_color = " color: $color;" if $color;
1071
1072  # If the group doesn't have a fill attribute, then it is defined in the CSS
1073  # of the group, and since we get the same class, we can skip the background.
1074  # But if the group has a fill, we need to use this as override.
1075  # The idea behind is to omit the "background: #daffff;" as much as possible.
1076
1077  my $bg = $self->attribute('background') || '';
1078  my $group = $self->{edge}->{group};
1079  $bg = '' if $bg eq 'inherit';
1080  $bg = $group->{att}->{fill} if $group->{att}->{fill} && $bg eq '';
1081  $bg = '' if $bg eq 'inherit';
1082  $bg = " background: $bg;" if $bg;
1083
1084  my $title = $self->title();
1085  $title =~ s/"/&#22;/g;			# replace quotation marks
1086  $title = " title=\"$title\"" if $title ne '';	# add mouse-over title
1087
1088  ###########################################################################
1089  # replace templates
1090
1091  require Graph::Easy::As_ascii if $as ne 'none';	# for _unicode_arrow()
1092
1093  # replace borderv with the border for the vertical edge on CROSS sections
1094  $border =~ s/\s+/ /g;			# collapse multiple spaces
1095  $border_v =~ s/\s+/ /g;
1096  my $cl = $self->class(); $cl =~ s/\./_/g;	# group.cities => group_cities
1097
1098  my $rc;
1099  for my $a (@$code)
1100    {
1101    if (ref($a))
1102      {
1103      for my $c (@$a)
1104        {
1105        push @$rc, $self->_format_td($c,
1106	  $border, $border_v, $label_style, $edge_color, $bg, $as, $ashape, $title, $label, $cl);
1107	}
1108      }
1109    else
1110      {
1111      push @$rc, $self->_format_td($a,
1112	$border, $border_v, $label_style, $edge_color, $bg, $as, $ashape, $title, $label, $cl);
1113      }
1114    }
1115
1116  $rc;
1117  }
1118
1119sub _format_td
1120  {
1121  my ($self, $c,
1122	$border, $border_v, $label_style, $edge_color, $bg, $as, $ashape, $title, $label, $cl) = @_;
1123
1124  # insert 'style="##bg##"' unless there is already a style
1125  $c =~ s/( e[bl]")(>(&nbsp;)?<\/td>)/$1 style="##bg##"$2/g;
1126  # insert missing "##bg##"
1127  $c =~ s/style="border/style="##bg##border/g;
1128
1129  $c =~ s/##class##/$cl/g;
1130  $c =~ s/##border##/$border/g;
1131  $c =~ s/##borderv##/$border_v/g;
1132  $c =~ s/##lc##/$label_style/g;
1133  $c =~ s/##edgecolor##/ style="$edge_color"/g;
1134  $c =~ s/##ec##/$edge_color/g;
1135  $c =~ s/##bg##/$bg/g;
1136  $c =~ s/ style=""//g;		# remove empty styles
1137
1138  # remove arrows if edge is undirected
1139  $c =~ s/>(v|\^|&lt;|&gt;)/>/g if $as eq 'none';
1140
1141  # insert "nice" looking Unicode arrows
1142  $c =~ s/>(v|\^|&lt;|&gt;)/'>' . $self->_unicode_arrow($ashape, $as, $1); /eg;
1143
1144  # insert the label last, other "v" as label might get replaced above
1145  $c =~ s/>##label##/$title>$label/;
1146  # for empty labels use a different class
1147  $c =~ s/ lh"/ eb"/ if $label eq '';
1148
1149  $c .= "\n" unless $c =~ /\n\z/;
1150
1151  $self->quoted_comment() . $c;
1152  }
1153
1154sub class
1155  {
1156  my $self = shift;
1157
1158  my $c = $self->{class} . ($self->{cell_class} || '');
1159  $c = $self->{edge}->{group}->class() . ' ' . $c if ref($self->{edge}->{group});
1160
1161  $c;
1162  }
1163
1164sub group
1165  {
1166  # return the group we belong to as the group of our parent-edge
1167  my $self = shift;
1168
1169  $self->{edge}->{group};
1170  }
1171
1172#############################################################################
1173# accessor methods
1174
1175sub type
1176  {
1177  # get/set type of this path element
1178  # type - EDGE_START, EDGE_END, EDGE_HOR, EDGE_VER, etc
1179  my ($self,$type) = @_;
1180
1181  if (defined $type)
1182    {
1183    if (defined $type && $type < 0 || $type > EDGE_MAX_TYPE)
1184      {
1185      require Carp;
1186      Carp::confess ("Cell type $type for cell $self->{x},$self->{y} is not valid.");
1187      }
1188    $self->{type} = $type;
1189    }
1190
1191  $self->{type};
1192  }
1193
1194#############################################################################
1195
1196# For rendering this path element as ASCII, we need to correct our width based
1197# on whether we have a border or not. But this is only known after parsing is
1198# complete.
1199
1200sub _correct_size
1201  {
1202  my ($self,$format) = @_;
1203
1204  return if defined $self->{w};
1205
1206  # min-size is this
1207  $self->{w} = 5; $self->{h} = 3;
1208  # make short cell pieces very small
1209  if (($self->{type} & EDGE_SHORT_CELL) != 0)
1210    {
1211    $self->{w} = 1; $self->{h} = 1;
1212    return;
1213    }
1214
1215  my $arrows = ($self->{type} & EDGE_ARROW_MASK);
1216  my $type = ($self->{type} & EDGE_TYPE_MASK);
1217
1218  if ($self->{edge}->{bidirectional} && $arrows != 0)
1219    {
1220    $self->{w}++ if $type == EDGE_HOR;
1221    $self->{h}++ if $type == EDGE_VER;
1222    }
1223
1224  # make joints bigger if they got arrows
1225  my $ah = $self->{type} & EDGE_ARROW_HOR;
1226  my $av = $self->{type} & EDGE_ARROW_VER;
1227  $self->{w}++ if $ah && ($type == EDGE_S_E_W || $type == EDGE_N_E_W);
1228  $self->{h}++ if $av && ($type == EDGE_E_N_S || $type == EDGE_W_N_S);
1229
1230  my $style = $self->{edge}->attribute('style') || 'solid';
1231
1232  # make the edge to display ' ..-> ' instead of ' ..> ':
1233  $self->{w}++ if $style eq 'dot-dot-dash';
1234
1235  if ($type >= EDGE_LOOP_TYPE)
1236    {
1237    #  +---+
1238    #  |   V
1239
1240    #       +
1241    #  +--> |
1242    #  |    |
1243    #  +--- |
1244    #       +
1245    $self->{w} = 7;
1246    $self->{w} = 8 if $type == EDGE_N_W_S || $type == EDGE_S_W_N;
1247    $self->{h} = 3;
1248    $self->{h} = 5 if $type != EDGE_N_W_S && $type != EDGE_S_W_N;
1249    }
1250
1251  if ($self->{type} == EDGE_HOR)
1252    {
1253    $self->{w} = 0;
1254    }
1255  elsif ($self->{type} == EDGE_VER)
1256    {
1257    $self->{h} = 0;
1258    }
1259  elsif ($self->{type} & EDGE_LABEL_CELL)
1260    {
1261    # edges do not have borders
1262    my ($w,$h) = $self->dimensions(); $h-- unless $h == 0;
1263
1264    $h += $self->{h};
1265    $w += $self->{w};
1266    $self->{w} = $w;
1267    $self->{h} = $h;
1268    }
1269  }
1270
1271#############################################################################
1272# attribute handling
1273
1274sub attribute
1275  {
1276  my ($self, $name) = @_;
1277
1278  my $edge = $self->{edge};
1279
1280#  my $native = $edge->{att}->{$name};
1281#  return $native if defined $native && $native ne 'inherit';
1282
1283  # shortcut, look up the attribute directly
1284  return $edge->{att}->{$name}
1285    if defined $edge->{att}->{$name} && $edge->{att}->{$name} ne 'inherit';
1286
1287  return $edge->attribute($name);
1288
1289  # XXX TODO This does not work, since caching the attribute doesn't get invalidated
1290  # upon set_attribute().
1291
1292#  $edge->{cache} = {} unless exists $edge->{cache};
1293#  $edge->{cache}->{att} = {} unless exists $edge->{cache}->{att};
1294#
1295#  my $cache = $edge->{cache}->{att};
1296#  return $cache->{$name} if exists $cache->{$name};
1297#
1298#  my $rc = $edge->attribute($name);
1299#  # only cache values that weren't inherited to avoid cache problems
1300#  $cache->{$name} = $rc unless defined $native && $native eq 'inherit';
1301#
1302#  $rc;
1303  }
1304
13051;
1306
1307#############################################################################
1308#############################################################################
1309
1310package Graph::Easy::Edge::Cell::Empty;
1311
1312require Graph::Easy::Node::Cell;
1313our @ISA = qw/Graph::Easy::Node::Cell/;
1314
1315#use vars qw/$VERSION/;
1316
1317our $VERSION = '0.76';
1318
1319use constant isa_cell => 1;
1320
13211;
1322__END__
1323
1324=head1 NAME
1325
1326Graph::Easy::Edge::Cell - A cell in an edge in Graph::Easy
1327
1328=head1 SYNOPSIS
1329
1330        use Graph::Easy;
1331
1332	my $ssl = Graph::Easy::Edge->new(
1333		label => 'encrypted connection',
1334		style => 'solid',
1335		color => 'red',
1336	);
1337	my $src = Graph::Easy::Node->new( 'source' );
1338	my $dst = Graph::Easy::Node->new( 'destination' );
1339
1340	$graph = Graph::Easy->new();
1341
1342	$graph->add_edge($src, $dst, $ssl);
1343
1344	print $graph->as_ascii();
1345
1346=head1 DESCRIPTION
1347
1348A C<Graph::Easy::Edge::Cell> represents an edge between two (or more) nodes
1349in a simple graph.
1350
1351Each edge has a direction (from source to destination, or back and forth),
1352plus a style (line width and style), colors etc. It can also have a name,
1353e.g. a text label associated with it.
1354
1355There should be no need to use this package directly.
1356
1357=head1 METHODS
1358
1359=head2 error()
1360
1361	$last_error = $edge->error();
1362
1363	$cvt->error($error);			# set new messages
1364	$cvt->error('');			# clear error
1365
1366Returns the last error message, or '' for no error.
1367
1368=head2 as_ascii()
1369
1370	my $ascii = $path->as_ascii();
1371
1372Returns the path-cell as a little ascii representation.
1373
1374=head2 as_html()
1375
1376	my $html = $path->as_html($tag,$id);
1377
1378eturns the path-cell as HTML code.
1379
1380=head2 label()
1381
1382	my $label = $path->label();
1383
1384Returns the name (also known as 'label') of the path-cell.
1385
1386=head2 style()
1387
1388	my $style = $edge->style();
1389
1390Returns the style of the edge.
1391
1392=head1 EXPORT
1393
1394None by default. Can export the following on request:
1395
1396  EDGE_START_E
1397  EDGE_START_W
1398  EDGE_START_N
1399  EDGE_START_S
1400
1401  EDGE_END_E
1402  EDGE_END_W
1403  EDGE_END_N
1404  EDGE_END_S
1405
1406  EDGE_SHORT_E
1407  EDGE_SHORT_W
1408  EDGE_SHORT_N
1409  EDGE_SHORT_S
1410
1411  EDGE_SHORT_BD_EW
1412  EDGE_SHORT_BD_NS
1413
1414  EDGE_SHORT_UN_EW
1415  EDGE_SHORT_UN_NS
1416
1417  EDGE_HOR
1418  EDGE_VER
1419  EDGE_CROSS
1420
1421  EDGE_N_E
1422  EDGE_N_W
1423  EDGE_S_E
1424  EDGE_S_W
1425
1426  EDGE_S_E_W
1427  EDGE_N_E_W
1428  EDGE_E_N_S
1429  EDGE_W_N_S
1430
1431  EDGE_LOOP_NORTH
1432  EDGE_LOOP_SOUTH
1433  EDGE_LOOP_EAST
1434  EDGE_LOOP_WEST
1435
1436  EDGE_N_W_S
1437  EDGE_S_W_N
1438  EDGE_E_S_W
1439  EDGE_W_S_E
1440
1441  EDGE_TYPE_MASK
1442  EDGE_FLAG_MASK
1443  EDGE_ARROW_MASK
1444
1445  EDGE_START_MASK
1446  EDGE_END_MASK
1447  EDGE_MISC_MASK
1448
1449  ARROW_RIGHT
1450  ARROW_LEFT
1451  ARROW_UP
1452  ARROW_DOWN
1453
1454=head1 SEE ALSO
1455
1456L<Graph::Easy>.
1457
1458=head1 AUTHOR
1459
1460Copyright (C) 2004 - 2007 by Tels L<http://bloodgate.com>.
1461
1462See the LICENSE file for more details.
1463
1464=cut
1465