#!/usr/bin/perl # # # xfigchart.pl -- produces bar charts in xfig format # Copyright (C) 1999 - 2001 Michael Pronath # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. require 5; use POSIX qw(ceil); use strict; use Getopt::Long; my $blowfak; my $dpi = 1200; my $depth = 375; my $xfdepth = 500; my $angle = 25.0; my $deltax = 1500; my $floorxovershoot = 0.2; my $boxwidth = 1200; my @chartdata = (); my $firstboxxpos = 2400; my $floorreldepth = 0.75; my $scalediff = 1.0; my $threedflag = 0; my $wallflag = 1; my $wallmaxy = 0; my $version = "0.6"; my $vergetflag = 0; my $result = GetOptions( "3d", \$threedflag, "angle=f", \$angle, "boxwidth=i", \$boxwidth, "deltax=i", \$deltax, "depth=i", \$depth, "dpi=i", \$dpi, "firstboxxpos=f", \$firstboxxpos, "floorxovershoot=f", \$floorxovershoot, "floorreldepth=f", \$floorreldepth, "scalediff=f", \$scalediff, "wallmaxy=f", \$wallmaxy, "walls!", \$wallflag, "v", \$vergetflag, "xfdepth=i", \$xfdepth); if ($vergetflag) { print "xfigchart.pl version $version\n" . "(c) 1999 - 2001 Michael Pronath \n"; exit; } my $floordepth = int($depth * $floorreldepth); # size of wallfloor my $walldepth = -1; # xfig layer depth $angle = $angle * 0.017453292; # degree -> radians conversion readchartdata(); writefileheader(); blowupvalues(@chartdata); if ($threedflag == 1) { make3dstackedbarrow(@chartdata); if ($wallflag == 1) { make3dwalls(@chartdata); } } else { makestackedbarrow(@chartdata); if ($wallflag == 1) { makewalls(@chartdata); } } sub writefileheader { print "#FIG 3.2\n" . "Portrait\nCenter\nInches\nA4\n100.00\nSingle\n-2\n$dpi 2\n"; } sub readchartdata { my $numcols = -1; while () { s/^\s*//; if ( /^\#/ ) { next; } s/\#.*//; s/\s*$//; my $thisline = []; @{$thisline} = split /\s+/; if ( $numcols == -1 ) { $numcols = @{$thisline}; } else { if ( $numcols != @{$thisline} ) { die "Lines of different length!"; } } push @chartdata, $thisline; } } sub makewalls { my $numbars = @_; my $numcols = @{$_[0]}; my $width = int($deltax * ($numbars-1) + $boxwidth + 2*$floorxovershoot*$boxwidth) ; my $xpos = $firstboxxpos - $floorxovershoot*$boxwidth; my $ypos = 4800; my $height = 2000; print "6 $xpos " . int($ypos-$height+0.5) . " " . int($xpos+$width+0.5) . " " . int($ypos+0.5) . "\n"; writepolygon( [$xpos, $ypos, $xpos, $ypos - $height, $xpos+$width, $ypos - $height, $xpos+$width, $ypos, $xpos,$ypos], 0, 5, $walldepth+1,1); my $i; for ($i = $scalediff; $i < $wallmaxy; $i += $scalediff) { my $liney0 = 4800 - $i*$blowfak; writeline([ $xpos, $liney0, $xpos+$width, $liney0], $walldepth,1); } print "-6\n"; } sub make3dwalls { my $numbars = @_; my $numcols = @{$_[0]}; my $width = $deltax * ($numbars-1) + $boxwidth + 2*$floorxovershoot*$boxwidth ; my $xpos = $firstboxxpos - $floordepth - $floorxovershoot*$boxwidth; my $floorheight = $floordepth*sin($angle)/cos($angle); my $barheight = $depth*sin($angle)/cos($angle); my $ypos = 4800 + $floorheight; my $height = 2000; my $sidewallyovershoot = $scalediff / $wallmaxy; my $upperend = $ypos - $height - $floorheight - $barheight; print "6 $xpos " . int($upperend+0.5) . " " . int($xpos+$depth+$floordepth+$width+0.5) . " " . int($ypos+0.5) . "\n"; # bottom plane writepolygonsplit( [ $xpos, $ypos, $xpos+$depth+$floordepth, $ypos - $floorheight - $barheight, $xpos+$depth+$floordepth+$width, $ypos - $floorheight - $barheight, $xpos+$width, $ypos, $xpos, $ypos ], 0, 5, $walldepth+1 ,1); # left sidewall writepolygonsplit( [$xpos, $ypos, $xpos, $ypos - $height, $xpos+$depth+$floordepth, $upperend, $xpos+$depth+$floordepth, $ypos - $floorheight - $barheight, $xpos, $ypos ], 0, 5, $walldepth+1 ,1); # backplane writepolygonsplit( [$xpos+$depth+$floordepth, $ypos - $floorheight - $barheight, $xpos+$depth+$floordepth, $upperend, $xpos+$depth+$floordepth+$width, $upperend, $xpos+$depth+$floordepth+$width, $ypos - $floorheight - $barheight, $xpos+$depth+$floordepth, $ypos - $floorheight - $barheight], 0, 5, $walldepth+1,1); my $i; for ($i = $scalediff; $i < $wallmaxy ; $i += $scalediff) { my $liney0 = 4800 + $floorheight - $i*$blowfak; writeline([$xpos, $liney0, $xpos+$depth+$floordepth, $liney0-$floorheight - $barheight], $walldepth,1); writeline([$xpos+$depth+$floordepth, $liney0-$floorheight - $barheight, $xpos+$depth+$floordepth+$width, $liney0-$floorheight - $barheight], $walldepth,1); } print "-6\n"; } sub blowupvalues { my @dataset = @_; my $row; if ($wallmaxy == 0) { foreach $row (@dataset) { my $value = sumuparray(@{$row}); if ( $value > $wallmaxy ) { $wallmaxy = $value; } } } $blowfak = 2000.0/((ceil($wallmaxy/$scalediff))*$scalediff); foreach $row (@dataset) { my $i; for ($i = 0; $i < @{$row}; $i++) { $row->[$i] = $row->[$i] * $blowfak; } } } sub make3dstackedbarrow { my $xpos = $firstboxxpos; my $numcols = @{$_[0]}; my $numbars = @_; my $numboxes = $numbars * $numcols; my $columndepth = $xfdepth + ($numboxes >> 1); $walldepth = $columndepth + $numcols; my $heights; foreach $heights (@_) { my ($fronts, $tops, $rightsides, $bounds) = makestacked3dbar( $xpos, 4800-sumuparray(@{$heights}), $heights, $boxwidth, $depth, $angle); my $i; my $color = 1; my $curdepth = $columndepth; for ( $i = 0; $i < $numcols; $i++) { print "6 "; my $b; foreach $b (@{$ {$bounds}[$i]}) { print int($b+0.5) . " "; } print "\n"; writepolygon($ {$fronts}[$i], $color, 20, $curdepth ,1); writepolygon($ {$tops}[$i], $color, 30, $curdepth ,1); writepolygon($ {$rightsides}[$i], $color, 10, $curdepth ,1); print "-6\n"; $color = $color % 7 +1; $curdepth++; } $columndepth -= $numcols; $xpos += $deltax; } } sub makestackedbarrow { my $heights; my $xpos = $firstboxxpos; $walldepth = $xfdepth +1; foreach $heights (@_) { my $y0 = 4800 - sumuparray(@{$heights}); my ($fronts, $bounds) = makestackedbar( $xpos, $y0, $heights, $boxwidth); my $i; my $color = 1; my $numcols = @{$heights}; for ( $i = 0; $i < $numcols; $i++) { writepolygon($fronts->[$i], $color, 20, $xfdepth, 1); $color = $color % 7 +1; } $xpos += $deltax; } } sub sumuparray { my $x; my $sum = 0; foreach $x (@_) { $sum += $x; } return $sum; } # just like writepolygon, but the border is a separate set of lines # whereas the polygon itself has no border sub writepolygonsplit { my ($coordlist,$fillcolor,$shade,$depth,$thickness) = @_; my $numpoints = @{$coordlist} / 2; writepolygon($coordlist,$fillcolor,$shade,$depth,0); my $i; for ($i = 0; $i < $numpoints-1; $i++) { my $ncoordlist = [$ {$coordlist}[2*$i],$ {$coordlist}[2*$i+1], $ {$coordlist}[2*$i+2],$ {$coordlist}[2*$i+3]]; writeline($ncoordlist,$depth,$thickness); } } sub writepolygon { my ($coordlist,$fillcolor,$shade,$depth,$thickness) = @_; # [ x1, y1, x2, y2, ..., xn, yn ], fillcolor, shade, depth my $numpoints = @{$coordlist} / 2; print "2 3 0 $thickness 0 $fillcolor $depth 0 $shade 1.0 1 1 1 0 0 $numpoints\n"; my $i; for ($i = 0; $i < $numpoints; $i++) { print int($ {$coordlist}[2*$i]+0.5) . " " . int($ {$coordlist}[2*$i +1]+0.5) . " "; } print "\n"; } sub writeline { my ($coordlist,$depth,$thickness) = @_; # [ x1, y1, x2, y2, ..., xn, yn ], depth my $numpoints = @{$coordlist} / 2; print "2 1 0 $thickness 0 0 $depth 0 -1 1.0 1 1 1 0 0 $numpoints\n"; my $i; for ($i = 0; $i < $numpoints; $i++) { print int($ {$coordlist}[2*$i]+0.5) . " " . int($ {$coordlist}[2*$i +1]+0.5) . " "; } print "\n"; } sub makerectangle { my ($x1, $y1, $x2, $y2) = @_; return [ $x1, $y1, $x1, $y2, $x2, $y2, $x2, $y1, $x1, $y1 ]; } sub makebar { my ($x0, $y0, $height, $width ) = @_; my $bounds = [$x0,$y0,$x0+$width,$y0+$height]; return ( makerectangle(@{$bounds}), $bounds); } sub makestackedbar { my ($x0, $y0, $heights, $width) = @_; my $height; my $fronts = []; my $bounds = []; my $cury0 = $y0; foreach $height (@{$heights}) { my ($front,$bound) = makebar($x0,$cury0,$height,$width); $cury0 += $height; push @{$fronts},$front; push @{$bounds},$bound; } return ($fronts,$bounds); } sub make3dbar { my ( $x0, $y0, $height, $width, $depth, $angle ) = @_; my $ddx = $depth; my $ddy = $depth*sin($angle)/cos($angle); my $front = makerectangle( $x0, $y0, $x0+$width, $y0+$height); my $top = [ $x0, $y0, $x0+$ddx, $y0-$ddy, $x0+$ddx+$width, $y0-$ddy, $x0+$width, $y0, $x0, $y0 ]; my $rightside = [ $x0+$width, $y0, $x0+$width+$ddx, $y0-$ddy, $x0+$width+$ddx, $y0-$ddy+$height, $x0+$width, $y0+$height, $x0+$width, $y0 ]; my $bounds = [ $x0, $y0-$ddy, $x0+$width+$ddx, $y0+$height ]; return ($front, $top, $rightside, $bounds); } sub makestacked3dbar { my ($x0,$y0,$heights, $width, $depth, $angle ) = @_; # $heights is an arrayref now; my $height; my $cury0 = $y0; my $fronts = []; my $tops = []; my $rightsides = []; my $bounds = []; foreach $height (@{$heights}) { my ($front, $top, $rightside, $bound) = make3dbar($x0,$cury0,$height,$width,$depth,$angle); $cury0 += $height; push @{$fronts}, $front; push @{$tops}, $top; push @{$rightsides}, $rightside; push @{$bounds}, $bound; } return ( $fronts, $tops, $rightsides, $bounds); } __END__ =head1 NAME xfigchart.pl - make charts in XFig format =head1 SYNOPSIS B S<[-3d]> S<[-angle I]> S<[-boxwidth I]> S<[-deltax I]> S<[-depth I]> S<[-dpi I]> S<[-firstboxxpos I]> S<[-floorxovershoot I]> S<[-floorreldepth I]> S<[-scalediff I]> S<[-[no]walls]> S<[-xfdepth I]> B S<-v> =head1 DESCRIPTION B takes a table of figures from stdin and writes a bar chart in xfig format to stdout. The options are =over 4 =item -3d produce 3d bar charts instead of 2d ones (which is the default). =item -angle I (3d only) the view angle in degree. The parallel projection of the 3d view is determined by I and I. default value: 25. =item -boxwidth I The width (in dots) of the bars. The default value is 1200, which is 1 inch if the default value of I (1200) is not changed. =item -deltax I The x-distance (in dots) between the left sides of the bars, i.e. the bars touch each other if I = I. default value: 1500. =item -depth I (3d only) The other parameter to the 3d parallel projection. I is the x-distance (in dots) between the right side of a bar and its backside's right side. default value: 375. =item -dpi I Dots per inch. default value: 1200. =item -firstboxxpos I This value is the x-position (in dots), where the left border of the first box starts. Default value: 2400 (i.e. 2 inch) =item -floorxovershoot I This value determines, how much the floor should overlap the bars at the right and left side, relative to the boxwidth. default value: 20% = 0.2. =item -floorreldepth I (3d only) How much the floor should overlap the bars at the front side. This is relative to the depth of the bars. Default: 75% = 0.75. =item -scalediff I This is the distance between the lines on the wall. I is in "real" units, not in dots or inches. default value: 1.0 =item -[no]walls -nowalls keeps xfigchart.pl from drawing walls and lines on them. default value: -walls =item -v Display version number and exit =item -xfdepth I The xfig depth values of all components are approximately this value. Change this if you want to combine more than one chart by xfig merge. default value: 500 =back =head1 AUTHOR Michael Pronath =cut