Revert rss20 to using $ENV{PATH_INFO} in self link.
[blosxom-plugins.git] / general / postgraph
blob2e7b92e8dbf14d6c96227b47054207b52108edcc
1 # Blosxom Plugin: postgraph                                        -*- perl -*-
2 # Author: Todd Larason jtl@molehill.org and Nilesh Chaudhari http://nilesh.org/
3 # Version: 0+1i
4 # Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
5 # Categories plugin Home/Docs/Licensing:
6 #   http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/Graph/
7 # parts Copyright (c) 2002 Nilesh Chaudhari http://nilesh.org/
9 package postgraph;
11 # --- Configuration Variables ---
12 $destination_dir ||= ''; # must be configured
13 $graph_start_day ||= "19000101";
14 $graph_num_bars  ||= 24;
15 $graph_width     ||= 200;
16 $graph_height    ||= 100;
17 $barcolor        ||= "#f5deb3";  # the bars themselves
18 $bordercolor     ||= "#83660f";  # the borders of the bars
19 $outlinecolor    ||= "#83660f";  # the outline of the box around the graph
20 $boxcolor        ||= "#fffbf0";  # the inside of the box
21 $textcolor       ||= "#4f0000";  # the various text bits
23 $bt_width       ||= 400;
24 $bt_height      ||= 30;
25 $bt_linecolor   ||= '#ffffff';
26 $bt_textcolor   ||= '#757575';
27 $bt_fillcolor   ||= '#757575';
28 $bt_bordercolor ||= '#757575';
29 $bt_padding       = 5 unless defined $bt_padding;
30 $bt_show_text     = 1 unless defined $bt_show_text;
32 $debug_level = 0 unless defined $debug_level;
33 # ------------------------------------------------------------
35 use File::stat;
36 use GD;
37 use GD::Graph::bars;
38 use GD::Graph::colour qw/:convert :colours/;
39 use strict;
40 use vars qw/$destination_dir $graph_start_day $graph_num_bars $graph_width
41     $graph_height $barcolor $bordercolor $outlinecolor $boxcolor $textcolor
42     $bt_width $bt_height $bt_linecolor $bt_textcolor $bt_fillcolor 
43     $bt_bordercolor $bt_padding $bt_show_text $debug_level/;
45 my $package        = "postgraph";
46 my $timestamp_file = "$blosxom::plugin_state_dir/.$package.timestamp";
47 my $last_timestamp;
49 sub debug {
50     my ($level, @msg) = @_;
52     if ($debug_level >= $level) {
53         print STDERR "$package debug $level: @msg\n";
54     }
57 # utility funcs
58 sub max {
59     my $max = 0;
60     foreach (@_) {
61         $max = $_ if $_ > $max;
62     }
63     return $max;
65 sub round {
66     return int($_[0] + .5);
68 sub hex2rgb {
69     my ($hex) = @_;
71     my ($r, $g, $b) = ($hex =~ m!\#(..)(..)(..)!);
72     return (hex($r),hex($g),hex($b));
75 my @bucketcount;
76 my $secs_per_bucket = 86400 / $graph_num_bars;
77 my $mins_per_bucket  = 1440 / $graph_num_bars;
78 my $buckets_per_hour = $graph_num_bars / 24;
80 sub graph_add {
81     my ($hour, $min, $sec) = @_;
82     my $time            = $hour * 3600 + $min * 60 + $sec;
83     my $bucket          = $time / $secs_per_bucket;
84     $bucketcount[$bucket]++;
87 sub build_graph {
88     my @labels;
89     for (my $hour = 0; $hour < 24; $hour++) {
90         for (my $min; $min < 60; $min += $mins_per_bucket) {
91             push @labels, $hour; # sprintf("%d:%02d", $hour, $min);
92         }
93     }
94     my $graph = GD::Graph::bars->new($graph_width, $graph_height);
95     $graph->set(
96 #               title        => 'Posts per hour of day',
97                 y_max_value  => max(@bucketcount) + 2,
98                 x_label_skip => $buckets_per_hour * 4,
99 #               bgclr        => add_colour($bgcolor),  # does nothing?
100                 fgclr        => add_colour($outlinecolor),
101                 boxclr       => add_colour($boxcolor),
102                 labelclr     => add_colour($textcolor),
103                 axislabelclr => add_colour($textcolor), 
104                 legendclr    => add_colour($textcolor),
105                 valuesclr    => add_colour($textcolor),
106                 textclr      => add_colour($textcolor), 
107                 dclrs        => [add_colour($barcolor)],
108                 borderclrs   => [add_colour($bordercolor)],
109                 );
110     $#bucketcount = $graph_num_bars;
111     my $gd = $graph->plot([\@labels, \@bucketcount]);
112     open IMG, "> $destination_dir/graph.png";
113     binmode IMG;
114     print IMG $gd->png;
115     close IMG;
116     debug(2, "build graph");
119 # blogtimes directly derived from BLOGTIMES by Nilesh Chaudhari
120 # -- http://nilesh.org/mt/blogtimes/
122 my @monthname=('null', 'JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE',
123                'JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER');
125 my @bt_entry_times;
126 sub bt_add {
127     my ($hour, $min) = @_;
128     push @bt_entry_times, $hour*60 + $min;
130 sub build_blogtimes {
131   my ($year, $month) = @_;
132   my $txtpad         = $bt_show_text ? gdTinyFont->height : 0;
133   my $scale_width    = $bt_width  + ($bt_padding*2);
134   my $scale_height   = $bt_height + ($bt_padding*2) + ($txtpad*2);
135   my $img            = GD::Image->new($scale_width,$scale_height);
136   my $white          = $img->colorAllocate(255,255,255);
137   my $linecolor      = $img->colorAllocate(hex2rgb($bt_linecolor));
138   my $textcolor      = $img->colorAllocate(hex2rgb($bt_textcolor));
139   my $fillcolor      = $img->colorAllocate(hex2rgb($bt_fillcolor));
140   my $bordercolor    = $img->colorAllocate(hex2rgb($bt_bordercolor));
141   my $line_y1        = $bt_padding + $txtpad;
142   my $line_y2        = $bt_padding + $txtpad + $bt_height;
143   $img->transparent($white);
144   $img->rectangle(0, 0, $scale_width-1, $scale_height-1, $bordercolor);
145   $img->filledRectangle($bt_padding, $line_y1, $bt_padding + $bt_width,  
146                         $line_y2, $fillcolor);
147   my ($line_x,$i);
148   foreach $i (@bt_entry_times) {
149     $line_x = $bt_padding + (round(($i/1440) * $bt_width));
150     $img->line($line_x, $line_y1, $line_x, $line_y2, $linecolor);
151   }
152   # Shut off text if width is too less.
153   if ($bt_show_text) {
154       if ($bt_width >= 100) {
155           my $ruler_y = $bt_padding + $txtpad + $bt_height + 2;
156           my $ruler_x;
157           for ($i = 0; $i <= 23; $i += 2) {
158               $ruler_x = $bt_padding + round($i * $bt_width/24);
159               $img->string(gdTinyFont,$ruler_x,$ruler_y,"$i",$textcolor);
160               debug(5, 'tinyfont',$ruler_x,$ruler_y,"$i",$textcolor);
161           }
162           $img->string(gdTinyFont, $bt_padding + $bt_width-2,
163                        $ruler_y, "0", $textcolor);
164           my $caption_x = $bt_padding;
165           my $caption_y = $bt_padding-1;
166           my $caption = "B L O G T I M E S   $monthname[$month] $year";
167           $img->string(gdTinyFont, $caption_x, $caption_y,
168                        $caption, $textcolor);
169           debug(5, 'tinyfont', $caption_x, $caption_y, $caption, $textcolor);
170       } else {
171           my $ruler_y = $bt_padding + $txtpad + $bt_height + 2;
172           my $ruler_x;
173           for ($i = 0; $i <= 23; $i += 6) {
174               $ruler_x = $bt_padding + round($i * $bt_width/24);
175               $img->string(gdTinyFont,$ruler_x,$ruler_y,"$i",$textcolor);
176           }
177           $img->string(gdTinyFont, $bt_padding + $bt_width - 2,
178                        $ruler_y, "0", $textcolor);
179           my $caption_x = $bt_padding;
180           my $caption_y = $bt_padding-1;
181           my $caption = "$month $year";
182           $img->string(gdTinyFont, $caption_x, $caption_y,
183                        $caption, $textcolor);
184       }
185   }
186   open IMG, "> $destination_dir/blogtimes.png" or die "$!";
187   binmode IMG;
188   print IMG $img->png or die "$!";
189   close IMG;
190   debug(2, "build blogtimes");
193 sub filter {
194     my ($pkg, $files) = @_;
195     my $latest_story = (sort {$b <=> $a} values %$files)[0];
196     return 1 if $latest_story <= $last_timestamp;
197     my @now = localtime;
198     my $now_month = $now[4] + 1;
199     my $now_year  = $now[5] + 1900;
201     debug(1, "updating graph");
203     foreach (keys %{$files}) {
204         my @date  = localtime($files->{$_});
205         my $mday  = $date[3];
206         my $month = $date[4] + 1;
207         my $year  = $date[5] + 1900;
208         graph_add($date[2], $date[1], $date[0])
209             if (sprintf("%04d%02d%02d", $year, $month, $mday) ge 
210                 $graph_start_day);
211         bt_add($date[2], $date[1])
212             if ($year == $now_year and $month == $now_month);
213     }
214     build_graph();
215     build_blogtimes($now_year, $now_month);
218 sub start {
219     return 0 unless $destination_dir;
220     $last_timestamp = -e $timestamp_file ? stat($timestamp_file)->mtime : 0;
221     my $fh = new FileHandle;
222     if (!$fh->open(">$timestamp_file")) {
223         debug(0, "Couldn't touch timestamp file $timestamp_file");
224         return 0;
225     }
226     $fh->close;
227     debug(1, "$package enabled");
228     return 1;
232 =head1 NAME
234 Blosxom Plug-in: graph
236 =head1 SYNOPSIS
238 Purpose: creates graphs showing the time of day stories are posted
240 Files created: 
241   * $destination_dir/graph.png -- bar graph showing number of posts per
242     period of time (default: hour) since $graph_start_day (default: 19000101)
243   * $destination_dir/blogtimes.png -- a vertical-line form of a scatterplot,
244     showing the posting time of all stories posted this month
246 =head1 VERSION
248 0+1i
250 1st test release
252 =head1 AUTHOR
254 Todd Larason  <jtl@molehill.org>, http://molelog.molehill.org/
256 portions (the "BLOGTIMES" chart style) based on code by Nilesh Chaudhari;
257 see http://nilesh.org/mt/blogtimes/.  Nilesh gets credit, but direct bugs
258 to Todd Larason.
260 This plugin is now maintained by the Blosxom Sourceforge Team,
261 <blosxom-devel@lists.sourceforge.net>.
263 =head1 BUGS
265 None known; please send bug reports and feedback to the Blosxom
266 development mailing list <blosxom-devel@lists.sourceforge.net>.
268 =head1 Customization
270 =head2 Configuration variables
272 There are many configuration variables controlling height, width and colors
273 of the two graphs; see the configuration variable section for those.
275 There's also:
277 C<$destination_dir>, the directory where the output files will be created; 
278 this must be configured.
280 C<$graph_start_day>, the earlist date to consider stories from for the bar 
281 graph.  If you've converted from another weblogging package and lost time of
282 day information on converted stories, you probably want to set this to when
283 you started using Blosxom.
285 C<$graph_num_bars> is the number of bars to create in the bargraph form;
286 if it isn't evenly divisible by 24, things probably act weird -- that isn't
287 well tested.
289 The C<bt_> variables control the "BLOGTIMES" style chart; the others the
290 bar graph.  For the bargraph, the width and height is the size of the overall
291 image; for the blogtimes, it's for the graph portion only -- padding is added
292 for a border and optionally for text.  One or the other of these is likely to 
293 change in a future version to provide consistency.
295 =head1 Caching
297 The images are only recreated when they appear to be out of date; a timestamp
298 file is maintained for this.  To force them to be regenerated, remove
299 $plugin_state_dir/.postgraph.timestamp.
301 =head1 LICENSE
303 this Blosxom Plug-in
304 Copyright 2003, Todd Larason
305 "BLOGTIME" Portions
306 Copyright (c) 2002 Nilesh Chaudhari http://nilesh.org/
308 (This license is the same as Blosxom's and the original BLOGTIME's)
310 Permission is hereby granted, free of charge, to any person obtaining a
311 copy of this software and associated documentation files (the "Software"),
312 to deal in the Software without restriction, including without limitation
313 the rights to use, copy, modify, merge, publish, distribute, sublicense,
314 and/or sell copies of the Software, and to permit persons to whom the
315 Software is furnished to do so, subject to the following conditions:
317 The above copyright notice and this permission notice shall be included
318 in all copies or substantial portions of the Software.
320 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
321 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
322 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
323 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
324 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
325 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
326 OTHER DEALINGS IN THE SOFTWARE.