tagging: Allow using titles in for related stories.
[matthijs/upstream/blosxom-plugins.git] / general / postgraph
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/
8
9 package postgraph;
10
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
22
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;
31
32 $debug_level = 0 unless defined $debug_level;
33 # ------------------------------------------------------------
34 \f
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/;
44 \f
45 my $package        = "postgraph";
46 my $timestamp_file = "$blosxom::plugin_state_dir/.$package.timestamp";
47 my $last_timestamp;
48 \f
49 sub debug {
50     my ($level, @msg) = @_;
51
52     if ($debug_level >= $level) {
53         print STDERR "$package debug $level: @msg\n";
54     }
55 }
56 \f
57 # utility funcs
58 sub max {
59     my $max = 0;
60     foreach (@_) {
61         $max = $_ if $_ > $max;
62     }
63     return $max;
64 }
65 sub round {
66     return int($_[0] + .5);
67 }
68 sub hex2rgb {
69     my ($hex) = @_;
70
71     my ($r, $g, $b) = ($hex =~ m!\#(..)(..)(..)!);
72     return (hex($r),hex($g),hex($b));
73 }
74 \f
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;
79
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]++;
85 }
86
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");
117 }
118 \f
119 # blogtimes directly derived from BLOGTIMES by Nilesh Chaudhari
120 # -- http://nilesh.org/mt/blogtimes/
121
122 my @monthname=('null', 'JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE',
123                'JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER');
124
125 my @bt_entry_times;
126 sub bt_add {
127     my ($hour, $min) = @_;
128     push @bt_entry_times, $hour*60 + $min;
129 }
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");
191 }
192 \f
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;
200
201     debug(1, "updating graph");
202
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);
216 }
217
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;
229 }
230 1;
231
232 =head1 NAME
233
234 Blosxom Plug-in: graph
235
236 =head1 SYNOPSIS
237
238 Purpose: creates graphs showing the time of day stories are posted
239
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
245
246 =head1 VERSION
247
248 0+1i
249
250 1st test release
251
252 =head1 AUTHOR
253
254 Todd Larason  <jtl@molehill.org>, http://molelog.molehill.org/
255
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.
259
260 This plugin is now maintained by the Blosxom Sourceforge Team,
261 <blosxom-devel@lists.sourceforge.net>.
262
263 =head1 BUGS
264
265 None known; please send bug reports and feedback to the Blosxom
266 development mailing list <blosxom-devel@lists.sourceforge.net>.
267
268 =head1 Customization
269
270 =head2 Configuration variables
271
272 There are many configuration variables controlling height, width and colors
273 of the two graphs; see the configuration variable section for those.
274
275 There's also:
276
277 C<$destination_dir>, the directory where the output files will be created; 
278 this must be configured.
279
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.
284
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.
288
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.
294
295 =head1 Caching
296
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.
300
301 =head1 LICENSE
302
303 this Blosxom Plug-in
304 Copyright 2003, Todd Larason
305 "BLOGTIME" Portions
306 Copyright (c) 2002 Nilesh Chaudhari http://nilesh.org/
307
308 (This license is the same as Blosxom's and the original BLOGTIME's)
309
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:
316
317 The above copyright notice and this permission notice shall be included
318 in all copies or substantial portions of the Software.
319
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.
327
328