jeiku has asked for the wisdom of the Perl Monks concerning the following question:

Hi,
I'm having some problems creating a line graph with GD::Graph::Lines.

My graph data consists of 3 things. Temperature, radiation and a timeline.
My trouble is the timeline.
Should I have a static set of numbers (like 00-24 for a 24 hour clock) on the X axis?
The graph will be adding data every 1 minute and I want the graph to plot a bit more on a minute, but I'm not sure how to acomplish this.
I hope this makes sense and any help is much appreciated! :)

Replies are listed 'Best First'.
Re: Help with GD::Graph
by samtregar (Abbot) on May 11, 2006 at 07:10 UTC
    Showing ("00" .. "24") will be easy but I doubt it'll look very good. It also won't work as soon as you have more than 24 hours to display, or when you don't have enough space for all the numbers. I had a similar problem a few months back, here's how I solved it:

    # format a set of dates for display on the X axis sub dates_to_header { my ($self, $dates) = @_; my @header = ("") x scalar @$dates; # choose a format based on the length of the range my $delta = $dates->[-1]->subtract_datetime($dates->[0]); my $months = $delta->delta_months; my $days = $delta->delta_days + ($months * 30); # rough guess is + ok here my ($format, $number); if ($months >= 5 and $months <= 11) { $format = "%b"; # month abrev $number = $months + 1; } elsif ($days <= 1) { $format = "%l %p"; # HH AM/PM $number = 5; } elsif ($days <= 6) { $format = "%a"; # weekdays, mark them all $number = @$dates; } elsif ($dates->[0]->year eq $dates->[-1]->year) { $format = "%m/%d"; # MM/DD $number = 5; } else { $format = "%D"; # MM/DD/YYx $number = 5; } if ($number == @$dates) { # marking all bars return [map { $_->strftime($format) } @$dates]; } # find the optimal spacing between labels my $spacing = @$dates / ("$number.0" - 1); # place a label at the edges my @spots = (0, $#$dates); # if there are odd numbers of dates to place, put one in the cente +r my $center = int(@$dates / 2); push(@spots, $center) if $number % 2; # place the remaining dates around the center, following the spaci +ng for (1 .. (($number - @spots) / 2)) { push(@spots, $center + int($spacing * $_)); push(@spots, $center - int($spacing * $_)); } # render the date at each chosen spot foreach my $spot (@spots) { $header[$spot] = $dates->[$spot]->strftime($format); } return \@header; }

    That subroutine takes a reference to an array of DateTime objects representing the data-points on the graph. It then applies a bunch of heuristics to come up with some reasonable labels for the X axis, leaving enough space for them to render successfully.

    Hope that helps.

    -sam

Re: Help with GD::Graph
by zentara (Cardinal) on May 11, 2006 at 12:09 UTC
    Maybe you are looking for the X_labels_vertical option?
    #!/usr/bin/perl use strict; use warnings; use GD::Graph::lines; my $data_ref = [ ['2004-01-08','2004-01-09','2004-01-10'], # X-values [ 15.9, 10.7, 15.8 ], # Y-value 1 [ 18,18,18 ], #Y-value 2 reference line [ 12,12,12 ], ]; my $graph = GD::Graph::lines->new(400, 300); $graph->set( transparent => '0', bgclr => 'lgray', boxclr => 'white', fgclr => 'white', x_label => 'Time', x_labels_vertical => 1, x_label_skip => 50, y_label => 'Price', title => 'Some simple graph', y_max_value => 20, y_tick_number => 1, y_label_skip => 2, long_ticks => 0, # make a grid of all the ticks ) or die $graph->error; my $gd_image = $graph->plot($data_ref) or die $graph->error; open(OUTPUT, ">$0.png") or die "Can't open $0.png: $!\n"; print OUTPUT $gd_image->png(); close(OUTPUT);

    I'm not really a human, but I play one on earth. flash japh
      Thanks for the replies! Hmmm, not sure what I'm doing wrong now.. I tried that but the graph is kinda screwed.
      #!/usr/bin/perl use GD::Graph::lines; use strict; my $i = ""; open(DATA, "newdata.txt") or die "Cannot open for reading: $!\n"; my @newdata = <DATA>; close(DATA); my @graph_data = [ ['May 12', 'May 13'], [], [], [] ]; for($i = 0; $i < scalar($#newdata + 1); $i++) { my ($celcius, $radiation, $time) = split(/ /, $newdata[$i]); $graph_data[1][$i] = $celcius; $graph_data[2][$i] = $radiation; $graph_data[3][$i] = $time; } my $mygraph = GD::Graph::lines->new(600, 300); $mygraph->set( x_label => 'Time', y_label => 'Value', title => 'Temperature and Radiation', x_labels_vertical => 1, x_label_skip => 50, line_types => [1, 1], line_width => 2, dclrs => ['blue', 'green'], ) or warn $mygraph->error; $mygraph->set_legend_font(GD::gdMediumBoldFont); $mygraph->set_legend('Temperature', 'Radiation'); my $myimage = $mygraph->plot(\@graph_data) or die $mygraph->error; open(GRAPH, ">graph.png"); print GRAPH $myimage->png; close(GRAPH);
      newdata.txt:
      36 85 13:37 36 86 13:38
      Man I suck at Perl :/
        You are on the right track, just making some dereferencing mistakes, and the big x_label_skip value. I'll post a better example later, if you don't figure it out.

        I'm not really a human, but I play one on earth. flash japh
        Try this. I will say, that you are going to run into a problem of having too many x values for the width of the graph, then you need to adjust this x_label_skip value to condense it. A Tk canvas would be a nice way to do this. You could plot every value on a scrolled canvas, and scroll to the current time(or time of interest). Of course if it's for the web, Tk won't help.
        #!/usr/bin/perl use warnings; use GD::Graph::lines; use strict; my @time; my @temp; my @rad; while(my $line = <DATA>){ chomp $line; my ($celcius, $radiation, $time) = split(/ /, $line); push @temp, $celcius; push @rad, $radiation; push @time, $time; } my $graph_data = [ [@time], # X-values [@rad], # Y-value 1 [@temp], #Y-value 2 reference line ]; my $mygraph = GD::Graph::lines->new(600, 300); $mygraph->set( transparent => '0', bgclr => 'white', boxclr => 'lgray', fgclr => 'white', x_label => 'Time', y_label => 'Value', title => 'Temperature and Radiation', x_labels_vertical => 1, # x_label_skip => 1, # line_types => [1, 1], line_width => 2, dclrs => ['blue', 'green'], ) or warn $mygraph->error; $mygraph->set_legend_font(GD::gdLargeFont); $mygraph->set_legend('Radiation','Temperature'); my $myimage = $mygraph->plot($graph_data) or die $mygraph->error; open(GRAPH, ">$0.png"); print GRAPH $myimage->png; close(GRAPH); __END__ 36 85 13:37 36 86 13:38 37 85 13:39 37 86 13:40 38 85 13:41 38 86 13:42 38 85 13:43 39 86 13:44 39 85 13:45 39 88 13:46 39 88 13:47 39 88 13:48 39 88 13:49 39 88 13:50 39 88 13:51 38 87 13:52 38 85 13:53 38 86 13:54 38 85 13:55 38 86 13:56 38 85 13:57 38 86 13:58 37 85 13:59 37 86 14:00 37 85 14:01 37 86 14:02 37 85 14:03 37 86 14:04 37 85 14:05 36 86 14:06 36 85 14:07 36 86 14:08

        I'm not really a human, but I play one on earth. flash japh
Re: Help with GD::Graph
by odha57 (Monk) on May 12, 2006 at 20:24 UTC
    The last post has pretty much laid out how I use GD::Graph. Here is an example to look at from something I use. The data I collect is on 5 minute intervals, but the graph can be from a day to several days wide. On a 600x480 graph, if I get more than 24 time values it looks very cluttered. You will want to use x_label_skip for this and x_labels_vertical to stand them on end. I hope it helps. One other thing to consider is using RRDTool. If your graphing app is going 7x24 it can collect your data, age off your data and then produce a graph for you. I use it all the time for just this type of thing. It should install fine on the machine you are using GD::Graph on. It uses all the same stuff. If you would like to see a couple of examples of putting in data and generating a graph I'd be happy to pass on a couple.
    # here is where we put a varying amount of timestamps across the graph + depending on how much time is involved. # if this isn't done, you can either see a graph with 2 timestamps or +with so many they are not legible. if ($elapsed_time > 604800){ $x_label_skip = 216; } elsif ($elapsed_time > 172800){ $x_label_skip = 36; } elsif ($elapsed_time > 86400){ $x_label_skip = 24; } elsif ($elapsed_time > 43200){ $x_label_skip = 12; } elsif ($elapsed_time > 3600){ $x_label_skip = 6; } else { $x_label_skip = 1; } foreach $entry (@timeline){ + # build an array of timestamps of date year $date = &SYSTIME($entry); push @dateline, $date; + # the epoch time is converted to a string and saved in a list } $poop = 'CDR data from '.$startdate.' - '.$enddate; + # a fancy title for our graph @data = (\@dateline, \@callAgent_A, \@callAgent_B); + # this is an array of arrays. The first one is the X axis + # which is the time, the others are the Y axis my $query = new CGI; + # start a new cgi object print $query->header(-type => 'image/png'); + # output a content type header for a png file my $data = GD::Graph::Data->new() or die GD::Graph::Data->error; $data->copy_from(\@data); + # this tells the graph to get the data from the data array my $my_graph = new GD::Graph::area(600,480); + # and this says what type (area) and it's size $my_graph->set( + # and set up all the particulars... r_margin => 1, x_label => 'Time', y_label => 'Hourly Attempts', title => $poop, y_min_value => 0, y_tick_number => 8, y_label_skip => 1, x_ticks => 1, x_label_skip => $x_label_skip, x_labels_vertical => 1, x_label_position => 1/2, cumulate => 1, transparent => 1, )or warn $my_graph->error; open (IMG, ">-") or die; + # open up an output file to STDOUT binmode IMG; $my_graph->set( dclrs => [ qw(lgreen lblue) ] ); + # here is the colors in the graph $my_graph->set_legend("Call Agent A","Call Agent B" ); + # and the legend text $my_graph->set_title_font(GD::Font->Giant); print IMG $my_graph->plot($data)->png(); + # and this plots the image out to a png format close IMG;