File Coverage

blib/lib/Map/Tube/Plugin/Graph/Utils.pm
Criterion Covered Total %
statement 12 14 85.7
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 17 19 89.4


line stmt bran cond sub pod time code
1             package Map::Tube::Plugin::Graph::Utils;
2              
3             $Map::Tube::Plugin::Graph::Utils::VERSION = '0.26';
4             $Map::Tube::Plugin::Graph::Utils::AUTHORITY = 'cpan:MANWAR';
5              
6             =head1 NAME
7              
8             Map::Tube::Plugin::Graph::Utils - Helper package for Map::Tube::Plugin::Graph.
9              
10             =head1 VERSION
11              
12             Version 0.26
13              
14             =cut
15              
16 1     1   11 use vars qw(@ISA @EXPORT_OK);
  1         2  
  1         66  
17             require Exporter;
18             @ISA = qw(Exporter);
19             @EXPORT_OK = qw(graph_line_image graph_map_image);
20              
21 1     1   18 use 5.006;
  1         4  
22 1     1   7 use strict; use warnings;
  1     1   2  
  1         30  
  1         7  
  1         3  
  1         36  
23 1     1   272 use GraphViz2;
  0            
  0            
24             use Data::Dumper;
25             use MIME::Base64;
26             use Graphics::ColorNames;
27             use Map::Tube::Exception::MissingLineName;
28             use Map::Tube::Exception::InvalidLineName;
29             use File::Temp qw(tempfile tempdir);
30              
31             our $STYLE = 'dashed';
32             our $NODE_COLOR = 'black';
33             our $EDGE_COLOR = 'brown';
34             our $SHAPE = 'oval';
35             our $DIRECTED = 1;
36             our $ARROWSIZE = 1;
37             our $LABELLOC = 'top';
38             our $BGCOLOR = 'grey';
39              
40             =head1 DESCRIPTION
41              
42             B
43              
44             =cut
45              
46             sub graph_line_image {
47             my ($map, $line_name) = @_;
48              
49             my @caller = caller(0);
50             @caller = caller(2) if $caller[3] eq '(eval)';
51              
52             Map::Tube::Exception::MissingLineName->throw({
53             method => __PACKAGE__."::graph_line_image",
54             message => "ERROR: Missing Line name.",
55             filename => $caller[1],
56             line_number => $caller[2] })
57             unless defined $line_name;
58              
59             my $line = $map->_get_line_object_by_name($line_name);
60             Map::Tube::Exception::InvalidLineName->throw({
61             method => __PACKAGE__."::_validate_param",
62             message => "ERROR: Invalid Line name [$line_name].",
63             filename => $caller[1],
64             line_number => $caller[2] })
65             unless defined $line;
66              
67             my $color = $EDGE_COLOR;
68             $color = $line->color if defined $line->color;
69             $line_name = $line->name;
70             my $graph = GraphViz2->new(
71             edge => { color => $color,
72             arrowsize => $ARROWSIZE },
73             node => { shape => $SHAPE },
74             global => { directed => $DIRECTED },
75             graph => { label => _graph_line_label($line_name, $map->name),
76             labelloc => $LABELLOC,
77             bgcolor => _graph_bgcolor($color) });
78              
79             my $stations = $line->get_stations;
80             foreach my $node (@$stations) {
81             $graph->add_node(name => $node->name,
82             color => $color,
83             fontcolor => $color);
84             }
85              
86             my $skip = $map->{skip};
87             foreach my $node (@$stations) {
88             my $from = $node->name;
89             foreach (split /\,/,$node->link) {
90             my $to = $map->get_node_by_id($_);
91             next if (defined $skip
92             &&
93             (exists $skip->{$line_name}->{$from}->{$to->name}
94             ||
95             exists $skip->{$line_name}->{$to->name}->{$from}));
96              
97             if (grep /$line_name/, (map { $_->name } @{$to->line})) {
98             $graph->add_edge(from => $from, to => $to->name);
99             }
100             else {
101             $graph->add_edge(from => $from,
102             to => $to->name,
103             color => $color,
104             style => $STYLE);
105             }
106             }
107             }
108              
109             return _graph_encode_image($graph);
110             }
111              
112             sub graph_map_image {
113             my ($map) = @_;
114              
115             my $graph = GraphViz2->new(
116             node => { shape => $SHAPE },
117             edge => { arrowsize => $ARROWSIZE },
118             global => { directed => $DIRECTED },
119             graph => { label => _graph_map_label($map->name),
120             labelloc => $LABELLOC,
121             bgcolor => $BGCOLOR
122             });
123              
124             my $lines = $map->lines;
125             my $stations = [];
126             foreach my $line (@$lines) {
127             next unless defined ($line->name);
128              
129             foreach my $station (@{$map->get_stations($line->name)}) {
130             push @$stations, $station;
131             my $color = $NODE_COLOR;
132             my $_lines = $station->line;
133             $color = $line->color if ((scalar(@$_lines) == 1) && defined $line->color);
134             $graph->add_node(name => $station->name,
135             color => $color,
136             fontcolor => $color);
137             }
138             }
139              
140             my $seen = {};
141             foreach my $station (@$stations) {
142             my $from = $station->name;
143             foreach (split /\,/,$station->link) {
144             my $to = $map->get_node_by_id($_);
145             next if $seen->{$from}->{$to->name};
146             $graph->add_edge(from => $from, to => $to->name);
147             $seen->{$from}->{$to->name} = 1;
148             }
149             }
150              
151             return _graph_encode_image($graph);
152             }
153              
154             #
155             #
156             # PRIVATE METHODS
157              
158             sub _graph_encode_image {
159             my ($graph) = @_;
160              
161             my $dir = tempdir(CLEANUP => 1);
162             my ($fh, $filename) = tempfile(DIR => $dir);
163             $graph->run(format => 'png', output_file => "$filename");
164             my $raw_string = do { local $/ = undef; <$fh>; };
165              
166             return encode_base64($raw_string);
167             }
168              
169             sub _graph_line_label {
170             my ($line_name, $map_name) = @_;
171              
172             $map_name = '' unless defined $map_name;
173             return sprintf("%s Map: %s Line (Generated by Map::Tube::Plugin::Graph v%s at %s)",
174             $map_name, $line_name, $Map::Tube::Plugin::Graph::VERSION, _graph_timestamp());
175             }
176              
177             sub _graph_map_label {
178             my ($map_name) = @_;
179              
180             $map_name = '' unless defined $map_name;
181             return sprintf("%s Map (Generated by Map::Tube::Plugin::Graph v%s at %s)",
182             $map_name, $Map::Tube::Plugin::Graph::VERSION, _graph_timestamp());
183             }
184              
185             sub _graph_timestamp {
186             my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
187             return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
188             }
189              
190             # TODO: Unfinished work, still not getting the right combination.
191             sub _graph_bgcolor {
192             my ($color) = @_;
193              
194             unless ($color =~ /^#(..)(..)(..)$/) {
195             my $name = Graphics::ColorNames->new('X');
196             $color = $name->hex($color, '#');
197             }
198              
199             return _graph_contrast_color($color);
200             }
201              
202             # Code borrowed from http://www.perlmonks.org/?node_id=261561 provided by msemtd.
203             sub _graph_contrast_color {
204             my ($color) = @_;
205              
206             die "ERROR: Invalid color hex code [$color].\n"
207             unless ($color =~ /^#(..)(..)(..)$/);
208              
209             my ($r, $g, $b) = (hex($1), hex($2), hex($3));
210             my %oppcolors = (
211             "00" => "FF",
212             "33" => "FF",
213             "66" => "FF",
214             "99" => "FF",
215             "CC" => "00",
216             "FF" => "00",
217             );
218              
219             $r = int($r / 51) * 51;
220             $g = int($g / 51) * 51;
221             $b = int($b / 51) * 51;
222              
223             $r = $oppcolors{sprintf("%02X", $r)};
224             $g = $oppcolors{sprintf("%02X", $g)};
225             $b = $oppcolors{sprintf("%02X", $b)};
226              
227             return "#$r$g$b";
228             }
229              
230             =head1 AUTHOR
231              
232             Mohammad S Anwar, C<< >>
233              
234             =head1 REPOSITORY
235              
236             L
237              
238             =head1 BUGS
239              
240             Please report any bugs or feature requests to C, or
241             through the web interface at L.
242             I will be notified and then you'll automatically be notified of progress on your
243             bug as I make changes.
244              
245             =head1 SUPPORT
246              
247             You can find documentation for this module with the perldoc command.
248              
249             perldoc Map::Tube::Plugin::Graph::Utils
250              
251             You can also look for information at:
252              
253             =over 4
254              
255             =item * RT: CPAN's request tracker (report bugs here)
256              
257             L
258              
259             =item * AnnoCPAN: Annotated CPAN documentation
260              
261             L
262              
263             =item * CPAN Ratings
264              
265             L
266              
267             =item * Search CPAN
268              
269             L
270              
271             =back
272              
273             =head1 LICENSE AND COPYRIGHT
274              
275             Copyright (C) 2015 - 2016 Mohammad S Anwar.
276              
277             This program is free software; you can redistribute it and/or modify it under
278             the terms of the the Artistic License (2.0). You may obtain a copy of the full
279             license at:
280              
281             L
282              
283             Any use, modification, and distribution of the Standard or Modified Versions is
284             governed by this Artistic License.By using, modifying or distributing the Package,
285             you accept this license. Do not use, modify, or distribute the Package, if you do
286             not accept this license.
287              
288             If your Modified Version has been derived from a Modified Version made by someone
289             other than you,you are nevertheless required to ensure that your Modified Version
290             complies with the requirements of this license.
291              
292             This license does not grant you the right to use any trademark, service mark,
293             tradename, or logo of the Copyright Holder.
294              
295             This license includes the non-exclusive, worldwide, free-of-charge patent license
296             to make, have made, use, offer to sell, sell, import and otherwise transfer the
297             Package with respect to any patent claims licensable by the Copyright Holder that
298             are necessarily infringed by the Package. If you institute patent litigation
299             (including a cross-claim or counterclaim) against any party alleging that the
300             Package constitutes direct or contributory patent infringement,then this Artistic
301             License to you shall terminate on the date that such litigation is filed.
302              
303             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
304             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
305             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
306             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
307             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
308             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
309             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
310              
311             =cut
312              
313             1; # End of Map::Tube::Plugin::Graph::Utils