aboutsummaryrefslogtreecommitdiff
path: root/challenge-165/james-smith/perl
diff options
context:
space:
mode:
Diffstat (limited to 'challenge-165/james-smith/perl')
-rw-r--r--challenge-165/james-smith/perl/SVG.pm136
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.pl150
-rw-r--r--challenge-165/james-smith/perl/object-orientated.pl31
l---------challenge-165/james-smith/perl/plot.pl1
12 files changed, 321 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..99c03be04a
--- /dev/null
+++ b/challenge-165/james-smith/perl/SVG.pm
@@ -0,0 +1,136 @@
+package SVG;
+use warnings;
+use feature qw(say);
+use Data::Dumper qw(Dumper);
+use Const::Fast qw(const);
+
+## -----------------------------------
+## Constants - configuration & SVG templates...
+## -----------------------------------
+
+const my %DEF_CNF => ( 'margin' => 40, 'max_w' => 960, 'max_h' => 540, 'color' => '#000', 'stroke' => 3,
+ 'fill' => '#ccc', 'radius' => 10, 'border' => '#000', 'bg' => '#eee' );
+const my $JOIN => "\n ";
+const my $LN_TMPL => '<line x1="%0.4f" y1="%0.4f" x2="%0.4f" y2="%0.4f" />';
+const my $PT_TMPL => '<circle cx="%0.4f" cy="%0.4f" r="%0.4f" />';
+const my $SVG_TMPL => '<?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="%0.4f" width="%0.4f" viewBox="%0.4f %0.4f %0.4f %0.4f" 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="%0.4f" fill="%s" x="%0.4f" y="%0.4f" width="%0.4f" height="%0.4f" />
+ <g transform="scale(1,-1) translate(0,%0.4f)">
+ <g stroke="%s" stroke-width="%0.4f">
+ %s
+ </g>
+ <g fill="%s">
+ %s
+ </g>
+ </g>
+</svg>';
+
+
+## -----------------------------------
+## Constructor
+## -----------------------------------
+
+sub new {
+ bless my$s={'cnf'=>{%DEF_CNF},'scale'=>1,'points'=>[],'lines'=>[],'range'=>[],'size'=>[]},$_[0];
+ $s->update_cnf(%{$_[1]||{}})->set_size($s->max_w,$s->max_h)->set_range(0,0,$s->max_w,$s->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 cnf { $_[0]{'cnf'}{$_[1]} }
+sub max_w { $_[0]->cnf('max_w') } sub max_h { $_[0]->cnf('max_h') }
+sub color { $_[0]->cnf('color') } sub stroke { $_[0]->cnf('stroke') }
+sub fill { $_[0]->cnf('fill') } sub radius { $_[0]->cnf('radius') }
+sub border { $_[0]->cnf('border') } sub bg { $_[0]->cnf('bg') }
+sub margin { $_[0]->cnf('margin') }
+sub bb_l { $_[0]->min_x-$_[0]->margin } sub bb_r { $_[0]->max_x+$_[0]->margin }
+sub bb_b { $_[0]->min_y-$_[0]->margin } sub bb_t { $_[0]->max_y+$_[0]->margin }
+sub bb_w { $_[0]->bb_r-$_[0]->bb_l } sub bb_h { $_[0]->bb_t-$_[0]->bb_b }
+
+## -----------------------------------
+## Setters..
+## -----------------------------------
+
+sub add_point { my $s = shift; push @{$s->{'points'}}, [@_]; $s }
+sub add_points { my $s = shift; push @{$s->{'points'}}, @_; $s }
+sub add_line { my $s = shift; push @{$s->{'lines'}}, [@_]; $s }
+sub add_lines { my $s = shift; push @{$s->{'lines'}}, @_; $s }
+sub set_size { my $s = shift; $s->{'size'} = [@_]; $s }
+sub set_range { my $s = shift; $s->{'range'} = [@_]; $s }
+sub set_scale { my $s = shift; $s->{'scale'} = $_[0]; $s }
+
+sub update_cnf {my($s,%p)=@_; exists$s->{'cnf'}{$_}&&($s->{'cnf'}{$_}=$p{$_}) for keys %p; $s}
+
+## --------------------------------------------------------
+## The real code - no direct references to the $s hash!
+## --------------------------------------------------------
+
+sub load_data {
+ local $/=undef, my( $s, $fn, @t ) = @_;
+ open my $ifh, '<', $fn;
+ 4==(@t = split /,/) ? ($s->add_line(@t)) : 2==@t ? ($s->add_point(@t)) : (warn "Error: $_")
+ for grep { /\S/ } split /\s+/, <$ifh>;
+ close $ifh;
+ $s;
+}
+
+sub compute_range {
+ my $s = shift;
+ my( $min_x,$min_y ) = my( $max_x,$max_y ) = @{ $s->points ? ($s->points)[0] : ($s->lines)[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 $s->points, map { ($_, [$_->[2],$_->[3]]) } $s->lines;
+
+ $s->set_range( $min_x, $min_y, $max_x, $max_y );
+}
+
+sub add_line_of_best_fit {
+ my $s = shift;
+ my $sx = my $sy = my $sxy = my $sxx = 0, my $n = $s->points;
+
+ $sx += $_->[0], $sxy += $_->[0]*$_->[1], $sy += $_->[1], $sxx += $_->[0]*$_->[0] for $s->points;
+
+ return $s->add_line( $sx/$n, $s->bb_b, $sx/$n, $s->bb_t ) unless $n*$sxx - $sx*$sx;
+
+ my $b = ( $n*$sxy-$sx*$sy ) / ( $n*$sxx - $sx*$sx );
+ my ( $a, $l, $r, $d, $t ) = ( ( $sy-$b*$sx ) / $n, $s->bb_l, $s->bb_r, $s->bb_b, $s->bb_t );
+ my ( $l_y, $r_y ) = ( $a+$b*$l, $a+$b*$r );
+ my ( $l_x, $r_x ) = ( $l_y<$d ? (($l_y=$d)-$a)/$b : $l_y>$t ? (($l_y=$t)-$a)/$b : $l,
+ $r_y<$d ? (($r_y=$d)-$a)/$b : $r_y>$t ? (($r_y=$t)-$a)/$b : $r );
+
+ $s->add_line( $l_x, $l_y, $r_x, $r_y );
+}
+
+sub calculate_image_size {
+ my $s = shift;
+
+ my( $W, $H, $w, $h ) = ( $s->max_w, $s->max_h, $s->bb_w, $s->bb_h );
+
+ ( $w/$h > $W/$H ) ? $s->set_size($W,$h/$w*$W)->set_scale($w/$W)
+ : $s->set_size($w/$h*$H,$H)->set_scale($h/$H);
+}
+
+sub render_with_best_fit { shift->compute_range->add_line_of_best_fit->_render }
+sub render { shift->compute_range->_render }
+
+sub _render {
+ my $s = shift;
+ my $m = $s->calculate_image_size->margin;
+
+ sprintf $SVG_TMPL, $s->height, $s->width, $s->bb_l, $s->bb_b, $s->bb_w, $s->bb_h,
+ $s->border, $s->scale, $s->bg, $s->bb_l, $s->bb_b, $s->bb_w, $s->bb_h, -$s->bb_b-$s->bb_t,
+ $s->color, $s->stroke * $s->scale, join( $JOIN, map { sprintf $LN_TMPL, @{$_} } $s->lines ),
+ $s->fill, join( $JOIN, map { sprintf $PT_TMPL, @{$_}, $s->radius*$s->scale } $s->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..628a23970b
--- /dev/null
+++ b/challenge-165/james-smith/perl/functional.pl
@@ -0,0 +1,150 @@
+#!/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 transform="scale(1,-1) translate(0,%s)">
+ <g stroke="%s" stroke-width="%s">
+ %s
+ </g>
+ <g fill="%s">
+ %s
+ </g>
+ </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
+ -$min_y-$max_y,
+ $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..2b2abad63a
--- /dev/null
+++ b/challenge-165/james-smith/perl/object-orientated.pl
@@ -0,0 +1,31 @@
+#!/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' => 960, # 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 '<!DOCTYPE html>
+<html>
+ <head>
+ <title>Examples: %s</title>
+ </head>
+ <body>
+ <h1>SVG examples: %s</h1>
+ %s
+
+ </body>
+</html>', $0, $0, join "\n ", map { join "\n", '', "<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