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 )