2007-03-07 06:33:45 +01:00
|
|
|
#!/usr/bin/perl
|
|
|
|
package IkiWiki::Plugin::sparkline;
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
use IkiWiki;
|
|
|
|
use IPC::Open2;
|
|
|
|
|
|
|
|
my $match_num=qr/[-+]?[0-9]+(?:\.[0-9]+)?/;
|
|
|
|
my %locmap=(
|
|
|
|
top => 'TEXT_TOP',
|
|
|
|
right => 'TEXT_RIGHT',
|
|
|
|
bottom => 'TEXT_BOTTOM',
|
|
|
|
left => 'TEXT_LEFT',
|
|
|
|
);
|
|
|
|
|
|
|
|
sub import { #{{{
|
|
|
|
hook(type => "preprocess", id => "sparkline", call => \&preprocess);
|
|
|
|
} # }}}
|
|
|
|
|
|
|
|
sub preprocess (@) { #{{{
|
|
|
|
my %params=@_;
|
|
|
|
|
|
|
|
my $php;
|
|
|
|
|
|
|
|
my $style=(exists $params{style} && $params{style} eq "bar") ? "Bar" : "Line";
|
|
|
|
$php=qq{<?php
|
|
|
|
require_once('sparkline/Sparkline_$style.php');
|
|
|
|
\$sparkline = new Sparkline_$style();
|
|
|
|
\$sparkline->SetDebugLevel(DEBUG_NONE);
|
|
|
|
};
|
|
|
|
|
|
|
|
foreach my $param (qw{BarWidth BarSpacing YMin YMaz}) {
|
|
|
|
if (exists $params{lc($param)}) {
|
|
|
|
$php.=qq{\$sparkline->Set$param(}.int($params{lc($param)}).qq{);\n};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my $c=0;
|
|
|
|
while (@_) {
|
|
|
|
my $key=shift;
|
|
|
|
my $value=shift;
|
|
|
|
|
|
|
|
if ($key=~/^($match_num)(?:,($match_num))?(?:\(([a-z]+)\))?$/) {
|
|
|
|
$c++;
|
|
|
|
my ($x, $y);
|
|
|
|
if (defined $2) {
|
|
|
|
$x=$1;
|
|
|
|
$y=$2;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$x=$c;
|
|
|
|
$y=$1;
|
|
|
|
}
|
|
|
|
if ($style eq "Bar" && defined $3) {
|
|
|
|
$php.=qq{\$sparkline->SetData($x, $y, '$3');\n};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$php.=qq{\$sparkline->SetData($x, $y);\n};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif (! length $value) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("parse error")." \"$key\"]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
elsif ($key eq 'featurepoint') {
|
|
|
|
my ($x, $y, $color, $diameter, $text, $location)=
|
|
|
|
split(/\s*,\s*/, $value);
|
|
|
|
if (! defined $diameter || $diameter < 0) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("bad featurepoint diameter")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
$x=int($x);
|
|
|
|
$y=int($y);
|
|
|
|
$color=~s/[^a-z]+//g;
|
|
|
|
$diameter=int($diameter);
|
|
|
|
$text=~s/[^-a-zA-Z0-9]+//g if defined $text;
|
|
|
|
if (defined $location) {
|
|
|
|
$location=$locmap{$location};
|
|
|
|
if (! defined $location) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("bad featurepoint location")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$php.=qq{\$sparkline->SetFeaturePoint($x, $y, '$color', $diameter};
|
|
|
|
$php.=qq{, '$text'} if defined $text;
|
|
|
|
$php.=qq{, $location} if defined $location;
|
|
|
|
$php.=qq{);\n};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($c eq 0) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("missing values")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
my $height=int($params{height} || 20);
|
|
|
|
if ($height < 2 || $height > 100) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("bad height value")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
if ($style eq "Bar") {
|
|
|
|
$php.=qq{\$sparkline->Render($height);\n};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (! exists $params{width}) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("missing width parameter")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
my $width=int($params{width});
|
|
|
|
if ($width < 2 || $width > 1024) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("bad width value")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
$php.=qq{\$sparkline->RenderResampled($width, $height);\n};
|
|
|
|
}
|
|
|
|
|
|
|
|
$php.=qq{\$sparkline->Output();\n?>\n};
|
|
|
|
|
|
|
|
# Use the sha1 of the php code that generates the sparkline as
|
|
|
|
# the base for its filename.
|
|
|
|
eval q{use Digest::SHA1};
|
|
|
|
error($@) if $@;
|
|
|
|
my $fn=$params{page}."/sparkline-".
|
|
|
|
IkiWiki::possibly_foolish_untaint(Digest::SHA1::sha1_hex($php)).
|
|
|
|
".png";
|
|
|
|
will_render($params{page}, $fn);
|
|
|
|
|
|
|
|
if (! -e "$config{destdir}/$fn") {
|
|
|
|
my $pid;
|
|
|
|
my $sigpipe=0;;
|
|
|
|
$SIG{PIPE}=sub { $sigpipe=1 };
|
|
|
|
$pid=open2(*IN, *OUT, "php");
|
|
|
|
|
|
|
|
# open2 doesn't respect "use open ':utf8'"
|
|
|
|
binmode (OUT, ':utf8');
|
|
|
|
|
|
|
|
print OUT $php;
|
|
|
|
close OUT;
|
|
|
|
|
|
|
|
my $png;
|
|
|
|
{
|
|
|
|
local $/=undef;
|
|
|
|
$png=<IN>;
|
|
|
|
}
|
|
|
|
close IN;
|
|
|
|
|
|
|
|
waitpid $pid, 0;
|
|
|
|
$SIG{PIPE}="DEFAULT";
|
|
|
|
if ($sigpipe) {
|
2007-03-07 13:04:45 +01:00
|
|
|
return "[[sparkline ".gettext("failed to run php")."]]";
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
|
2007-03-07 08:41:07 +01:00
|
|
|
if (! $params{preview}) {
|
|
|
|
writefile($fn, $config{destdir}, $png, 1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
# can't write the file, so embed it in a data uri
|
|
|
|
eval q{use MIME::Base64};
|
|
|
|
error($@) if $@;
|
|
|
|
return "<img src=\"data:image/png;base64,".
|
|
|
|
encode_base64($png)."\" />";
|
|
|
|
}
|
2007-03-07 06:33:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return '<img src="'.
|
|
|
|
IkiWiki::abs2rel($fn, IkiWiki::dirname($params{destpage})).
|
|
|
|
'" alt="graph" />';
|
|
|
|
} # }}}
|
|
|
|
|
|
|
|
1
|