内容


使用 Perl 和 GD 创建自定义数据制图工具

自动交付专业外观的图形和可视化

Comments

本文不仅仅是一个 “用 GD 构建饼图” 教程,它还将介绍可用于在动态生成的图中创建有用的新层次的技术。为公司会议或实时企业目录数据打造自动生成的图表。为图表添加可读的文本注释,这将比标准的饼图交付更多的信息。

多年来,人们一直结合使用 Perl 与 GD 模块来为 Web 站点和演示文稿创建有用的动态图表和图形。本文将展示如何使用这些工具根据企业需求近乎实时地创建具有专业外观和高信息量的聚焦图表。

使用 GD::Pie.pm 模块的修订版本,我们将构建自定义图,然后使用采集到的数据点来创建指示符和文本注释。此外,我们将展示一种方法来对几何图元进行分层并使用透明的传递(pass-through)在图表内创建独立的信息通道。

本文介绍的技术将使您可以创建复杂的图表,并使用 GD 工具作为创建您自己的程序的起点。

要求

硬件

2000 年后制造的所有 PC 应当能够提供足够的能力来编译和运行代码。分层和注释处理都属于 CPU 密集型任务,因此如果计划在 Web 服务器中动态创建大量这类图表,使用多个快速处理器将提供更良好的用户体验。

软件

假定所选的操作环境包含最新版本的 Perl,您将需要下载并安装 GD Perl 模块。您可能还考虑使用诸如 feh 之类的快速而简单的图像查看器应用程序。

构造超级饼图库

buildPie.pl 程序

高级图表创建过程的第一步是构建相对来说未经修饰的饼图。GD::Pie 模块的用户可能十分熟悉它在标绘时对数据和标签的要求。GD::Pie 模块的标绘子程序期望获得一批数组(标签和数据值)。例如,清单 1 将展示在本文中用于构建图表的输入文件。

清单 1. test.pie.data 示例数据文件
#50#Alpha#
#30#Baker#
#20#Charlie#
#60#Delta#
#10#Echo Echo#

buildPie.pl 程序将读取此数据文件并使用 GD::Pie 的修订版本生成简单的图表。清单 2 将展示 buildPie.pl 程序。

清单 2. buildPie.pl
#!/usr/bin/perl -w
# buildPie.pl - build a simple pie chart using modified GD::Pie.pm
use strict;
use lib qw(./);
use customPie;

if( @ARGV != 1 ){ die "specify an output filename" }

my( @name, @ratio ) = ();
my $filename = $ARGV[0];

chomp($filename);
while(<STDIN>){
  my @parts = split "#", $_;
  push @name, " "; #add the names later
  push @ratio, $parts[1];
}#while stdin

my $mygraph = GD::Graph::pie->new(600,600);
# colors of the pie slices
$mygraph->set( dclrs => [ "#A8A499","#685E3F","#6C7595","#D8E21F",
                          "#D19126","#B5B87D","#B7C8E2","#DFE3E1" ] );
# color of pie divisors
$mygraph->set( accentclr => '#0000ff');
$mygraph->set( '3d' =>'0');

my @togr = ( [@name], [@ratio] );

my $myimage = $mygraph->plot(\@togr) or die $mygraph->error;

open(IMG, "> $filename.pie.png") or die $1;
  binmode IMG;
  print IMG $myimage->png;
close(IMG);

这段代码将读取如 test.pie.data 文件中所示的数据文件。此时只记录数据值,因为稍后将在图表生成过程中应用标签。读取数据值之后,将创建一块 600x600 的画布,并且定义了饼图颜色。注:颜色选择十分重要,因为这些颜色稍后将用于透明的创建过程。

关闭 3-D 绘制并且指定蓝色为饼图分区颜色后,将开始绘制图表。使用 cat test.pie.data | perl buildPie.pl step1 > triangle.vertices 命令运行程序。您可以使用图像查看器来查看 step1.pie.png 图形,但是此时没有什么能够比一个常规饼图更令人印象深刻。需要注意的要点是 STDOUT 正被重定向到 triangle.vertices 文件。三角形指示符的这些顶点都是在 GD::Pie 的修订版本中创建的。

customPie.pm

GD::Pie 模块将执行大部分工作,包括计算饼图分区的比例、为各分区设定标签并用颜色填充饼图。以下修改仅更改饼图分区线的宽度,并计算围绕饼图分区边缘的多边形的近似位置以供稍后进行注释。使用 cp /usr/lib/perl5/site_perl/5.8.5/GD/Graph/pie.pm ./customPie.pm 命令把现有的 pie.pm 文件复制到本地目录中。

        # Draw the lines on the front of the pie
        $self->{graph}->line($xe, $ye, $xe, $ye + $self->{pie_height}, $ac)
            if in_front($pa) && $self->{'3d'};

要使修改生效,请用以下代码替换上面所示的 customPie.pm 中的代码行(从行号 270 开始)。

清单 3. customPie.pm 新行
        # Give the pie slices a nice wide divider
        $self->{graph}->setThickness(5);
        $self->{graph}->line($self->{xc}, $self->{yc}, $xe, $ye, $ac);

        # inward facing point of the triangle
        my ($newxe, $newye) = cartesian(
                3 * $self->{w}/6.5, ($pa+$pb)/2,
                $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
            );

        # first corner
        my $tangle = (($pa+$pb)/2) + 5;
        my  ($corn1xe, $corn1ye) = cartesian(
                3 * $self->{w}/5.5, $tangle,
                $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
            );

        # second corner 
        $tangle = (($pa+$pb)/2) - 5;
        my ($corn2xe, $corn2ye) = cartesian(
              3 * $self->{w}/5.5, $tangle,
              $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
        );

        print "polygon: $newxe,$newye $corn1xe,$corn1ye $corn2xe,$corn2ye\n";

五个像素的线分区稍后将被重写为图像中饼图分区之间的白色分隔符。随后将获得三条粗略定义的角边,用于定义指向图像中心的近似的等边三角形。为了便于对图像进行修改和注释,将输出三角形坐标以便稍后应用到图像中。

compositePieIndicators.pl —— GD 注释

虽然设置了 buildPie.pl 程序来使用修改过的 pie.pm 创建简单的饼图,但是进一步的修改和注释工作都将在 compositePieIndicators.pl 中完成。回想一下,您使用 buildPie.pl 创建了简单的饼图,如图 1 中的步骤 1 所示。我们需要获得该饼图并且把它转换为图 1 的步骤 2 图形中。

图 1. 创建饼图的步骤
创建饼图的步骤
创建饼图的步骤

在读入步骤 1 的饼图后,将在步骤 1 的图表中绘制由三角形顶点文件指定的多边形。将使新饼图的边变得平滑,把饼图的径向分区涂上白色,并且饼图的内部都会成为白色。遵循此过程,将在图的内部绘制黑色的圆环,并且文本指示符将被添加到各个饼图分区附近。

让我们把 compositePie.pl 程序划分为三大块并详细介绍如何完成这些步骤。

清单 4. compositePieIndicators.pl 的第 1 部分 —— 读取图像、多边形坐标
#!/usr/bin/perl -w
# compositePieIndicators.pl - build super pie graphics
use strict;
use GD;
die "usage: compositePieIndicators.pl image_file " .
    "vertices_file <main title> <sub title> data_file output_file"
unless @ARGV == 6;

my $pieImg = newFromPng GD::Image( $ARGV[0] );
my $white = $pieImg->colorAllocate(255,255,255);
my @textLocs = ();

# draw the pie slices
open( POLYFILE, "$ARGV[1]" ) or die "can't open vertices file $ARGV[1]";
  while( my $polyLine = <POLYFILE> )
  {
    my (undef, @polyParts ) = split " ", $polyLine;
    my $poly = new GD::Polygon;

    my $textPos = 0;
    for ( @polyParts ){
      my( $px, $py ) = split ",";
      $poly->addPt( $px,$py );

      next unless $textPos == 0;
      push @textLocs, "$px, $py";
      $textPos = 1;

    }#for each polygon part

    $pieImg->filledPolygon( $poly, $white );
  }#polyCoords
close(POLYFILE);

compositePieIndicators.pl 程序的第 1 部分将涉及用法声明并读入基本的饼图图像,以及 triangle.vertices 文件。在第 10 行中定义白色对于确保各个绘制命令按预期完成是十分重要的。在某些情况下,GD 将要求显式确立颜色,并且通过定义白色,我们可以确保图像将被正确处理。

下一步是像 triangle.vertices 文件中指定的一样绘制所有三角形。多边形的各个点都将被添加到第 23 行的临时图形中,并且如果它是多边形中的第一个点,则把其位置记录为注释该部分的文本的位置。

清单 5. compositePieIndicators.pl 的第 2 部分 —— 绘制图形、文本
my $whiteImg = new GD::Image(1000,1000);
my $secWhite = $whiteImg->colorAllocate(255,255,255);
my $black = $whiteImg->colorAllocate(0,0,0);
$whiteImg->copy( $pieImg, 200,200, 0,0, 600,600 );

# now build a squarish border to hide the drawing artifacts
my $circleImg = new GD::Image( 600,600 );
my $circlewhite = $circleImg->colorAllocate(255,255,255);
my $circleblack = $circleImg->colorAllocate(0,0,0);

$circleImg->filledArc(300,300, 580,580, 0,360,$black);
$circleImg->transparent($circleblack);

$whiteImg->copy($circleImg,200,200,0,0,600,600);

# make the pie radial divisor arms white
$whiteImg->fill(500,500,$secWhite);

# draw center white circle area for text
$whiteImg->filledArc( 500,500, 440,440, 0,360, $secWhite );

# draw black border around text area - with line arc
$whiteImg->setThickness(4);
$whiteImg->arc( 500,500, 425,425, 0,360, $black );

# main title
$whiteImg->stringFT( $black, './Vera.ttf',
  45,0,350,470, "$ARGV[2]" );

# sub title
$whiteImg->stringFT( $black, './Vera.ttf',
  32,0,370,560, "$ARGV[3]" );

compositePieIndicators.pl 的第 2 部分的第一步将创建一个新的 1,000 像素的正方形。在定义完一些颜色之后,基本的饼图图像将被装入到新图像的中心(通过创建围绕整个图像的 200 像素的边界,有效地把 600x600 图像扩展为 1,000x100 图像而不用拉伸原图像)。这将允许进行扩展文本注释使其保留在图像区域内,并且不需要进行裁剪。

为了切去简单圆形图案中饼图分区的前缘,将创建一张中间带有透明圆形的图像。将此图像复制到现有图像中后,通过使用一条简单的指定为从图像的中心(所有分区半径相交的位置)开始的填充命令,径向分区半径将被渲染成白色。接下来,将绘制一个白色圆形擦掉饼图的中心,并且绘制另一条弧线来提供内部圆形与外围饼图分区之间的可视边界。然后在图像的中心绘制图形的主标题和子标题。Vera.ttf 字体文件可在 下载 压缩包中获得,或者您可以使用系统中可用的另一种 TrueType 字体。

清单 6 将展示为各个饼图分区添加注释所需的代码。

清单 6. compositePieIndicators.pl 的第 3 部分 —— 为扇区进行注释
# draw the labels for the pie slices
open( DATAFILE, "$ARGV[4]" ) or die "can't open data file ";

  my $arrayPos = 0;
  while( my $line = <DATAFILE> )
  {
    my( undef, undef, $text ) = split '#', $line;
    my( $posx, $posy ) = split ',', $textLocs[$arrayPos];
    print "text $text at pos $textLocs[$arrayPos] \n";

    # the kludge text alignment zone
    $posx += 200;
    $posy += 200;
    my $ptSize = 18;

    if( $posx <= 500 ){
      if( $posy <= 500 ){
        # nudge left, up if upper left
        $posx -= 70;
        $posy -= 20;
      }else{
        # nudge left, down if upper left
        $posx -= 100;
        $posy += 20;
      }
    }else{
      if( $posy <= 500 ){
        # nudge left, down if upper right
        $posy -= 20;
        $posx += 10;
      }else{
        # nudge right, down if lower right
        $posy += 20 ;
        $posx += 25 ;
      }
    }#if left or right of image

    # nudge text down if right enough
    if( $posy > 600 ){ $posy += 10 }

    $whiteImg->stringFT( $black, './Vera.ttf',
      $ptSize,0,$posx,$posy, "$text" );

    $arrayPos++;
  }#while data file line
close(DATAFILE);

open( TILEOUT,"> $ARGV[5]") or die "can't write out file ";
  print TILEOUT $whiteImg->png;
close(TILEOUT);

回想一下 compositePieIndicators.pl 的第 1 部分,文本的位置被定义为 triangle.vertices 文件中某一行的第一组多边形坐标。这对 x,y 值被保存到 textLocs 数组中并且为数据文件中定义的相应的文本注释行提取它。结果证明精确地放置文本(围绕圆形的周边垂直并水平对齐文本)有些复杂,因此在第 80 行至第 107 行中显示了一种简单的试错方法。虽然有点笨拙,但这是一种非常简单的方法,能够使每条文本注释显示在与其相应饼图分区适当靠近的位置。

当文本坐标采集组装完成后,文本将被写到图像中,并且图 1 的步骤 2 所示的图像将被输出。使用 perl compositePieIndicators.pl test.pie.png triangle.vertices Managers 'Top 5 People' test.pie.data test.compout.png 命令运行 compositePieIndicators.pl

进一步修改

您现在已经能够自动生成高效的专业饼图,可以将其插入到任意数目的应用程序。还有一种层次的修改可以为令人厌倦的旧饼图提供大量有趣特性:即平铺分区。

创建一张由许多小贴图构成的大图像,这些小图像包含与饼图分区相关的主题。例如,如果知道 Manager Delta 能够与 Linux® 结合使用,则可以创建一张多种形式的 Tux 的背景平铺图像,并用平铺的图像替换 Delta 饼图分区颜色。继续在整个饼图分区内执行此过程,每个组成部分都可以被替换为不同的平铺图像集。图 2 展示了此过程的完整示例。

图 2. 拼装饼图
拼装饼图
拼装饼图

依次将每个饼图分区设为透明,并且随后将拼成的背景图像应用到饼图中。清单 7 展示了生成图 2 中的图像所使用的命令序列(有关自动生成背景拼图的更多信息,请参阅 参考资料)。

清单 7. 平铺图像饼图分区组成
convert -transparent "#A8A499" test.compout.png lev1.png
composite -compose over lev1.png backgrounds/back_big_linux.png lev2.png
convert -transparent "#685E3F" lev2.png lev3.png
composite -compose over lev3.png backgrounds/back_big_db2.png lev4.png
convert -transparent "#6C7595" lev4.png lev5.png
composite -compose over lev5.png backgrounds/back_big_diskdrive.png lev6.png
convert -transparent "#D8E21F" lev6.png lev7.png
composite -compose over lev7.png backgrounds/back_bigLinux.png lev8.png 
convert -transparent "#D19126" lev8.png lev9.png
composite -compose over lev9.png backgrounds/back_big_micro.png completed_pie.png

结束语

利用 GD 和 Perl 的力量,您可以把各种数据和图像链接到一起来创建有助于提升应用程序视觉兴趣的复杂图形。使用此程序将使您可以用比简单的 GD::Pie 更丰富的信息密度来表示图形。考虑将使用地理数据图、人员图像或 Web 站点的点击计数数据创建的图结合起来。您可以用本文提供的代码创建许多有趣的图,并且还可以使用这些技术进一步拓展使用 GD 的处理选项的能力。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=226129
ArticleTitle=使用 Perl 和 GD 创建自定义数据制图工具
publish-date=06072007