aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrbaggy <js5@sanger.ac.uk>2022-05-20 05:05:03 +0100
committerdrbaggy <js5@sanger.ac.uk>2022-05-20 05:05:03 +0100
commite5fa1ba2fd3414349ab8d3c04e2c7cfc5c82216d (patch)
treee8055fdc0ba9bb63a5c2888ce1a343ac9168c46e
parent117ab805b1ab93a933f864b0f16c1f7416f47fc7 (diff)
downloadperlweeklychallenge-club-e5fa1ba2fd3414349ab8d3c04e2c7cfc5c82216d.tar.gz
perlweeklychallenge-club-e5fa1ba2fd3414349ab8d3c04e2c7cfc5c82216d.tar.bz2
perlweeklychallenge-club-e5fa1ba2fd3414349ab8d3c04e2c7cfc5c82216d.zip
tidied up OO
-rw-r--r--challenge-165/james-smith/perl/SVG.pm183
1 files changed, 91 insertions, 92 deletions
diff --git a/challenge-165/james-smith/perl/SVG.pm b/challenge-165/james-smith/perl/SVG.pm
index 9a9ca6b77b..7e8c413244 100644
--- a/challenge-165/james-smith/perl/SVG.pm
+++ b/challenge-165/james-smith/perl/SVG.pm
@@ -4,13 +4,16 @@ use feature qw(say);
use Data::Dumper qw(Dumper);
use Const::Fast qw(const);
-const my $DEFAULT_CONFIG => { 'margin' => 40, 'max_w' => 960, 'max_h' => 540,
- 'color' => '#000', 'stroke' => 3,
- 'fill' => '#ccc', 'radius' => 10,
- 'border' => '#000', 'bg' => '#eee' };
+## -----------------------------------
+## Constants - configuration & SVG templates...
+## -----------------------------------
+
+const my %DEFAULT_CONFIG => ( 'margin' => 40, 'max_w' => 960, 'max_h' => 540, 'color' => '#000', 'stroke' => 3,
+ 'fill' => '#ccc', 'radius' => 10, 'border' => '#000', 'bg' => '#eee' );
const my $LINE_TEMPLATE => '<line x1="%s" y1="%s" x2="%s" y2="%s" />';
const my $POINT_TEMPLATE => '<circle cx="%s" cy="%s" r="%s" />';
-const my $SVG_TEMPLATE => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+const my $SVG_TEMPLATE =>
+'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="%s" width="%s" viewBox="%s %s %s %s" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
@@ -24,131 +27,127 @@ const my $SVG_TEMPLATE => '<?xml version="1.0" encoding="UTF-8" standalone="ye
</svg>';
+## -----------------------------------
+## Constructor
+## -----------------------------------
+
sub new {
my ( $class, $config ) = @_;
my $self = {
- 'config' => { %{$DEFAULT_CONFIG}, %{$config} },
- 'points' => [],
- 'lines' => [],
- 'min_x' => undef,
- 'max_x' => undef,
- 'min_y' => undef,
- 'max_y' => undef,
- 'width' => undef,
- 'height' => undef,
- 'scale' => undef,
+ 'config' => { %DEFAULT_CONFIG },
+ 'points' => [], 'lines' => [], 'range' => [], 'size' => [],
+ 'scale' => 1,
};
bless $self, $class;
- $self;
+ $self->update_config( %{$config} )
+ ->set_range( 0, 0, $self->max_w, $self->max_h )
+ ->set_size( $self->max_w, $self->max_h )
}
+## -----------------------------------
+## Getters....
+## -----------------------------------
+
+sub min_x { $_[0]{'range'}[0] } sub min_y { $_[0]{'range'}[1] }
+sub max_x { $_[0]{'range'}[2] } sub max_y { $_[0]{'range'}[3] }
+sub width { $_[0]{'size'}[0] } sub height { $_[0]{'size'}[1] }
+sub points { @{$_[0]{'points'}} } sub lines { @{$_[0]{'lines'}} }
+sub scale { $_[0]{'scale'} } sub config { $_[0]{'config'}{$_[1]} }
+sub margin { $_[0]->config('margin') }
+sub max_w { $_[0]->config('max_w') } sub max_h { $_[0]->config('max_h') }
+sub color { $_[0]->config('color') } sub stroke { $_[0]->config('stroke') }
+sub fill { $_[0]->config('fill') } sub radius { $_[0]->config('radius') }
+sub border { $_[0]->config('border') } sub bg { $_[0]->config('bg') }
+
+## -----------------------------------
+## Setters..
+## -----------------------------------
+
+sub add_point { my( $self, @coord ) = @_; push @{$self->{'points'}}, [@coord]; $self }
+sub add_points { my( $self, @points ) = @_; push @{$self->{'points'}}, @points; $self }
+sub add_line { my( $self, @coord ) = @_; push @{$self->{'lines'}}, [@coord]; $self }
+sub add_lines { my( $self, @lines ) = @_; push @{$self->{'lines'}}, @lines; $self }
+sub set_size { my( $self, $w, $h ) = @_; $self->{'size'} = [ $w, $h ]; $self }
+sub set_range { my( $self, $l, $b, $r, $t ) = @_; $self->{'range'} = [ $l, $b, $r, $t ]; $self }
+sub set_scale { my( $self, $scale ) = @_; $self->{'scale'} = $scale; $self }
+
+sub update_config { my( $self, %pars ) = @_;
+ ( exists $self->{'config'}{$_} ) && ( $self->{'config'}{$_} = $pars{$_} ) for keys %pars; $self }
+
+## --------------------------------------------------------
+## The real code - no direct references to the $self hash!
+## --------------------------------------------------------
+
sub load_data {
my( $self, $fn, @t ) = @_;
local $/ = undef;
open my $ifh, '<', $fn;
- 4 == (@t = split /,/) ? ( push @{$self->{'lines'}}, [@t] ) ## Length 4 - line
- : 2 == @t ? ( push @{$self->{'points'}}, [@t] ) ## Length 2 - point
- : ( warn "input error: $_" ) ## o/w error
+ 4 == (@t = split /,/) ? ( $self->add_line( @t ) ) ## Length 4 - line
+ : 2 == @t ? ( $self->add_point( @t ) ) ## Length 2 - point
+ : ( warn "input error: $_" ) ## o/w error
for grep { /\S/ } split /\s+/, <$ifh>;
close $ifh;
$self;
}
-sub add_points {
- my( $self, @points ) = @_;
- push @{$self->{'points'}}, @points;
- $self;
-}
-
-sub add_lines {
- my( $self, @lines ) = @_;
- push @{$self->{'lines'}}, @lines;
- $self;
-}
-
-sub get_range {
+sub compute_range {
my $self = shift;
+ my( $min_x,$min_y ) = my( $max_x,$max_y ) = @{ $self->points ? ($self->points)[0] : ($self->lines)[0] };
- ## rather than having a special cast as the first part of the loop, we start with the
- ## values for the first point (or start of line if no points)
- my( $min_x,$min_y ) = my( $max_x,$max_y ) = @{$self->{'points'}} ? @{$self->{'points'}[1]} : @{$self->{'lines'}[0]};
-
- ## Compute the range of all points. We comma separate conditions so we only need one postfix for
- ## We use ($c)&&($a=?) to mimic if($c) { $a=? } so we can use the postfix loop...
- ## Note we unravel the two ends of the line by mapping the each line ($_) to $_ + the last two values $_.
($_->[0]<$min_x)&&($min_x=$_->[0]), ($_->[0]>$max_x)&&($max_x=$_->[0]),
($_->[1]<$min_y)&&($min_y=$_->[1]), ($_->[1]>$max_y)&&($max_y=$_->[1])
- for @{$self->{'points'}}, map {($_,[$_->[2],$_->[3]])} @{$self->{'lines'}||[]};
- ( $self->{'min_x'}, $self->{'max_x'}, $self->{'min_y'}, $self->{'max_y'} ) = ( $min_x, $max_x, $min_y, $max_y );
- $self;
-}
+ for $self->points, map { ($_, [$_->[2],$_->[3]]) } $self->lines;
-sub best_fit {
- my $self = shift;
- my $sx = my $sy = my $sxy = my $sxx = 0, my $n = @{$self->{'points'}};
- $sx += $_->[0], $sxy += $_->[0]*$_->[1], $sy += $_->[1], $sxx += $_->[0]*$_->[0] for @{$self->{'points'}};
- return $sx/$n unless $n*$sxx - $sx*$sx;
- my $b = ( $n*$sxy-$sx*$sy ) / ( $n*$sxx - $sx*$sx );
- ( ($sy-$b*$sx)/$n, $b );
+ $self->set_range( $min_x, $min_y, $max_x, $max_y );
}
sub add_line_of_best_fit {
my $self = shift;
- my ($a,$b) = $self->best_fit;
- my ( $min_x, $max_x, $min_y, $max_y, $extn ) =
- ( $self->{'min_x'}, $self->{'max_x'}, $self->{'min_y'}, $self->{'max_y'}, $self->{'config'}{'margin'} );
- ## special case of a vertical line
- $self->add_lines( [ $a, $min_y - $extn, $a, $max_y + $extn] ), return $self unless defined $b;
-
- ## Normal case - get y coprdinates of end points, adjust if outside the box...
- my $l_y = $a + $b * ($min_x - $extn);
- my $r_y = $a + $b * ($max_x + $extn);
- my $l_x = $l_y < $min_y - $extn ? ( ( $l_y = $min_y - $extn ) - $a)/$b
- : $l_y > $max_y + $extn ? ( ( $l_y = $max_y + $extn ) - $a)/$b : $min_x - $extn;
- my $r_x = $r_y < $min_y - $extn ? ( ( $r_y = $min_y - $extn ) - $a)/$b
- : $r_y > $max_y + $extn ? ( ( $r_y = $max_y + $extn ) - $a)/$b : $max_x + $extn;
- $self->add_lines( [ $l_x, $l_y, $r_x, $r_y ] );
+ my $sx = my $sy = my $sxy = my $sxx = 0, my $n = $self->points;
+
+ $sx += $_->[0], $sxy += $_->[0]*$_->[1], $sy += $_->[1], $sxx += $_->[0]*$_->[0] for $self->points;
+
+ return $self->add_line( $sx/$n, $self->min_y - $self->margin, $sx/$n, $self->max_y + $self->margin )
+ unless $n*$sxx - $sx*$sx; ## special case of a vertical line
+
+ my $b = ( $n*$sxy-$sx*$sy ) / ( $n*$sxx - $sx*$sx ); my $a = ($sy-$b*$sx)/$n;
+ my ( $l, $r, $d, $t ) = ( $self->min_x - $self->margin, $self->max_x + $self->margin,
+ $self->min_y - $self->margin, $self->max_y + $self->margin );
+
+ my $l_y = $a + $b * $l;
+ my $r_y = $a + $b * $r;
+ my $l_x = $l_y < $d ? ( ( $l_y = $d ) - $a)/$b : $l_y > $t ? ( ( $l_y = $t ) - $a)/$b : $l;
+ my $r_x = $r_y < $d ? ( ( $r_y = $d ) - $a)/$b : $r_y > $t ? ( ( $r_y = $t ) - $a)/$b : $r;
+
+ $self->add_line( $l_x, $l_y, $r_x, $r_y );
}
sub calculate_image_size {
my $self = shift;
- my $margin = $self->{'config'}{'margin'};
- ## Adjust height and width so it fits the size from the self->{'config'}ig.
- my($W,$H,$width,$height) = ($self->{'config'}{'max_w'},$self->{'config'}{'max_h'},
- $self->{'max_x'}-$self->{'min_x'}+2*$margin,$self->{'max_y'}-$self->{'min_y'}+2*$margin);
+ my($W,$H,$width,$height) = ( $self->max_w, $self->max_h,
+ $self->max_x-$self->min_x+2*$self->margin,$self->max_y-$self->min_y+2*$self->margin);
( $width/$height > $W/$H ) ? ( $H = $height/$width*$W ) : ( $W = $width/$height*$H );
- ## Calculate the scale factor so that we keep spots/lines the same size irrespective of the ranges.
- ( $self->{'width'}, $self->{'height'}, $self->{'scale'} ) = ( $W, $H, $width/$W );
- $self;
-}
-sub render_with_best_fit {
- my $self = shift;
- return $self->get_range()->add_line_of_best_fit()->_render();
+ $self->set_size( $W, $H )->set_scale( $width / $W );
}
-sub render {
- my $self = shift;
- return $self->get_range()->_render();
-}
+sub render_with_best_fit { shift->compute_range->add_line_of_best_fit->_render }
+sub render { shift->compute_range->_render }
sub _render {
my $self = shift;
- $self->calculate_image_size(); ## Given max height/width work out the dimensions of image and the scale factor.
- my $margin = $self->{'config'}{'margin'}; ## Get margin...
- sprintf $SVG_TEMPLATE,
- $self->{'height'}, $self->{'width'}, $self->{'min_x'} - $margin, $self->{'min_y'} - $margin,
- $self->{'max_x'} - $self->{'min_x'} + 2 * $margin,
- $self->{'max_y'} - $self->{'min_y'} + 2 * $margin,
- $self->{'config'}{'border'}, $self->{'scale'}, $self->{'config'}{'bg'}, $self->{'min_x'} - $margin, $self->{'min_y'} - $margin,
- $self->{'max_x'} - $self->{'min_x'} + 2 * $margin,
- $self->{'max_y'} - $self->{'min_y'} + 2 * $margin,
- $self->{'config'}{'color'}, $self->{'config'}{'stroke'} * $self->{'scale'},
- join( qq(\n ), map { sprintf $LINE_TEMPLATE, @{$_} } @{$self->{'lines'}} ), ## lines
- $self->{'config'}{'fill'}, join( qq(\n ),
- map { sprintf $POINT_TEMPLATE, @{$_}, $self->{'config'}{'radius'}*$self->{'scale'} } @{$self->{'points'}} ) ## points
+ $self->calculate_image_size; ## Given max height/width work out the dimensions of image and the scale factor.
+ my $margin = $self->margin; ## Get margin...
+
+ sprintf $SVG_TEMPLATE,
+ $self->height, $self->width, $self->min_x - $margin, $self->min_y - $margin,
+ $self->max_x - $self->min_x + 2 * $margin, $self->max_y - $self->min_y + 2 * $margin,
+ $self->border, $self->scale, $self->bg, $self->min_x - $margin, $self->min_y - $margin,
+ $self->max_x - $self->min_x + 2 * $margin, $self->max_y - $self->min_y + 2 * $margin,
+ $self->color, $self->stroke * $self->scale, join( qq(\n ), map { sprintf $LINE_TEMPLATE, @{$_} } $self->lines ), ## lines
+ $self->fill, join( qq(\n ), map { sprintf $POINT_TEMPLATE, @{$_}, $self->radius*$self->scale } $self->points ) ## points
}
1;