FAQ-OMatic
view release on metacpan or search on metacpan
lib/FAQ/OMatic/statgraph.pm view on Meta::CPAN
} else {
$duration = int($duration);
}
if (($duration/$resolution) > $FAQ::OMatic::Appearance::graphWidth) {
# no point in gathering too many data points!
$resolution = int($duration / $FAQ::OMatic::Appearance::graphWidth);
}
# guarantee at least two items in @mydata
$duration = $resolution if ($duration <= 0);
# collect the data of interest.
my $day;
my $earliestday = FAQ::OMatic::Log::adddays($today, -$duration);
my @mydata = (); # The data point itself
my @myindex = (); # The day "number" (-$duration .. 0) of that data point
# (i.e. where it goes on the graph)
my $i;
for ($day = $today, $i=0;
$day ge $earliestday;
$day = FAQ::OMatic::Log::adddays($day, -$resolution), $i-=$resolution) {
my $item = new FAQ::OMatic::Item($day.".smry", $FAQ::OMatic::Config::metaDir);
if (defined $item->{$property}) {
unshift @mydata, $item->{$property};
unshift @myindex, $i;
} else {
unshift @mydata, 0;
unshift @myindex, $i;
}
}
# days run from negative (history) to zero (today)
$minx = $myindex[0];
my $maxx = $myindex[$#myindex];
my $maxy;
($miny, $maxy) = (+10000000, -1000000);
foreach my $i (@mydata) {
$miny = min($miny, $i);
$maxy = max($maxy, $i);
}
# autorange y
$miny = 0; #Want bottom of graph to be at 0
$maxy = max($maxy,1); # make sure range doesn't collapse to 0
my $intervaly;
($miny, $maxy, $rangey, $intervaly) = rerange($miny, $maxy);
$miny = 0; # autoranging may have nudged miny to -1, so fix it
$rangey = $maxy-$miny; # thus fix range, too.
# get x interval automatically
$rangex = $maxx - $minx;
my ($intervalx,$unitsx) = autoIntervalDays($rangex);
$width = $FAQ::OMatic::Appearance::graphWidth;
$height = $FAQ::OMatic::Appearance::graphHeight; # from FAQ::OMatic::FaqConfig
my $borderx = 30;
my $bordery = 10;
my $image = new GD::Image ($width, $height);
my $transparent = $image->colorAllocate(255, 255, 255);
my $framefill = $image->colorAllocate(255, 255, 255);
my $grid = $image->colorAllocate(192, 192, 192);
my $datafill = $image->colorAllocate(0, 0, 132);
my $datacolor = $image->colorAllocate(240, 0, 0);
my $frame = $image->colorAllocate(128, 0, 0);
my $labels = $image->colorAllocate(10, 88, 0);
# draw the graph in this order, back-to-front:
# transparent (whole image background)
# framefill
# grid
# datafill
# datacolor
# ticks (grid color)
# frame
# labels
# transparent
$image->filledRectangle(0, 0, $width, $height, $transparent);
# framefill
# note these are globals so calc[xy] can see them
($basex, $basey) = ($borderx, $bordery);
($sizex, $sizey) = ($width-2*$borderx, $height-3*$bordery);
$image->filledRectangle($basex, $basey, $basex+$sizex, $basey+$sizey,
$framefill);
# grid
for ($i=$miny; $i<=$maxy; $i+=$intervaly) {
$image->line(calcx($minx), calcy($i),
calcx($miny), calcy($i), $grid);
}
# datafill - plot data region
# I use many little polys instead of one big one because GD has
# a bug wherein it fails to fill polygons correctly when three points
# share the same y value.
for ($i=0; $i<scalar(@mydata)-1; $i++) {
my $poly = new GD::Polygon;
$poly->addPt(calcx($myindex[$i]), calcy($miny));
$poly->addPt(calcx($myindex[$i]), calcy($mydata[$i]));
$poly->addPt(calcx($myindex[$i+1]), calcy($mydata[$i+1]));
$poly->addPt(calcx($myindex[$i+1]), calcy($miny));
#$image->filledPolygon($poly, $datafill);
}
# ticks - provide scale for image
my $tickwidth = 5;
for ($i=$maxx; $i>$minx; $i-=$intervalx) {
$image->line(calcx($i), calcy($miny),
calcx($i), calcy($miny)-$tickwidth, $grid);
my $label = $i/$intervalx;
$label = "Today" if ($label == 0);
$image->string(gdTinyFont, calcx($i)-2, calcy($miny)+1,
$label, $labels);
}
my $title=$params->{'title'} || $property;
$image->string(gdSmallFont, calcx($minx+$rangex*0.5)-length($title)*6/2,
calcy($maxy)-12, $title, $labels);
for ($i=$miny; $i<=$maxy; $i+=$intervaly) {
$image->line(calcx($minx), calcy($i),
calcx($minx)+$tickwidth, calcy($i), $grid);
$image->line(calcx($maxx), calcy($i),
calcx($maxx)-$tickwidth, calcy($i), $grid);
my $label = $i;
$label =~ s/000$/k/;
$image->string(gdTinyFont, $borderx-length($label)*5-2,
calcy($i)-3, $label, $labels);
}
$image->string(gdTinyFont, calcx($minx+$rangex*0.5)-length($unitsx)*5/2,
calcy($miny)+10, $unitsx, $labels);
# datacolor - highlight top edge of data
for ($i=0; $i<scalar(@mydata)-1; $i++) {
$image->line(calcx($myindex[$i]), calcy($mydata[$i]),
calcx($myindex[$i+1]), calcy($mydata[$i+1]), $datacolor);
}
# frame - outline the plot box
$image->rectangle($basex, $basey, $basex+$sizex, $basey+$sizey, $frame);
# announce final value (should show up over ticks)
my $value = $mydata[$#mydata];
if (not $value =~ m/\./) {
# for big numbers
$value =~ s/(?!^|,)(\d\d\d)$/,$1/; # add commas for readability
$value =~ s/(?!^|,)(\d\d\d),/,$1,/; # of big numbers. This'll keep you
$value =~ s/(?!^|,)(\d\d\d),/,$1,/; # til your trillionth hit. :v)
} else {
# for little numbers
$value = sprintf "%.2f", $value;
}
my @spots = ( [-1, 0,$framefill],
[-1,-1,$framefill],
[ 0,-1,$framefill],
[ 1,-1,$framefill],
[ 1, 0,$framefill],
[ 1, 1,$framefill],
[ 0, 1,$framefill],
[-1, 1,$framefill],
[ 0, 0,$datafill] );
foreach my $spot (@spots) {
$image->string(gdTinyFont, calcx($maxx)-5*length($value)+18+$spot->[0],
max(calcy($mydata[$#mydata])-9+$spot->[1],$basey+2),
$value, $spot->[2]);
}
# make image background transparent so it'll look nicer sitting
# in the browser
$image->transparent($transparent);
# send image
print $image->$image_type();
}
1;
( run in 1.513 second using v1.01-cache-2.11-cpan-39bf76dae61 )