File Coverage

blib/lib/Map/Tube/Plugin/Graph/Utils.pm
Criterion Covered Total %
statement 9 11 81.8
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 13 15 86.6


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