+# Blosxom Plugin: postgraph -*- perl -*-
+# Author: Todd Larason jtl@molehill.org and Nilesh Chaudhari http://nilesh.org/
+# Version: 0+1i
+# Blosxom Home/Docs/Licensing: http://www.raelity.org/blosxom
+# Categories plugin Home/Docs/Licensing:
+# http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/Graph/
+# parts Copyright (c) 2002 Nilesh Chaudhari http://nilesh.org/
+
+package postgraph;
+
+# --- Configuration Variables ---
+$destination_dir ||= ''; # must be configured
+$graph_start_day ||= "19000101";
+$graph_num_bars ||= 24;
+$graph_width ||= 200;
+$graph_height ||= 100;
+$barcolor ||= "#f5deb3"; # the bars themselves
+$bordercolor ||= "#83660f"; # the borders of the bars
+$outlinecolor ||= "#83660f"; # the outline of the box around the graph
+$boxcolor ||= "#fffbf0"; # the inside of the box
+$textcolor ||= "#4f0000"; # the various text bits
+
+$bt_width ||= 400;
+$bt_height ||= 30;
+$bt_linecolor ||= '#ffffff';
+$bt_textcolor ||= '#757575';
+$bt_fillcolor ||= '#757575';
+$bt_bordercolor ||= '#757575';
+$bt_padding = 5 unless defined $bt_padding;
+$bt_show_text = 1 unless defined $bt_show_text;
+
+$debug_level = 2 unless defined $debug_level;
+# ------------------------------------------------------------
+\f
+use File::stat;
+use GD;
+use GD::Graph::bars;
+use GD::Graph::colour qw/:convert :colours/;
+use strict;
+use vars qw/$destination_dir $graph_start_day $graph_num_bars $graph_width
+ $graph_height $barcolor $bordercolor $outlinecolor $boxcolor $textcolor
+ $bt_width $bt_height $bt_linecolor $bt_textcolor $bt_fillcolor
+ $bt_bordercolor $bt_padding $bt_show_text $debug_level/;
+\f
+my $package = "postgraph";
+my $timestamp_file = "$blosxom::plugin_state_dir/.$package.timestamp";
+my $last_timestamp;
+\f
+sub debug {
+ my ($level, @msg) = @_;
+
+ if ($debug_level >= $level) {
+ print STDERR "$package debug $level: @msg\n";
+ }
+}
+\f
+# utility funcs
+sub max {
+ my $max = 0;
+ foreach (@_) {
+ $max = $_ if $_ > $max;
+ }
+ return $max;
+}
+sub round {
+ return int($_[0] + .5);
+}
+sub hex2rgb {
+ my ($hex) = @_;
+
+ my ($r, $g, $b) = ($hex =~ m!\#(..)(..)(..)!);
+ return (hex($r),hex($g),hex($b));
+}
+\f
+my @bucketcount;
+my $secs_per_bucket = 86400 / $graph_num_bars;
+my $mins_per_bucket = 1440 / $graph_num_bars;
+my $buckets_per_hour = $graph_num_bars / 24;
+
+sub graph_add {
+ my ($hour, $min, $sec) = @_;
+ my $time = $hour * 3600 + $min * 60 + $sec;
+ my $bucket = $time / $secs_per_bucket;
+ $bucketcount[$bucket]++;
+}
+
+sub build_graph {
+ my @labels;
+ for (my $hour = 0; $hour < 24; $hour++) {
+ for (my $min; $min < 60; $min += $mins_per_bucket) {
+ push @labels, $hour; # sprintf("%d:%02d", $hour, $min);
+ }
+ }
+ my $graph = GD::Graph::bars->new($graph_width, $graph_height);
+ $graph->set(
+# title => 'Posts per hour of day',
+ y_max_value => max(@bucketcount) + 2,
+ x_label_skip => $buckets_per_hour * 4,
+# bgclr => add_colour($bgcolor), # does nothing?
+ fgclr => add_colour($outlinecolor),
+ boxclr => add_colour($boxcolor),
+ labelclr => add_colour($textcolor),
+ axislabelclr => add_colour($textcolor),
+ legendclr => add_colour($textcolor),
+ valuesclr => add_colour($textcolor),
+ textclr => add_colour($textcolor),
+ dclrs => [add_colour($barcolor)],
+ borderclrs => [add_colour($bordercolor)],
+ );
+ $#bucketcount = $graph_num_bars;
+ my $gd = $graph->plot([\@labels, \@bucketcount]);
+ open IMG, "> $destination_dir/graph.png";
+ binmode IMG;
+ print IMG $gd->png;
+ close IMG;
+ debug(2, "build graph");
+}
+\f
+# blogtimes directly derived from BLOGTIMES by Nilesh Chaudhari
+# -- http://nilesh.org/mt/blogtimes/
+
+my @monthname=('null', 'JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE',
+ 'JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER');
+
+my @bt_entry_times;
+sub bt_add {
+ my ($hour, $min) = @_;
+ push @bt_entry_times, $hour*60 + $min;
+}
+sub build_blogtimes {
+ my ($year, $month) = @_;
+ my $txtpad = $bt_show_text ? gdTinyFont->height : 0;
+ my $scale_width = $bt_width + ($bt_padding*2);
+ my $scale_height = $bt_height + ($bt_padding*2) + ($txtpad*2);
+ my $img = GD::Image->new($scale_width,$scale_height);
+ my $white = $img->colorAllocate(255,255,255);
+ my $linecolor = $img->colorAllocate(hex2rgb($bt_linecolor));
+ my $textcolor = $img->colorAllocate(hex2rgb($bt_textcolor));
+ my $fillcolor = $img->colorAllocate(hex2rgb($bt_fillcolor));
+ my $bordercolor = $img->colorAllocate(hex2rgb($bt_bordercolor));
+ my $line_y1 = $bt_padding + $txtpad;
+ my $line_y2 = $bt_padding + $txtpad + $bt_height;
+ $img->transparent($white);
+ $img->rectangle(0, 0, $scale_width-1, $scale_height-1, $bordercolor);
+ $img->filledRectangle($bt_padding, $line_y1, $bt_padding + $bt_width,
+ $line_y2, $fillcolor);
+ my ($line_x,$i);
+ foreach $i (@bt_entry_times) {
+ $line_x = $bt_padding + (round(($i/1440) * $bt_width));
+ $img->line($line_x, $line_y1, $line_x, $line_y2, $linecolor);
+ }
+ # Shut off text if width is too less.
+ if ($bt_show_text) {
+ if ($bt_width >= 100) {
+ my $ruler_y = $bt_padding + $txtpad + $bt_height + 2;
+ my $ruler_x;
+ for ($i = 0; $i <= 23; $i += 2) {
+ $ruler_x = $bt_padding + round($i * $bt_width/24);
+ $img->string(gdTinyFont,$ruler_x,$ruler_y,"$i",$textcolor);
+ debug(5, 'tinyfont',$ruler_x,$ruler_y,"$i",$textcolor);
+ }
+ $img->string(gdTinyFont, $bt_padding + $bt_width-2,
+ $ruler_y, "0", $textcolor);
+ my $caption_x = $bt_padding;
+ my $caption_y = $bt_padding-1;
+ my $caption = "B L O G T I M E S $monthname[$month] $year";
+ $img->string(gdTinyFont, $caption_x, $caption_y,
+ $caption, $textcolor);
+ debug(5, 'tinyfont', $caption_x, $caption_y, $caption, $textcolor);
+ } else {
+ my $ruler_y = $bt_padding + $txtpad + $bt_height + 2;
+ my $ruler_x;
+ for ($i = 0; $i <= 23; $i += 6) {
+ $ruler_x = $bt_padding + round($i * $bt_width/24);
+ $img->string(gdTinyFont,$ruler_x,$ruler_y,"$i",$textcolor);
+ }
+ $img->string(gdTinyFont, $bt_padding + $bt_width - 2,
+ $ruler_y, "0", $textcolor);
+ my $caption_x = $bt_padding;
+ my $caption_y = $bt_padding-1;
+ my $caption = "$month $year";
+ $img->string(gdTinyFont, $caption_x, $caption_y,
+ $caption, $textcolor);
+ }
+ }
+ open IMG, "> $destination_dir/blogtimes.png" or die "$!";
+ binmode IMG;
+ print IMG $img->png or die "$!";
+ close IMG;
+ debug(2, "build blogtimes");
+}
+\f
+sub filter {
+ my ($pkg, $files) = @_;
+ my $latest_story = (sort {$b <=> $a} values %$files)[0];
+ return 1 if $latest_story <= $last_timestamp;
+ my @now = localtime;
+ my $now_month = $now[4] + 1;
+ my $now_year = $now[5] + 1900;
+
+ debug(1, "updating graph");
+
+ foreach (keys %{$files}) {
+ my @date = localtime($files->{$_});
+ my $mday = $date[3];
+ my $month = $date[4] + 1;
+ my $year = $date[5] + 1900;
+ graph_add($date[2], $date[1], $date[0])
+ if (sprintf("%04d%02d%02d", $year, $month, $mday) ge
+ $graph_start_day);
+ bt_add($date[2], $date[1])
+ if ($year == $now_year and $month == $now_month);
+ }
+ build_graph();
+ build_blogtimes($now_year, $now_month);
+}
+
+sub start {
+ return 0 unless $destination_dir;
+ $last_timestamp = -e $timestamp_file ? stat($timestamp_file)->mtime : 0;
+ my $fh = new FileHandle;
+ if (!$fh->open(">$timestamp_file")) {
+ debug(0, "Couldn't touch timestamp file $timestamp_file");
+ return 0;
+ }
+ $fh->close;
+ debug(1, "$package enabled");
+ return 1;
+}
+1;
+
+=head1 NAME
+
+Blosxom Plug-in: graph
+
+=head1 SYNOPSIS
+
+Purpose: creates graphs showing the time of day stories are posted
+
+Files created:
+ * $destination_dir/graph.png -- bar graph showing number of posts per
+ period of time (default: hour) since $graph_start_day (default: 19000101)
+ * $destination_dir/blogtimes.png -- a vertical-line form of a scatterplot,
+ showing the posting time of all stories posted this month
+
+=head1 VERSION
+
+0+1i
+
+1st test release
+
+=head1 AUTHOR
+
+Todd Larason <jtl@molehill.org>, http://molelog.molehill.org/
+
+portions (the "BLOGTIMES" chart style) based on code by Nilesh Chaudhari;
+see http://nilesh.org/mt/blogtimes/. Nilesh gets credit, but direct bugs
+to Todd Larason.
+
+=head1 BUGS
+
+None known; address bug reports and comments to me or to the Blosxom
+mailing list [http://www.yahoogroups.com/groups.blosxom].
+
+=head1 Customization
+
+=head2 Configuration variables
+
+There are many configuration variables controlling height, width and colors
+of the two graphs; see the configuration variable section for those.
+
+There's also:
+
+C<$destination_dir>, the directory where the output files will be created;
+this must be configured.
+
+C<$graph_start_day>, the earlist date to consider stories from for the bar
+graph. If you've converted from another weblogging package and lost time of
+day information on converted stories, you probably want to set this to when
+you started using Blosxom.
+
+C<$graph_num_bars> is the number of bars to create in the bargraph form;
+if it isn't evenly divisible by 24, things probably act weird -- that isn't
+well tested.
+
+The C<bt_> variables control the "BLOGTIMES" style chart; the others the
+bar graph. For the bargraph, the width and height is the size of the overall
+image; for the blogtimes, it's for the graph portion only -- padding is added
+for a border and optionally for text. One or the other of these is likely to
+change in a future version to provide consistency.
+
+=head1 Caching
+
+The images are only recreated when they appear to be out of date; a timestamp
+file is maintained for this. To force them to be regenerated, remove
+$plugin_state_dir/.postgraph.timestamp.
+
+=head1 LICENSE
+
+this Blosxom Plug-in
+Copyright 2003, Todd Larason
+"BLOGTIME" Portions
+Copyright (c) 2002 Nilesh Chaudhari http://nilesh.org/
+
+(This license is the same as Blosxom's and the original BLOGTIME's)
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+