File Coverage

blib/lib/Alzabo/Display/SWF/Schema.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Alzabo::Display::SWF::Schema;
2              
3 1     1   5 use strict;
  1         2  
  1         38  
4 1     1   5 use warnings;
  1         2  
  1         29  
5              
6 1     1   2325 use SWF qw(:ALL);
  0            
  0            
7             use Alzabo::Display::SWF::Util qw/rgb2ary button_shape get_coordinates/;
8             use Alzabo::Display::SWF::Table;
9             use Alzabo::Display::SWF::Column;
10             use Alzabo::Display::SWF::Key;
11             use GraphViz;
12             use Alzabo::Runtime;
13             our $VERSION = '0.01';
14              
15             SWF::setVersion(5);
16             my $scale = 5;
17             SWF::setScale($scale);
18              
19             sub new {
20             my $pkg = shift;
21             my %p = @_;
22             my $self = {};
23             bless $self, $pkg;
24             $self->{mov} = new SWF::Movie;
25             $self->{ars} = Alzabo::Runtime::Schema->load_from_file(name => $p{name});
26             $self->{cfg} = $p{cfg};
27             $self->{gvz} = GraphViz->new( qw/
28             layout neato
29             no_overlap 1
30             /,
31             node => {qw/ shape box fontname Courier /},
32             graph => {qw/ splines false /, label => $p{name} },
33             );
34             return $self;
35             }
36              
37             sub create_graph {
38             my $self = shift;
39             my $g = $self->{gvz};
40             my (%fk, %tm);
41             my @t = $self->{ars}->tables;
42             for my $t ( @t ) {
43             my $n = $t->name;
44             $tm{$n} = Alzabo::Display::SWF::Table->new(
45             $t, $self->{mov}, $scale, $self->{cfg},
46             );
47             $g->add_node( $n,
48             height => ( 2 + $tm{$t->name}->{height} )/72,
49             width => ( 2 + $tm{$t->name}->{width} )/72
50             );
51              
52             foreach my $fk ($t->all_foreign_keys) {
53             my @from_id = qw( columns_from columns_to );
54             my $id1 = join "\0", map { $_->name } map { $fk->$_() }
55             @from_id, qw( table_from table_to );
56             $id1 .= "\0";
57             $id1 .= join "\0", $fk->cardinality;
58              
59             my @to_id = qw( columns_to columns_from );
60             my $id2 = join "\0", map { $_->name } map { $fk->$_() }
61             @to_id, qw( table_to table_from );
62             $id2 .= "\0";
63             $id2 .= join "\0", reverse $fk->cardinality;
64              
65             next if $fk{$id1} || $fk{$id2};
66              
67             my %p;
68             my ($taillabel) = $fk->cardinality;
69             $taillabel .= 'd' if $fk->from_is_dependent;
70              
71             my ($headlabel) = reverse $fk->cardinality;
72             $headlabel .= 'd' if $fk->to_is_dependent;
73             $p{taillabel} = $taillabel;
74             $p{headlabel} = $headlabel;
75              
76             if ($fk->is_one_to_one) {
77             $p{dir} = 'none';
78             $p{arrowhead} = $fk->from_is_dependent ? 'dot' : 'odot';
79             $p{arrowtail} = $fk->to_is_dependent ? 'dot' : 'odot';
80             }
81             elsif ($fk->is_many_to_one) {
82             $p{dir} = 'forward';
83             $p{arrowhead} = $fk->from_is_dependent ? 'dot' : 'odot';
84             $p{arrowtail} = $fk->to_is_dependent ? 'invdot' : 'invodot';
85             }
86             else {
87             $p{dir} = 'back';
88             $p{arrowhead} = $fk->from_is_dependent ? 'invdot' : 'invodot';
89             $p{arrowtail} = $fk->to_is_dependent ? 'dot' : 'odot';
90             }
91              
92             # PLAYED AROUND WITH WEIGHT AND LEN OF EDGES IN ORDER TO MAKE
93             # THE PICTURES OF BIG DATABASES SMALLER.
94             my @w = map { ( $fk->$_->all_foreign_keys ) } qw/table_from table_to/;
95             # $p{weight} = 5 / scalar @w;
96             $p{len} = scalar @w / ( 2 * sqrt(scalar @t) );
97              
98             $g->add_edge( $fk->table_from->name, $fk->table_to->name, %p );
99             $fk{$id1} = $fk{$id2} = 1;
100             }
101             }
102             $self->{tmv} = \%tm;
103             }
104              
105             sub create_movie {
106             my $self = shift;
107             local $_ = $self->{gvz}->as_text;
108              
109             # DIMENSION OF THE GRAPH/MOVIE
110             my ($x, $y) =
111             ( $self->{X}, $self->{Y} ) = /graph \[.*bb="0,0,(\d+),(\d+)".*\];/;
112             my $m = $self->{mov};
113             $m->setDimension( $x+10, $y+10 );
114              
115             # POSITIONS OF TABLES IN THE MOVIE
116             my %tm = %{ $self->{tmv} };
117             for my $t ( $self->{ars}->tables ) {
118             my $n = $t->name;
119             /^\s*$n\s\[.*pos="(\d+),(\d+)".*\];/m
120             or die "Didn't find table $n in GraphViz Output";
121             $tm{$n}->{dx} = $1 - int($tm{$n}->{width} / 2);
122             $tm{$n}->{dy} = $y - ( $2 + 6 + int($tm{$n}->{height} / 2) );
123             }
124              
125             # TABLE KEYS (PRIMARY AND FOREIGN)
126             my $cfg = $self->{cfg};
127             my @primary_fill = rgb2ary( $cfg->{table}{key}{primary} );
128             my @foreign_fill = rgb2ary( $cfg->{table}{key}{foreign} );
129             my $ew = $cfg->{table}{edge}{width};
130              
131             for my $t ( $self->{ars}->tables ) {
132             my $n = $t->name;
133             for my $fk ($t->all_foreign_keys) {
134             if ($n eq $fk->table_from->name) {
135             my $tn = $fk->table_to->name;
136             for my $c_pair ($fk->column_pairs) {
137             # $c_from = Alzabo::Display::SWF::Column Object
138             my $c_from = $tm{$n}->{column_by_name}{$c_pair->[0]->name};
139             $c_from->{is_foreign_key} = 1;
140             my $cn = $c_pair->[1]->name;
141             # $c_to = Alzabo::Display::SWF::Column Object
142             my $c_to = $tm{$tn}->{column_by_name}{$cn};
143             push @{ $c_from->{foreign_keys} }, {
144             name => $cn,
145             xy => [
146             $tm{$tn}->{dx} - $tm{$n}->{dx},
147             $tm{$tn}->{dy} - $tm{$n}->{dy} + $c_to->{dy} - $c_from->{dy}
148             ]
149             };
150             }
151             }
152             else {
153             for my $c ($fk->columns_to) {
154             my $co = $tm{$n}->{column_by_name}{$c->name};
155             $co->{is_foreign_key} = 1;
156             }
157             }
158             }
159             for my $c ($t->columns) {
160             my $co = $tm{$n}->{column_by_name}{$c->name};
161             $co->{is_primary_key} = 1 if $c->is_primary_key;
162             my $cs = new Alzabo::Display::SWF::Key qw/r 6 segments 4/,
163             name => $n.$c->name,
164             linestyle_up => [ $scale*$ew,
165             rgb2ary( $cfg->{table}{linestyle}{color} ) ],
166             linestyle_over => [ $scale*$ew,
167             rgb2ary( $cfg->{column}{color}{fg}{over} ) ],
168             opac => hex( $cfg->{table}{edge}{opacity} );
169             if ( $co->{is_primary_key} ) {
170             if ( $co->{is_foreign_key} ) {
171             $cs->{fill2} = [ @primary_fill ];
172             $cs->{fill1} = [ @foreign_fill ];
173             }
174             else { $cs->{fill1} = [ @primary_fill ] }
175             }
176             else {
177             if ( $co->{is_foreign_key} ) {
178             $cs->{fill1} = [ @foreign_fill ];
179             }
180             }
181             my $k;
182             if ( $co->{is_primary_key} or $co->{is_foreign_key} ) {
183             $k = $cs->indicator( $co->{foreign_keys} );
184             my $ki = $m->add($k);
185             push @{ $tm{$n}->{keys} }, $ki;
186             }
187             else { push @{ $tm{$n}->{keys} }, undef }
188             my $ci = $m->add($co->{sprite});
189             push @{ $tm{$n}->{columns} }, $ci;
190             }
191              
192             $tm{$n}->moveTo($tm{$n}->{dx}, $tm{$n}->{dy});
193             }
194              
195             # EDGES BETWEEN TABLES
196             my @xy = /(\w+)\s-[->]\s(\w+)\s.*
197             pos="s,(\d+),(\d+) \s
198             e,(\d+),(\d+) \s
199             \d+,\d+ \s [^"]*\d+,\d+
200             "/xg;
201             my @edge;
202             for my $i ( map $_*6, 0 .. scalar(@xy)/6 - 1 ) {
203             local $_;
204             my ($t1, $t2) = map $tm{$xy[$_]}, $i, $i+1;
205             my ($x1, $y1, $x2, $y2) = map $xy[$_], $i+2 .. $i+5;
206             $y1 = $y - $y1;
207             $y2 = $y - $y2;
208             ($x1, $y1) = get_coordinates($t1, $x1, $y1);
209             ($x2, $y2) = get_coordinates($t2, $x2, $y2);
210             $y1 += 6; $y2 += 6;
211             my $edge = new SWF::Shape;
212             $edge->setLineStyle( $cfg->{schema}{edge}{width} * $scale,
213             rgb2ary( $cfg->{schema}{color}{fg} ),
214             hex( $cfg->{schema}{edge}{opacity} ) );
215             $edge->movePenTo($x1, $y1);
216             if ( $xy[$i] eq $xy[$i+1] ) {
217             my ($xc, $yc);
218             if ( $x1 == $x2 ) {
219             local $_ = int( ($y2 - $y1)/2 );
220             $yc = $y1 + $_;
221             $xc = $x1 > $t1->{dx} ? $x1 + abs : $x1 - abs;
222             }
223             else {
224             local $_ = int( ($x2 - $x1)/2 );
225             $xc = $x1 + $_;
226             $yc = $y1 > $t1->{dy} ? $y1 + abs : $y1 - abs;
227             }
228             $edge->drawCurveTo( $xc, $yc, $x2, $y2 );
229             }
230             else { $edge->drawLineTo($x2, $y2) }
231             push @edge, $edge;
232             }
233              
234             my $f = new SWF::Font $cfg->{fdb_dir} . '/' . $cfg->{schema}{fdb} . '.fdb';
235             my @c_fg = rgb2ary( $cfg->{schema}{color}{fg} );
236             my @c_bg = rgb2ary( $cfg->{schema}{color}{bg} );
237             my $sn = $self->{ars}->name;
238             my $t = new Alzabo::Display::SWF::Text $sn, $f, @c_fg;
239             my $w = $t->getStringWidth($sn);
240              
241             my $bs1 = button_shape($w, @c_bg);
242             my $bs2 = button_shape($w, @c_bg);
243              
244             my $b = new SWF::Button;
245             $b->addShape($bs1, SWF::Button::SWFBUTTON_HIT);
246             $b->addShape($bs1, SWF::Button::SWFBUTTON_UP);
247             $b->addShape($bs2, SWF::Button::SWFBUTTON_OVER);
248             $b->setAction(
249             new SWF::Action("play();"),
250             SWF::Button::SWFBUTTON_MOUSEDOWN
251             );
252             my $bi = $m->add($b);
253              
254             my @lp = /graph \[.*lp="(\d+),(\d+)".*\];/ or die;
255             $bi->moveTo($lp[0], $y - $lp[1]);
256             my $ti = $m->add($t);
257             $ti->moveTo($lp[0] + 1, $y - $lp[1] + 11);
258             $m->nextFrame;
259             $m->add(new SWF::Action("stop();"));
260             $m->nextFrame;
261             for my $edge (@edge) { $m->add($edge) }
262             $m->nextFrame();
263             $m->add(new SWF::Action("stop();"));
264             $m->nextFrame();
265             }
266              
267             sub save {
268             my ($self, $file) = @_;
269             $self->{mov}->save( $file );
270             }
271              
272             sub dim { ($_[0]->{X}, $_[0]->{Y}) }
273              
274             1;