Peter
Peter Campbell Smith

SVG picture and least squares

Weekly challenge 165 — 16 May 2022

Week 165 - 16 May 2022

Task 1

Task — Scalable vector graphics

Using Scalable Vector Graphics (SVG) accept a series of points and lines in the following format, one per line, in arbitrary order:

Point: x,y
Line: x1,y1,x2,y2

and create the corresponding graphic.

Examples


Example 1:
53,10
53,10,23,30
23,30

Analysis

SVG has always been a popular technology with me. Amongst other things, I've used it for making scale drawings for alterations to my house, and for making easily scalable or re-colourable logos for websites. For images which don't contain photographic material SVG files are often tiny in size when compared to compressed bitmaps like JPEG or PNG. But although most web browsers can render them, it's quite unusual to find them in use.

In my experience, Inkscape is the best GUI editor for generating SVG graphics from scratch, though its output is rather more verbose than that from the Perl SVG module.

Both tasks this week require us to draw lines and points in a 2-dimensional space. Once you've mastered the syntax required, the task is fairly simple, although there is one catch to be surmounted, which is that SVG's geometry is 'left-handed'. The normal convention for Cartesian (ie x and y) coordinates is that the positive direction of the y axis is 90 degrees anti-clockwise from the positive x axis. So in a rectangular container, if you place the origin (x = y = 0) at the bottom left, every point within the container has a positive x and y.

SVG uses the convention that the positive direction of the y axis is 90 degrees clockwise from that of the x axis. That means that in order to get the positive x and y valued points within a rectangle, the origin has to be at the top left, which is upside down to the normal convention.

So, I chose to write my code so that, in SVG terms, it transforms every (x, y) to (x, height - y) where 'height' is the height of the container, which draws the diagram in the way we were all taught at school.

Try it 

Try running the script with any input:



example:
[20,20],[20,20,20,60],[20,60],[20,40,40,40],[40,20], [40,60],[40,20,40,60],[60,20],[60,20,60,60],[60,60], [80,20],[80,30],[80,30,80,60],[80,60]

The bounding box is 100 x 100 so keep your points within
the range 0 - 100.

Script


#!/usr/bin/perl

# Peter Campbell Smith - 2022-05-16
# PWC 165 task 1

use v5.28;
use strict;
use warnings;
use utf8;
use SVG;

my ($grp, $grp1, $grp2, $i, $object, $size, $svg, $x1, $x2, $y1, $y2, @objects);

# data
@objects = ([20, 20], [20, 20, 20, 50], [20, 50], [20, 50, 50, 75], [50, 75],
    [80, 50], [20, 50, 80, 50], [50, 75, 80, 50], [80, 20], [80, 50, 80, 20],
    [80, 20, 20, 20]);

# create bounding box
$size = 100;
$svg = SVG->new(width => $size, height => $size);

# define groups
$grp1 = $svg->group(id => 'line_group',
    style => {stroke => 'blue', fill => 'blue'});
$grp2 = $svg->group(id => 'dot_group',
    style => {stroke => 'orange', fill => 'orange'});

# process lines and points - nb - SVG space has y axis zero at top
# so we have to use (height - y) to show in a right-handed space
for $object (@objects) {
    $i ++;
    
    # a point
    if (scalar(@$object) == 2) {
        ($x1, $y1) = @$object;
        $grp2->circle(id => "dot$i", cx => $x1, cy => $size - $y1, r => 2);
        
    # a line
    } elsif (scalar(@$object) == 4) {
        ($x1, $y1, $x2, $y2) = @$object;
        $grp1->line(id => "line$i", 
            x1 => $x1, y1 => $size - $y1, 
            x2 => $x2, y2 => $size - $y2);      
    }
}

# export the svg code
open OUT, '>utf8:', qq[/var/www/pwc/public_html/images/svg_165_1.svg] or die $!;
print OUT $svg->xmlify;
close OUT;

Output