File Coverage

blib/lib/SVG/Graph/Kit.pm
Criterion Covered Total %
statement 78 94 82.9
branch 14 26 53.8
condition 18 50 36.0
subroutine 10 12 83.3
pod 1 1 100.0
total 121 183 66.1


line stmt bran cond sub pod time code
1             package SVG::Graph::Kit;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Simplified SVG data plotting
5              
6 1     1   767 use strict;
  1         2  
  1         30  
7 1     1   4 use warnings;
  1         2  
  1         44  
8              
9             our $VERSION = '0.0403';
10              
11 1     1   357 use parent qw(SVG::Graph);
  1         331  
  1         7  
12              
13 1     1   55415 use SVG::Graph::Data;
  1         3  
  1         21  
14 1     1   587 use SVG::Graph::Data::Datum;
  1         306  
  1         30  
15 1     1   422 use Math::Trig;
  1         12283  
  1         1113  
16              
17              
18             sub new {
19 1     1 1 699 my $class = shift;
20 1         5 my %args = @_;
21              
22             # Move non-parent arguments to the kit.
23 1         2 my %kit = ();
24 1         3 for my $arg (qw(axis data plot polar)) {
25 4 100       11 next unless exists $args{$arg};
26 1         3 $kit{$arg} = $args{$arg};
27 1         3 delete $args{$arg};
28             }
29              
30             # Construct the SVG::Graph object with the remaining arguments.
31 1   50     8 $args{width} ||= 600;
32 1   50     7 $args{height} ||= 600;
33 1   50     6 $args{margin} ||= 35;
34 1         20 my $self = $class->SUPER::new(%args);
35              
36             # Re-bless as a Graph::Kit object.
37 1         737 bless $self, $class;
38 1         7 $self->_setup(%kit);
39 1         2608 return $self;
40             }
41              
42             sub _setup {
43 1     1   2 my $self = shift;
44 1         3 my %args = @_;
45              
46             # Start with an initial frame...
47 1         7 my $frame = $self->add_frame;
48              
49             # Plot the data.
50 1 50       375 if ($args{data}) {
51             # Load the graph data and use the SVG::Graph::Data object for label making.
52 1         31 $self->{graph_data} = _load_data($args{data}, $frame, $args{polar});
53             # Add the data to the graph.
54             my %plot = (
55             stroke => $args{plot}{stroke} || 'red',
56             fill => $args{plot}{fill} || 'red',
57 1   50     16 'fill-opacity' => $args{plot}{'fill-opacity'} || 0.5,
      50        
      50        
58             );
59 1   50     7 $args{plot}{type} ||= 'scatter';
60 1         5 $frame->add_glyph($args{plot}{type}, %plot);
61             }
62              
63             # Handle the axis unless it's set to 0.
64 1 50 0     2742 if (!exists $args{axis} || (exists $args{axis} && $args{axis})) {
      33        
65 1         8 my %axis = $self->_load_axis($args{data}, $args{axis});
66 1         15 $frame->add_glyph('axis', %axis);
67             }
68             }
69              
70             sub _load_axis {
71 1     1   5 my($self, $data, $axis) = @_;
72              
73             # Initialize an empty axis unless given a hashref.
74 1 50       6 $axis = {} unless ref $axis eq 'HASH';
75              
76             # Set the default properties and user override.
77 1         8 my %axis = (
78             x_intercept => 0,
79             y_intercept => 0,
80             stroke => 'gray',
81             'stroke-width' => 2,
82             ticks => 30, # Max data per axis
83             %$axis, # User override
84             );
85              
86             # Set the number of ticks to show on each axis.
87 1   33     9 $axis{xticks} ||= $axis{ticks};
88 1   33     7 $axis{yticks} ||= $axis{ticks};
89              
90             # Compute scale factors.
91 1         2 my ($xscale, $yscale) = (1, 1);
92 1 50 33     11 if ($data && ($self->{graph_data}->xmax - $self->{graph_data}->xmin) > $axis{xticks}) {
93             # Round to the integer, i.e. 0 decimal places.
94 0         0 $xscale = sprintf '%.0f', $self->{graph_data}->xmax / $axis{xticks};
95             }
96 1 50 33     772 if ($data && ($self->{graph_data}->ymax - $self->{graph_data}->ymin) > $axis{yticks}) {
97             # Round to the integer, i.e. 0 decimal places.
98 0         0 $yscale = sprintf '%.0f', $self->{graph_data}->ymax / $axis{yticks};
99             }
100              
101             # Use absolute_ticks if no tick mark setting is provided.
102 1 50 33     42 unless (defined $axis{x_absolute_ticks} || defined $axis{x_fractional_ticks}) {
103 1         3 $axis{x_absolute_ticks} = $xscale;
104             }
105 1 50 33     10 unless (defined $axis{y_absolute_ticks} || defined $axis{y_fractional_ticks}) {
106 1         3 $axis{y_absolute_ticks} = $yscale;
107             }
108              
109             # Use increments of 1 to data-max for ticks if none are provided.
110 1 50 33     36 if ($data && !defined $axis{x_tick_labels} && !defined $axis{x_intertick_labels}) {
      33        
111 1 50       5 if ($xscale > 1) {
112 0         0 $axis{x_tick_labels} = [ $self->{graph_data}->xmin ];
113 0         0 push @{ $axis{x_tick_labels} }, $_ * $xscale for 1 .. $axis{ticks};
  0         0  
114             }
115             else {
116 1         6 $axis{x_tick_labels} = [ $self->{graph_data}->xmin .. $self->{graph_data}->xmax ];
117             }
118             # XXX This is a lame hack
119 1         37 $axis{x_intertick_labels} = [ map { '' } $self->{graph_data}->ymin .. $self->{graph_data}->ymax ];
  28         63  
120             }
121 1 50 33     10 if ($data && !defined $axis{y_tick_labels} && !defined $axis{y_intertick_labels}) {
      33        
122 1 50       4 if ($yscale > 1) {
123 0         0 $axis{y_tick_labels} = [ $self->{graph_data}->ymin ];
124 0         0 push @{ $axis{y_tick_labels} }, $_ * $yscale for 1 .. $axis{ticks};
  0         0  
125             }
126             else {
127 1         4 $axis{y_tick_labels} = [ $self->{graph_data}->ymin .. $self->{graph_data}->ymax ];
128             }
129             # XXX This is also a lame hack
130 1         41 $axis{y_intertick_labels} = [ map { '' } $self->{graph_data}->ymin .. $self->{graph_data}->ymax ];
  28         64  
131             }
132              
133             # Remove keys not used by parent module.
134 1         4 delete $axis{ticks};
135 1         2 delete $axis{xticks};
136 1         9 delete $axis{yticks};
137              
138 1         10 return %axis;
139             }
140              
141             sub _load_data {
142 1     1   5 my ($data, $frame, $polar) = @_;
143              
144             # Create individual data points.
145 1         1 my @data = ();
146 1         3 for my $datum (@$data) {
147             # Set the coordinate.
148 9         215 my @coord = @$datum;
149 9 50       15 @coord = _to_polar($datum) if $polar;
150              
151             # Add our 3D data point.
152 9         24 push @data, SVG::Graph::Data::Datum->new(
153             x => $coord[0],
154             y => $coord[1],
155             z => $coord[2],
156             );
157             }
158              
159             # Instantiate a new SVG::Graph::Data object;
160 1         28 my $obj = SVG::Graph::Data->new(data => \@data);
161              
162             # Populate our graph with data.
163 1         79 $frame->add_data($obj);
164 1         31 return $obj;
165             }
166              
167             sub _theta {
168 0     0     my $point = shift;
169             # return int(rand 359);
170 0           return atan2($point->[1], $point->[0]);
171             }
172              
173             sub _to_polar {
174 0     0     my $point = shift;
175 0           my $r = 0;
176 0           $r += $_ ** 2 for @$point;
177 0           $r = sqrt $r;
178 0           my $t = _theta($point);
179 0           return $r, $t;
180             }
181              
182             __END__