aboutsummaryrefslogtreecommitdiff
path: root/challenge-165
diff options
context:
space:
mode:
Diffstat (limited to 'challenge-165')
-rw-r--r--challenge-165/james-smith/perl/SVG.pm150
l---------[-rw-r--r--]challenge-165/james-smith/perl/ch-1.pl119
l---------challenge-165/james-smith/perl/ch-2.pl2
-rw-r--r--challenge-165/james-smith/perl/examples/01-example-point-and_line.txt (renamed from challenge-165/james-smith/perl/examples/data1.txt)0
-rw-r--r--challenge-165/james-smith/perl/examples/02-example-from-site.txt (renamed from challenge-165/james-smith/perl/examples/data2.txt)0
-rw-r--r--challenge-165/james-smith/perl/examples/03-horizontal-cluster.txt (renamed from challenge-165/james-smith/perl/examples/data3.txt)0
-rw-r--r--challenge-165/james-smith/perl/examples/04-vertical-cluster.txt (renamed from challenge-165/james-smith/perl/examples/data4.txt)0
-rw-r--r--challenge-165/james-smith/perl/examples/05-only-one-x.txt (renamed from challenge-165/james-smith/perl/examples/data5.txt)0
l---------challenge-165/james-smith/perl/fit.pl1
-rw-r--r--challenge-165/james-smith/perl/functional.pl147
-rw-r--r--challenge-165/james-smith/perl/object-orientated.pl28
l---------challenge-165/james-smith/perl/plot.pl1
12 files changed, 329 insertions, 119 deletions
diff --git a/challenge-165/james-smith/perl/SVG.pm b/challenge-165/james-smith/perl/SVG.pm
new file mode 100644
index 0000000000..d2d8323c17
--- /dev/null
+++ b/challenge-165/james-smith/perl/SVG.pm
@@ -0,0 +1,150 @@
+package SVG;
+use warnings;
+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' };
+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"?>
+<!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">
+ <rect stroke="%s" stroke-width="%s" fill="%s" x="%s" y="%s" width="%s" height="%s" />
+ <g stroke="%s" stroke-width="%s">
+ %s
+ </g>
+ <g fill="%s">
+ %s
+ </g>
+</svg>';
+
+
+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,
+ };
+ bless $self, $class;
+ $self;
+}
+
+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
+ 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 {
+ my $self = shift;
+
+ ## 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;
+}
+
+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 );
+}
+
+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 ] );
+}
+
+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);
+ ( $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();
+}
+
+sub render {
+ my $self = shift;
+ return $self->get_range()->_render();
+}
+
+sub _render {
+ my $self = shift;
+ $self->calculate_image_size();
+ my $margin = $self->{'config'}{'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
+
+}
+
+1;
diff --git a/challenge-165/james-smith/perl/ch-1.pl b/challenge-165/james-smith/perl/ch-1.pl
index 89c27e4f6f..4e6fe42bc5 100644..120000
--- a/challenge-165/james-smith/perl/ch-1.pl
+++ b/challenge-165/james-smith/perl/ch-1.pl
@@ -1,118 +1 @@
-#!/usr/local/bin/perl
-use strict;
-
-use warnings;
-use feature qw(say);
-
-my $CONFIG = {
- 'margin' => 40, 'max_w' => 960, 'max_h' => 540, # Size of image & margins
- 'stroke' => 5, 'color' => '#900', # Style for lines
- 'radius' => 10, 'fill' => '#090', # Style for dots
- 'border' => '#009', 'bg' => '#ffd', # Style for "page"....
-};
-
-my ($pts,$lines) = get_points_and_lines( ); ## Parses file and updates lines/points
-add_best_fit_line( $pts, $lines, $CONFIG->{'margin'} ) if $0 eq 'ch-2.pl'; ## Only if fitting line!
-say render_svg( $pts, $lines, $CONFIG ); ## Pass in config to render correctly
-
-##----------------------------------------------------------------------
-## Now the code does the real work....
-##----------------------------------------------------------------------
-
-## Parse stdin / files given on command line, to return a list of points and lines..
-sub get_points_and_lines {
- my($ps,$ls,@t)=([],[]);
- local $/ = undef;
-
- 4 == (@t = split /,/) ? ( push @{$ls}, [@t] ) ## Length 4 - line
- : 2 == @t ? ( push @{$ps}, [@t] ) ## Length 2 - point
- : ( warn "input error: $_" ) ## o/w error
- for grep { $_ } split /\s+/, <>;
- return ($ps,$ls);
-}
-
-## Compute the best fit line for the points array (using linear regression...
-## Assumes a dependency of y on x....
-
-sub best_fit {
- my $sx = my $sy = my $sxy = my $sxx = 0, my $n = @{$_[0]};
- $sx += $_->[0], $sxy += $_->[0]*$_->[1], $sy += $_->[1], $sxx += $_->[0]*$_->[0] foreach @{$_[0]};
- return $sx/$n unless $n*$sxx - $sx*$sx;
- my $b = ( $n*$sxy-$sx*$sy ) / ( $n*$sxx - $sx*$sx );
- ( ($sy-$b*$sx)/$n, $b );
-}
-
-## Get the range of x,y values for the given list of lines/points
-## Returns a tuple of min & max x and min & max y.
-
-sub get_ranges {
- my( $ps, $ls ) = @_;
- my( $min_x,$min_y ) = my( $max_x,$max_y ) = @{$ps} ? @{$pts->[0]} : @{$ls->[0]};
- ($_->[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 @{$ps}, map {($_,[$_->[2],$_->[3]])} @{$ls||[]};
- ( $min_x, $max_x, $min_y, $max_y );
-}
-
-## Get the best fit line, and then add extend it to edge of the box - by default we assume that the line will start/end on
-## the side of the box, but just to be sure - we check to see if the pts lie above or below the top/bottom of the box and
-## move them appropriately.
-
-sub add_best_fit_line {
- my ($ps,$ls,$extn) = @_;
- $extn //= 40;
- my( $a, $b ) = best_fit( $ps );
- my( $min_x, $max_x, $min_y, $max_y ) = get_ranges( $ps );
- unless( defined $b ) {
- push @{$ls}, [ $a, $min_y - $extn, $a, $max_y + $extn];
- return;
- }
- 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;
- push @{$ls}, [ $l_x,$l_y,$r_x,$r_y ];
-}
-
-## Finally the rendering of the points/lines, this uses most of the config entries to deal with colour, size etc.
-## We get the range and again add the margin { we don't include the lines in the equation if we are doing challenge 2
-## the line fitting as otherwise we would extend the region twice... }
-##
-## Once we have the size of the image - we work out it's aspect ratio and work out whether we have to make the image
-## narrower or shorter so that the image is no-bigger than the suggested size and that the image is as big as possible
-##
-## As we have a scaling between the x+y values and the size of the image - we need to adjust the size of dots/width of lines
-## by multiplying these all by a scale factor
-
-sub render_svg {
- my( $ps, $ls, $config ) = @_;
- my( $min_x, $max_x, $min_y, $max_y ) = get_ranges( $pts, $0 eq 'ch-2.pl' ? [] : $lines );
- my $margin = $config->{'margin'}//20;
-
- ## Adjust height and width so it fits the size from the config.
- my($W,$H,$width,$height) = ($config->{'max_w'}//800,$config->{'max_h'}//600,$max_x-$min_x+2*$margin,$max_y-$min_y+2*$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.
- my $sf = $width/$W;
-
- sprintf '<?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">
- <rect stroke="%s" stroke-width="%s" fill="%s" x="%s" y="%s" width="%s" height="%s" />
- <g stroke="%s" stroke-width="%s">
- %s
- </g>
- <g fill="%s">
- %s
- </g>
-</svg>',
- $H, $W, $min_x - $margin, $min_y - $margin, $width, $height, ## svg element
- $config->{'border'}//'#000', $sf, $config->{'bg'}//'#eee', ## background rectangle
- $min_x - $margin, $min_y - $margin, $width, $height,
- $config->{'fill'}//'#000', ($config->{'stroke'}//5) * $sf, ## lines
- join( qq(\n ), map { sprintf '<line x1="%s" y1="%s" x2="%s" y2="%s" />', @{$_} } @{$ls} ),
- $config->{'color'}//'#ccc', ## dots
- join( qq(\n ), map { sprintf '<circle cx="%s" cy="%s" r="%s" />', @{$_}, ($config->{'radius'}//10)*$sf } @{$ps} )
-}
+functional.pl \ No newline at end of file
diff --git a/challenge-165/james-smith/perl/ch-2.pl b/challenge-165/james-smith/perl/ch-2.pl
index 8b09e2d217..4e6fe42bc5 120000
--- a/challenge-165/james-smith/perl/ch-2.pl
+++ b/challenge-165/james-smith/perl/ch-2.pl
@@ -1 +1 @@
-ch-1.pl \ No newline at end of file
+functional.pl \ No newline at end of file
diff --git a/challenge-165/james-smith/perl/examples/data1.txt b/challenge-165/james-smith/perl/examples/01-example-point-and_line.txt
index 42acc2b70b..42acc2b70b 100644
--- a/challenge-165/james-smith/perl/examples/data1.txt
+++ b/challenge-165/james-smith/perl/examples/01-example-point-and_line.txt
diff --git a/challenge-165/james-smith/perl/examples/data2.txt b/challenge-165/james-smith/perl/examples/02-example-from-site.txt
index 9e5ecc346a..9e5ecc346a 100644
--- a/challenge-165/james-smith/perl/examples/data2.txt
+++ b/challenge-165/james-smith/perl/examples/02-example-from-site.txt
diff --git a/challenge-165/james-smith/perl/examples/data3.txt b/challenge-165/james-smith/perl/examples/03-horizontal-cluster.txt
index dd2b0780d5..dd2b0780d5 100644
--- a/challenge-165/james-smith/perl/examples/data3.txt
+++ b/challenge-165/james-smith/perl/examples/03-horizontal-cluster.txt
diff --git a/challenge-165/james-smith/perl/examples/data4.txt b/challenge-165/james-smith/perl/examples/04-vertical-cluster.txt
index d22449e7df..d22449e7df 100644
--- a/challenge-165/james-smith/perl/examples/data4.txt
+++ b/challenge-165/james-smith/perl/examples/04-vertical-cluster.txt
diff --git a/challenge-165/james-smith/perl/examples/data5.txt b/challenge-165/james-smith/perl/examples/05-only-one-x.txt
index 76802ba689..76802ba689 100644
--- a/challenge-165/james-smith/perl/examples/data5.txt
+++ b/challenge-165/james-smith/perl/examples/05-only-one-x.txt
diff --git a/challenge-165/james-smith/perl/fit.pl b/challenge-165/james-smith/perl/fit.pl
new file mode 120000
index 0000000000..47aed1db5e
--- /dev/null
+++ b/challenge-165/james-smith/perl/fit.pl
@@ -0,0 +1 @@
+object-orientated.pl \ No newline at end of file
diff --git a/challenge-165/james-smith/perl/functional.pl b/challenge-165/james-smith/perl/functional.pl
new file mode 100644
index 0000000000..6064e5dff7
--- /dev/null
+++ b/challenge-165/james-smith/perl/functional.pl
@@ -0,0 +1,147 @@
+#!/usr/local/bin/perl
+use strict;
+
+use warnings;
+use feature qw(say);
+
+my $DEF = { 'margin' => 40, 'max_w' => 960, 'max_h' => 540,
+ 'color' => '#000', 'stroke' => 3,
+ 'fill' => '#ccc', 'radius' => 10,
+ 'border' => '#000', 'bg' => '#eee' };
+my $LINE_TEMPLATE = '<line x1="%s" y1="%s" x2="%s" y2="%s" />';
+my $POINT_TEMPLATE = '<circle cx="%s" cy="%s" r="%s" />';
+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">
+ <rect stroke="%s" stroke-width="%s" fill="%s" x="%s" y="%s" width="%s" height="%s" />
+ <g stroke="%s" stroke-width="%s">
+ %s
+ </g>
+ <g fill="%s">
+ %s
+ </g>
+</svg>';
+
+my $config = {
+ 'margin' => 40, 'max_w' => 960, 'max_h' => 540, # Size of image & margins
+ 'stroke' => 5, 'color' => '#900', # Style for lines
+ 'radius' => 10, 'fill' => 'rgba(0,153,0,0.5)', # Style for dots
+ 'border' => '#009', 'bg' => '#ffd', # Style for "page"....
+};
+
+my $html = '<html><head><title>Examples</title></head><body><h1>SVG examples</h1>';
+for ( @ARGV ) {
+ my ($pts,$lines) = get_points_and_lines( $_ ); ## Parses file and updates lines/points
+ add_best_fit_line( $pts, $lines, $config->{'margin'} ) if $0 eq 'ch-2.pl'; ## Only if fitting line!
+ $html .= "<h2>$_</h2>". render_svg( $pts, $lines, $config ); ## add to HTML
+}
+say $html.'</body></html>';
+
+##----------------------------------------------------------------------
+## Now the code does the real work....
+##----------------------------------------------------------------------
+
+## Parse stdin / files given on command line, to return a list of points and lines..
+sub get_points_and_lines {
+ my($ps,$ls,@t)=([],[]);
+ local $/ = undef;
+ open my $ifh, '<', $_[0];
+
+ 4 == (@t = split /,/) ? ( push @{$ls}, [@t] ) ## Length 4 - line
+ : 2 == @t ? ( push @{$ps}, [@t] ) ## Length 2 - point
+ : ( warn "input error: $_" ) ## o/w error
+ for grep { $_ } split /\s+/, <$ifh>;
+ close $ifh;
+ ($ps,$ls);
+}
+
+## Compute the best fit line for the points array (using linear regression...
+## Assumes a dependency of y on x....
+
+sub best_fit {
+ my $sx = my $sy = my $sxy = my $sxx = 0, my $n = @{$_[0]};
+ $sx += $_->[0], $sxy += $_->[0]*$_->[1], $sy += $_->[1], $sxx += $_->[0]*$_->[0] foreach @{$_[0]};
+ return $sx/$n unless $n*$sxx - $sx*$sx;
+ my $b = ( $n*$sxy-$sx*$sy ) / ( $n*$sxx - $sx*$sx );
+ ( ($sy-$b*$sx)/$n, $b );
+}
+
+## Get the range of x,y values for the given list of lines/points
+## Returns a 4-tuple of min & max x and min & max y.
+
+sub get_ranges {
+ my( $ps, $ls ) = @_;
+
+ ## 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 ) = @{$ps} ? @{$ps->[0]} : @{$ls->[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 @{$ps}, map {($_,[$_->[2],$_->[3]])} @{$ls||[]};
+
+ ( $min_x, $max_x, $min_y, $max_y );
+}
+
+## Get the best fit line, and then extend it to edge of the box - by default we start with the line going from the left
+## hand edge of the box to the right hand end. If either of these points lies outside the box we adjust the y-coord to
+## the top/bottom of the box, and then alter the x coordinate.
+## We treat the special case where the line is vertical ($b then contains the x-coordinate of all the points... by drawing
+## a vertical line...
+##
+## Note we use the trick of assigning values (the new y-position) with in the ternary operators computing the x-position
+## of the ends...
+
+sub add_best_fit_line {
+ my ($ps,$ls,$extn) = @_;
+ $extn //= $DEF->{'margin'};
+
+ my( $a, $b ) = best_fit( $ps );
+ my( $min_x, $max_x, $min_y, $max_y ) = get_ranges( $ps );
+
+ ## special case of a vertical line
+ push( @{$ls}, [ $a, $min_y - $extn, $a, $max_y + $extn]), return 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;
+
+ push @{$ls}, [ $l_x,$l_y,$r_x,$r_y ];
+}
+
+## Finally the rendering of the points/lines, this uses most of the config entries to deal with colour, size etc.
+## We get the range and again add the margin { we don't include the lines in the equation if we are doing challenge 2
+## the line fitting as otherwise we would extend the region twice... }
+##
+## Once we have the size of the image - we work out it's aspect ratio and work out whether we have to make the image
+## narrower or shorter so that the image is no-bigger than the suggested size and that the image is as big as possible
+##
+## As we have a scaling between the x+y values and the size of the image - we need to adjust the size of dots/width of lines
+## by multiplying these all by a scale factor
+
+sub render_svg {
+ my( $ps, $ls, $config ) = @_;
+ my %conf = (%{$DEF}, %{$config});
+ my( $min_x, $max_x, $min_y, $max_y ) = get_ranges( $ps, $0 eq 'ch-2.pl' ? [] : $ls );
+ my $margin = $conf{'margin'};
+
+ ## Adjust height and width so it fits the size from the config.
+ my($W,$H,$width,$height) = ($conf{'max_w'},$conf{'max_h'},$max_x-$min_x+2*$margin,$max_y-$min_y+2*$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.
+ my $sf = $width/$W;
+
+ sprintf $SVG_TEMPLATE,
+ $H, $W, $min_x - $margin, $min_y - $margin, $width, $height, ## svg element
+ $conf{'border'}, $sf, $conf{'bg'}, $min_x - $margin, $min_y - $margin, $width, $height, ## bg rect
+ $conf{'color'}, $conf{'stroke'} * $sf, join( qq(\n ), map { sprintf $LINE_TEMPLATE, @{$_} } @{$ls} ), ## lines
+ $conf{'fill'}, join( qq(\n ), map { sprintf $POINT_TEMPLATE, @{$_}, $conf{'radius'}*$sf } @{$ps} ) ## points
+}
+
diff --git a/challenge-165/james-smith/perl/object-orientated.pl b/challenge-165/james-smith/perl/object-orientated.pl
new file mode 100644
index 0000000000..790507747b
--- /dev/null
+++ b/challenge-165/james-smith/perl/object-orientated.pl
@@ -0,0 +1,28 @@
+#!/usr/local/bin/perl
+use strict;
+
+use warnings;
+use feature qw(say);
+use Cwd qw(getcwd);
+
+BEGIN { push @INC, getcwd; };
+use SVG;
+
+my $config = {
+ 'margin' => 40, 'max_w' => 960, 'max_h' => 540, # Size of image & margins
+ 'stroke' => 5, 'color' => '#900', # Style for lines
+ 'radius' => 10, 'fill' => 'rgba(0,153,0,0.5)', # Style for dots
+ 'border' => '#009', 'bg' => '#ffd', # Style for "page"....
+};
+
+my $method = $0 eq 'fit.pl' ? 'render_with_best_fit' : 'render';
+say sprintf '<html>
+ <head>
+ <title>Examples: $0</title>
+ </head>
+ <body>
+ <h1>SVG examples: %s</h1>
+ %s
+ </body>
+</html>', $0, join "\n ", map { "<h2>$_</h2>". SVG->new( $config )->load_data( $_ )->$method } @ARGV ;
+
diff --git a/challenge-165/james-smith/perl/plot.pl b/challenge-165/james-smith/perl/plot.pl
new file mode 120000
index 0000000000..47aed1db5e
--- /dev/null
+++ b/challenge-165/james-smith/perl/plot.pl
@@ -0,0 +1 @@
+object-orientated.pl \ No newline at end of file