Create custom data charting tools using Perl and GD

Deliver professional-looking graphs and visualizations automatically

Create professional-looking charts for data visualization using Perl and GD. Move beyond standard pie charts to incorporate annotations, indicators, and layering for enhanced informational delivery.

Share:

Nathan Harrington, Programmer, EMC

Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies.



24 April 2007

Also available in Chinese

More than just another "pie-graphs-with-GD" tutorial, this article describes techniques you can use to create new levels of usefulness in your dynamically generated charts. Cook up some automatically generated graphs for your organizational meetings or live enterprise directory data. Annotate the charts with readable text that delivers more information than the standard pie chart.

Perl and the GD modules have been used together for many years to create useful dynamic charts and graphics for Web sites and presentations. This article shows how to use these tools to create professional-looking, focused, and highly informative charts in near real time for your organizational needs.

Using a modified version of the GD::Pie.pm module, we will build custom charts, then use the acquired data points to create indicators and text annotations. Additionally, we show a method of layering geometric primitives and using transparent pass-throughs to create separate channels of information inside the charts.

The techniques described allow you to create complex charts and use the GD tools as starting points for creating your own programs.

Requirements

Hardware

Any PC manufactured after 2000 should provide enough horsepower for compiling and running the code. The layering and annotation processes are CPU-intensive, so if you plan to create many of these charts on the fly on a Web server, faster and multiple processors offer better user experiences.

Software

Assuming your operating environment of choice contains a recent version of Perl, you'll need to download and install the GD Perl module. You might also consider using a fast and simple image viewer application like feh.


Building the super-pie base

buildPie.pl program

The first component of the advanced chart creation process is to build a relatively unmodified pie chart. Users of the GD::Pie module may be familiar with its requirements for data and labels when plotting. The plot subroutine of the GD::Pie module expects an array of arrays (labels and data values). For example, Listing 1 shows an input file that will be used to build the charts in this article.

Listing 1. test.pie.data example data file
#50#Alpha#
#30#Baker#
#20#Charlie#
#60#Delta#
#10#Echo Echo#

The buildPie.pl program reads this data file and generate a simple chart using a modified version of GD::Pie. Listing 2 shows the buildPie.pl program.

Listing 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);

This code reads the data file as shown in the test.pie.data file. Only the data values are recorded at this point, as the labels will be applied later in the chart-generation process. After the data values have been read, a 600x600 charting canvas is created, and the pie colors are defined. Note that the color selection is important because these colors will be used later in the process for transparency creation.

The chart is then plotted after turning 3-D drawing off and specifying blue for the pie divisors color. Run the program with the command cat test.pie.data | perl buildPie.pl step1 > triangle.vertices. You can use your image viewer to look at the step1.pie.png graphic, but it won't be any more impressive than a regular pie chart at this point. The key to note is the fact that STDOUT is being redirected to the triangle.vertices file. These vertices for the triangle indicators are created in our modified version of GD::Pie.

customPie.pm

The GD::Pie module does the bulk of the work of calculating proportional pie slices, labeling the slices and filling the pies with color. The modifications below only change the width of the pie divisor lines and compute the approximate locations of a polygon centered around the edges of the pie slices for later annotation. Copy the existing pie.pm file into your local directory with the command cp /usr/lib/perl5/site_perl/5.8.5/GD/Graph/pie.pm ./customPie.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'};

For our modifications to be effective, replace the lines in customPie.pm shown above (starting at line number 270) with the code below.

Listing 3. customPie.pm new lines
        # 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";

The five-pixel line divisor will be overwritten later as a white separator between pie slices in the image. Three roughly defined corner edges are then acquired that define an approximately equilateral triangle pointed at the center of the image. To facilitate modification and annotation of the image, the triangle coordinates are printed out to be applied to the image later.


compositePieIndicators.pl -- GD annotation

While the buildPie.pl program is set up to create a simple pie graph with our modified pie.pm, the further work of modification and annotation is done in compositePieIndicators.pl. Recall that you used buildPie.pl to create a simple pie chart like that shown under Step 1 in Figure 1. We want to take that pie chart and transform it into the Step 2 graphic from Figure 1.

Figure 1. Step pie diagrams
Step pie diagrams

After reading in the Step 1 pie chart, polygons specified by the triangle vertices file are drawn on the Step 1 chart. The edge of the new pie chart is smoothed, the pie radial divisors are colored white, and the interior of the pie chart is made white. Following this process, a black ring is drawn around inside of the graph, and text indicators are added near the various pie slices.

Let's break apart the compositePie.pl program into three main chunks and cover how each of these steps is done in detail.

Listing 4. compositePieIndicators.pl Section 1 -- Reading image, polygon coordinates
#!/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);

Section 1 of the compositePieIndicators.pl program covers the usage statement and reading in the base pie image, as well as the triangle.vertices file. Defining the color white on line 10 is important to ensure that the various drawing commands complete as expected. GD requires explicit color establishment in some cases, and by defining white, we can ensure that the image is processed correctly.

The next step is to draw all the triangles as specified in the triangle.vertices file. Each point of the polygon is added to the temporary shape on line 23, and if it's the first point in the polygon, its location is recorded as the placement for the text annotating that piece.

Listing 5. compositePieIndicators.pl Section 2 -- Drawing shapes, text
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]" );

The first part of Section 2 of compositePieIndicators.pl creates a new image of 1,000 pixels square. After defining some colors, the base pie image is loaded into the center of the new image (by creating a 200-pixel border around the entire image, effectively expanding a 600x600 image to 1,000x100 without stretching the original). This is essential for allowing extended text annotations to remain within the image area without clipping.

To cut the leading edge of the pie slices in a simple circle pattern, an image is created with a transparent circle in the middle. After copying this onto the existing image, the radial divisor arms are colored white with a simple fill command specified to begin at the center of the image, where all of the divisor arms meet. Next, the center of the pie is erased by drawing a white circle, and another arc is drawn to provide a visual border between the interior circle and the peripheral pie slices. The main title and subtitle for the graph are then drawn in the center of the image. The Vera.ttf font file is available in the Downloads package, or you can use another TrueType font available on your system.

Listing 6 shows the code required to annotate the various pie slices.

Listing 6. compositePieIndicators.pl Section 3 -- Annotating slices
# 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);

Recall from Section 1 of compositePieIndicators.pl that the location of the text is defined as the first set of polygon coordinates on a line from the triangle.vertices file. This x,y value is stored in the textLocs array and is extracted for the respective text annotation line as defined in the data file. It turns out that accurately placing text aligned vertically and horizontally around the perimeter of a circle is somewhat complicated, so a simple trial-and-error approach is shown on lines 80-107. Although a bit clumsy, it's s simple way to have each of the text annotations appear in a reasonably close proximity to their appropriate pie slices.

Once the text coordinate acquisition kludge is complete, the text is written to the image, and the image as shown under Step 2 in Figure 1 is written out. Run compositePieIndicators.pl with the command perl compositePieIndicators.pl test.pie.png triangle.vertices Managers 'Top 5 People' test.pie.data test.compout.png.


Further modifications

You now have the capability for automatically generated, efficient, and professional pie chart graphs you can plug in to any number of applications. There's another layer of modification that can provide a lot of interest to the boring old pie chart: tiled slices.

Create a large image made up of smaller tiles that comprise a subject related to the pie slice. For example, if we know Manager Delta works extensively with Linux®, we can create a background tile image of many forms of Tux and replace the Delta pie slice colors with the tiled images. Continuing on through the pie slices, each component piece can be replaced with a different set of tile images. Figure 2 shows a completed example of what this can look like.

Figure 2. Mosaic pie diagrams
Mosaic pie diagrams

Each pie sliced is, in turn, set to transparent, and the mosaic background image is then applied to the pie graph. Listing 7 shows the sequence of commands used to produce the image in Figure 2. (See Resources for more information about generating background mosaic images automatically.)

Listing 7. Tiled image pie slice compositing
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

Conclusion

Using the power of GD and Perl, you can link various data and images together to create sophisticated charts that will help bring visual interest to your applications. Using this program allows you to present charts with much greater informational density than is available with plain GD::Pie. Consider combining the charts you've created with geographical data plots or personnel images, or hit count data from your Web site. You can create many interesting diagrams with the code presented here, and you can use these techniques for further advancing your abilities with GD's processing options.


Download

DescriptionNameSize
Scripts and filesos-perlgdchart_01.zip41KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=210043
ArticleTitle=Create custom data charting tools using Perl and GD
publish-date=04242007