#! /usr/bin/perl -w use strict; use Image::Magick; # this program ought to be written in C # read data in. format is "NAME X Y". our %x = (); our %y = (); our @names = (); while(<>) { my @s = split(/\s+/, $_); push(@names, $s[0]); $x{$s[0]} = int($s[1]) + 32; $y{$s[0]} = int($s[2]) + 32; } our %nx = %x; our %ny = %y; my $e; our $width = 768; our $height = 640; our $xpadding = 3; our $ypadding = 2; our $image = Image::Magick->new(size => "${width}x${height}"); $image->Read('xc:white'); # find out how big all the text is my %w = (); my %h = (); get_font_metrics($image); # rearrange things to have less overlap for my $n (1 .. 30) { print STDERR "iteration $n "; my $improvement = 0; for my $name (@names) { $improvement += reduce_overlap($name); } print STDERR "...improved by $improvement\n"; last if $improvement < 1; } # construct the image draw($image); # display it $e = $image->Display(); die "$e" if "$e"; ######################################################################## sub min { my ($x, $y) = @_; return $x < $y ? $x : $y; } sub max { my ($x, $y) = @_; return $x > $y ? $x : $y; } # find out how heavily N would be overlapped if it were at (X, Y) # pixels that fall outside the canvass are counted as overlapped sub find_overlap { my ($n, $x, $y) = @_; my $o = 0; my $xt = $x + $w{$n}; my $yt = $y + $h{$n}; # how much falls outside the image $o += $h{$n} * -$x if $x < 0; $o += $w{$n} * -$y if $y < 0; $o += $h{$n} * ($xt - $width) if $xt > $width; $o += $w{$n} * ($yt - $height) if $yt > $height; for my $m (@names) { next if $n eq $m; # skip self my $nxt = $nx{$m} + $w{$m}; my $nyt = $ny{$m} + $h{$m}; next if($x >= $nxt || $y >= $nyt || $xt <= $nx{$m} || $yt <= $ny{$m}); # skip non-overlapping things my $ix = max($x, $nx{$m}); my $iy = max($y, $ny{$m}); my $ixt = min($xt, $nxt); my $iyt = min($yt, $nyt); $o += ($ixt - $ix) * ($iyt - $iy); } return $o; } # move NAME in whichever of the 8 compass points reduces its overlap # with other names, and return how many pixels of overlap were saved. sub reduce_overlap { my $name = shift; my $best_offset = [0,0]; my $current_overlap = find_overlap($name, $nx{$name}, $ny{$name}); my $best_overlap = $current_overlap; # up and right are favoured as we're less likely to overlap the # connecting line that way. explicitly counting the pixels overlapping # with it would be better but just setting the ordering is much # cheaper. for my $offset ( [1,-1], # up right [1,0], # right [0,-1], # up [-1,-1], # up left [1,1], # down right [0,1], # down [-1,0], # left [-1,1], # down left ) { my $overlap = find_overlap($name, $nx{$name} + $offset->[0], $ny{$name} + $offset->[1]); if($overlap < $best_overlap) { $best_offset = $offset; $best_overlap = $overlap; } } $nx{$name} += $best_offset->[0]; $ny{$name} += $best_offset->[1]; return $current_overlap - $best_overlap; } # collect text size sub get_font_metrics { my $image = shift; for my $name (@names) { my ($x_ppem, $y_ppem, $ascender, $descender, $width, $height, $max_advance) = $image->QueryFontMetrics(text => $name, font => 'Generic.ttf', pointsize => 10); $w{$name} = $width + 2 * $xpadding; $h{$name} = $height + 2 * $ypadding; } } # draw the image sub draw { my $image = shift; my $e; for my $name (@names) { my $x = $x{$name}; my $y = $y{$name}; my $nx = $nx{$name} + $xpadding; my $ny = $ny{$name} + $ypadding; $e = $image->Draw(primitive => 'line', points => "$x,$y $nx,$ny", strokewidth => 1, stroke => 'black'); die "$e" if "$e"; my $xx = $x + 1; $e = $image->Draw(primitive => 'circle', points => "$x,$y $xx,$y", stroke => 'red', fill => 'red'); die "$e" if "$e"; $e = $image->Annotate(text => $name, fill => 'black', font => 'Generic.ttf', geometry => "+$nx+$ny", pointsize => 10); die "$e" if "$e"; } }