webgui/lib/WebGUI/Image/Graph.pm
2006-05-16 12:11:16 +00:00

769 lines
18 KiB
Perl

package WebGUI::Image::Graph;
use strict;
use WebGUI::Image;
use WebGUI::Image::Palette;
use WebGUI::Image::Font;
use List::Util;
use WebGUI::Utility;
our @ISA = qw(WebGUI::Image);
=head1 NAME
Package WebGUI::Image::Graph
=head1 DESCRIPTION
Base class for graphs.
=head1 SYNOPSIS
This package provides the basic needs for creating graphs.
Among others this package provides the base methods for configuration forms,
dataset addition, loading plugins, and setting general parameters like
backgraound color.
=cut
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 addDataset ( dataset )
Adds a dataset to the graph. Please not that not all graph types can handle
multiple datasets and will therefore ignore any dataset but the first.
=head3 dataset
An arrayref containg the values of the data. The dat must be numeric.
=cut
sub addDataset {
my $self = shift;
my $dataset = shift;
push(@{$self->{_datasets}}, $dataset);
}
#-------------------------------------------------------------------
=head2 configurationForm
Returns a hashref containing the form where the properties of your graph type
can be set. Your pluging should extend this method by append the form to the
hashref returned by the super method and returning the reference.
The key for this entry must be unique, so use the namespace of your plugin
without the WebGUI::Image part; the :: converted to and underscore and
everything in lowercase.
Check some of the plugins that come with WebGUI for examples.
=cut
sub configurationForm {
my $self = shift;
my $i18n = WebGUI::International->new($self->session, 'Image_Graph');
my $f = WebGUI::HTMLForm->new($self->session);
$f->trClass('Graph');
$f->integer(
-name => 'graph_imageWidth',
-value => $self->getImageWidth,
-label => $i18n->get('image width'),
-hoverHelp => $i18n->get('image width description'),
);
$f->integer(
-name => 'graph_imageHeight',
-value => $self->getImageHeight,
-label => $i18n->get('image height'),
-hoverHelp => $i18n->get('image height description'),
);
$f->color(
-name => 'graph_backgroundColor',
-value => $self->getBackgroundColor,
-label => $i18n->get('background color'),
-hoverHelp => $i18n->get('background color description'),
);
$f->selectBox(
-name => 'graph_paletteId',
-label => $i18n->get('palette'),
-hoverHelp => $i18n->get('palette description'),
-value => [ $self->getPalette->getId ],
-options=> $self->getPalette->getPaletteList,
);
$f->float(
-name => 'graph_labelOffset',
-value => $self->getLabelOffset,
-label => $i18n->get('label offset'),
-hoverHelp => $i18n->get('label offset description'),
);
$f->selectBox(
-name => 'graph_labelFontId',
-value => [ $self->getLabelFont->getId ],
-label => $i18n->get('label font'),
-hoverHelp => $i18n->get('label font description'),
-options=> WebGUI::Image::Font->getFontList($self->session),
);
$f->color(
-name => 'graph_labelColor',
-value => $self->getLabelColor,
-label => $i18n->get('label color'),
-hoverHelp => $i18n->get('label color description'),
);
$f->integer(
-name => 'graph_labelFontSize',
-value => $self->getLabelFontSize,
-label => $i18n->get('label fontsize'),
-hoverHelp => $i18n->get('label fontsize description'),
);
return {'graph' => $f->printRowsOnly};
}
#-------------------------------------------------------------------
=head2 drawLabel ( label, [ properties ] )
Draws a label with your preferred properties. Defaults the font, font size and
color which you can override.
=head3 label
The text of the label you want to print.
=head3 properties
A hash containing imagemagick Annotate properties.
=cut
sub drawLabel {
my $self = shift;
my $label = shift;
my %properties = @_;
$self->text(
text => $label,
font => $self->getLabelFont->getFile,
fill => $self->getLabelColor,
style => 'Normal',
pointsize => $self->getLabelFontSize,
%properties,
);
}
#-------------------------------------------------------------------
=head2 formNamespace
Returns the namespace used in the configuration form. You must extend this
method by concatenating an underscore and the last part of your namespace to the
output of the SUPER method.
For examples please see the implementation in the plugins that come with WebGUI.
=cut
sub formNamespace {
return "Graph";
}
#-------------------------------------------------------------------
=head2 getConfiguration
Returns the configuration hashref of the plugin. You must extend this method by
adding your configuration keys to the hashref returned by the SUPER method. To
avoid conflicts prepend your configuration keys with the namespace of your
plugin, encoded as follows: take the part of the namespace without
WebGUI::Image, convert it to lowercase and substitute the :: with a single
underscore.
Check out the plugins that are shipped with WebGUI for examples.
=cut
sub getConfiguration {
my $self = shift;
return {
graph_formNamespace => $self->formNamespace,
graph_paletteId => $self->getPalette->getId,
graph_labelOffset => $self->getLabelOffset,
graph_labelFontSize => $self->getLabelFontSize,
graph_labelFontId => $self->getLabelFont->getId,
graph_labelColor => $self->getLabelColor,
graph_imageWidth => $self->getImageWidth,
graph_imageHeight => $self->getImageHeight,
graph_backgroundColor => $self->getBackgroundColor,
};
}
#-------------------------------------------------------------------
=head2 getGraphingTab ( session, [ config ] )
Returns the contents of the graphing tab you can add to your asset.
This is a class method, and therefore you must pass the WebGUI session object.
=head3 session
An instanciated WebGUI session object.
=head3 config
Optionally you can pass a configuration hash to populate the form
=cut
sub getGraphingTab {
my (%configForms, $output);
my $class = shift;
my $session = shift;
my $config = shift;
my (@graphingPlugins, %graphingPlugins, @failedGraphingPlugins);
my $i18n = WebGUI::International->new($session, 'Image_Graph');
my $f = WebGUI::HTMLForm->new($session);
unless ($session->config->get("graphingPlugins")) {
$f->readOnly(
-value => "No graphing plugins are enabled in the config file."
);
return $f->printRowsOnly;
}
foreach (@{$session->config->get("graphingPlugins")}) {
my $plugin = WebGUI::Image::Graph->load($session, $_);
if ($plugin) {
push(@graphingPlugins, $plugin);
$plugin->setConfiguration($config);
$graphingPlugins{$plugin->formNamespace} = $_;
} else {
push(@failedGraphingPlugins, $_);
}
}
my $ns = $config->{graph_formNamespace};
# payment plugin
if (%graphingPlugins) {
$session->style->setRawHeadTags(<<EOS
<script type="text/javascript">
function inNamespace (clas, namespace) {
var namespaceParts = namespace.split('_');
var s = '';
for (var i = 0; i < namespaceParts.length; i++) {
if (i > 0) {
s = s + '_';
}
s = s + namespaceParts[i];
if (s == clas) {
return true;
}
}
return false;
}
function getContainerTag (elem, tagname) {
var parent = elem.parentNode;
while (parent.tagName != tagname) {
parent = parent.parentNode;
}
return parent;
}
function switchGraphingFormElements (elem, namespace) {
var rowElements = getContainerTag(elem, 'TABLE').getElementsByTagName('TR');
for (var ix = 0; ix < rowElements.length; ix++) {
if (inNamespace(rowElements[ix].className, namespace)) {
rowElements[ix].style.display = '';
} else {
if (rowElements[ix].className.match(/^Graph_/)) {
rowElements[ix].style.display = 'none';
}
}
}
}
</script>
EOS
);
$f->selectBox(
-name => 'graphingPlugin',
-options => \%graphingPlugins,
-label => $i18n->get('graph type'),
-hoverHelp => $i18n->get('graph type description'),
-id => 'graphTypeSelector',
-value => [ $config->{graph_formNamespace} ],
-extras => 'onchange="switchGraphingFormElements(this, this.value)"'
);
foreach my $currentPlugin (@graphingPlugins) {
%configForms = (%configForms, %{$currentPlugin->configurationForm});
}
} else {
$f->raw('<tr><td colspan="2" align="left">'.$i18n->get('no graphing plugins').'</td></tr>');
}
foreach (sort keys %configForms) {
$f->raw($configForms{$_});
}
$f->raw('<script type="text/javascript">'.
"switchGraphingFormElements(document.getElementById('graphTypeSelector'), '$ns');".
'</script>'
);
return $f->printRowsOnly;
}
#-------------------------------------------------------------------
=head2 getDataset ( [ index ] )
Returns the dataset indicated by index.
=head3 index
The index of the array containing the datasets. The first dataset is indicated
by index 0. If ommitted this method returns an arrayref of arrayrefs containing
all datasets.
=cut
sub getDataset {
my $self = shift;
my $index = shift;
return $self->{_datasets} unless (defined $index);
die "Illegal dataset" if ($index >= scalar(@{$self->{_datasets}}));
return $self->{_datasets}->[$index];
}
#-------------------------------------------------------------------
=head2 getLabel ( [ index ] )
Returns the index'th label or an arrayref containing all labels.
=head3 index
The index of label to return. Numbering starts at 0. If omitted an arrayref
containing all labels is returned.
=cut
sub getLabel {
my $self = shift;
my $index = shift;
return $self->{_labels}->{data} || [] unless (defined $index);
return $self->{_labels}->{data}->[$index];
}
#-------------------------------------------------------------------
=head2 getLabelColor
Returns the triplet of the label color. Defaults to '#333333'.
=cut
sub getLabelColor {
my $self = shift;
return $self->{_labels}->{labelColor} || '#333333';
}
#-------------------------------------------------------------------
=head2 getLabelDimensions ( text, [ properties ] )
Returns a hashref containg the width and height in pixels of the passed text.
Width and height are referenced by the keys 'width' and 'height' respectively.
=head3 text
The text you want to know the dimensions of.
=head3 properties
Optionally you can pass a hashref containing imagemagick's Annotate properties.
=cut
sub getLabelDimensions {
my $self = shift;
my $text = shift;
my $properties = shift || {};
my ($x_ppem, $y_ppem, $ascender, $descender, $width, $height, $max_advance) = $self->image->QueryFontMetrics(
font => $self->getLabelFont->getFile,
# stroke => $self->getLabelColor,
fill => $self->getLabelColor,
style => 'Normal',
pointsize => $self->getLabelFontSize,
%$properties,
text => $text,
);
return {width => $width, height => $height};
}
#-------------------------------------------------------------------
=head2 getLabelFont
Returns the WebGUI::Image::Font object this image is set to. Defaults to the
default font.
=cut
sub getLabelFont {
my $self = shift;
return $self->{_labels}->{labelFont} || WebGUI::Image::Font->new($self->session);
}
#-------------------------------------------------------------------
=head2 getLabelFontSize
Returns the font size of the labels. Defaults to 20.
=cut
sub getLabelFontSize {
my $self = shift;
return $self->{_labels}->{labelFontSize} || 20;
}
#-------------------------------------------------------------------
=head2 getLabelOffset
Returns the label offset. This is the distance between the label and the axis.
Defaults to 10 pixels.
=cut
sub getLabelOffset {
my $self = shift;
return $self->{_labels}->{labelOffset} || 10;
}
#-------------------------------------------------------------------
=head2 getMaxValueFromDataset
Returns the highest value of all added datasets.
=cut
sub getMaxValueFromDataset {
my $self = shift;
my ($sum, $maxSum);
if ($self->getDrawMode eq 'stacked') {
my $maxElements = List::Util::max(map {scalar @$_} @{$self->{_datasets}});
my $numberOfDatasets = scalar @{$self->{_datasets}};
for my $currentElement (0 .. $maxElements-1) {
$sum = 0;
for my $currentDataset (0 .. $numberOfDatasets - 1) {
$sum += $self->{_datasets}->[$currentDataset]->[$currentElement];
}
$maxSum = $sum if ($sum > $maxSum);
}
} else {
$maxSum = List::Util::max(map {(@$_)} @{$self->{_datasets}});
}
return $maxSum;
return List::Util::max(@{$self->{_dataset}});
}
#-------------------------------------------------------------------
=head2 getPluginList
Returns an arrayref containing the namespaces of the enabled graphing plugins.
=cut
sub getPluginList {
my $self = shift;
my $session = shift || $self->session;
return $session->config->get("graphingPlugins");
}
#-------------------------------------------------------------------
=head2 load ( session, namespace )
Instanciates an WebGUI::Graph object with the given namespace.
=head3 session
A WebGUI::Session object.
=head3 namespace
The full namespace of the plugin you want to load.
=cut
sub load {
my $self = shift;
my $session = shift;
my $namespace = shift;
my $cmd = "use $namespace";
eval($cmd);
$cmd = $namespace.'->new($session)';
my $plugin = eval($cmd);
return $plugin;
}
#-------------------------------------------------------------------
=head2 loadByConfiguration ( session, configuration )
Loads a plugin defined by a configuration hash.
=head3 session
A WebGUI::Session object.
=head3 configuration
A configuration hashref.
=cut
sub loadByConfiguration {
my $self = shift;
my $session = shift;
my $config = shift;
my $namespace = "WebGUI::Image::".$config->{graph_formNamespace};
$namespace =~ s/_/::/g;
$session->errorHandler->fatal("wrong namespace: [$namespace]") unless ($config->{graph_formNamespace} =~ /^[\w\d_]+$/);
my $plugin = $self->load($session, $namespace);
$plugin->setConfiguration($config);
return $plugin;
}
#-------------------------------------------------------------------
=head2 processConfigurationForm ( session )
Processes the configuration form that is submitted and returns the correct
instanciated graphing plugin.
=head3 session
The WebGUI session object.
=cut
sub processConfigurationForm {
my $class = shift;
my $session = shift;
return undef unless ($class->getPluginList($session));
my $namespace = "WebGUI::Image::".$session->form->process('graphingPlugin');
$namespace =~ s/_/::/g;
return undef unless (isIn($namespace, @{$class->getPluginList($session)}));
my $graph = $class->load($session, $namespace);
$graph->setConfiguration($session->form->paramsHashRef);
return $graph;
}
#-------------------------------------------------------------------
=head2 setConfiguration ( config )
Configures the pluging according to the configuration hashref that is passed.
You must extend this method by calling the SUPER method with the configuration
hashref and processing your part of the configuration options.
=head3 config
The configuration hashref.
=cut
sub setConfiguration {
my $self = shift;
my $config = shift;
$self->setPalette(WebGUI::Image::Palette->new($self->session, $config->{graph_paletteId}));
$self->setLabelOffset($config->{graph_labelOffset});
$self->setLabelFontSize($config->{graph_labelFontSize});
$self->setLabelFont(WebGUI::Image::Font->new($self->session, $config->{graph_labelFontId}));
$self->setLabelColor($config->{graph_labelColor});
$self->setImageWidth($config->{graph_imageWidth});
$self->setImageHeight($config->{graph_imageHeight});
$self->setBackgroundColor($config->{graph_backgroundColor});
};
#-------------------------------------------------------------------
=head2 setLabelColor ( color )
Sets the color triplet of the labels.
=head3 color
The triplet defining the color. The triplet should be in the form of '#ffffff'.
=cut
sub setLabelColor {
my $self = shift;
my $color = shift;
$self->{_labels}->{labelColor} = $color;
}
#-------------------------------------------------------------------
=head2 setLabelFont ( font )
Set the label font.
=head3 font
A WebGUI::Image::Font object.
=cut
sub setLabelFont {
my $self = shift;
my $font = shift;
$self->{_labels}->{labelFont} = $font;
}
#-------------------------------------------------------------------
=head2 setLabelFontSize ( size )
Sets the font size of the labels.
=head3 size
The desired font size.
=cut
sub setLabelFontSize {
my $self = shift;
my $size = shift;
$self->{_labels}->{labelFontSize} = $size;
}
#-------------------------------------------------------------------
=head2 setLabelOffset ( offset )
Sets the label offset. This is the distance in pixels between the labels and the
axis.
=head3 offset
The label offset.
=cut
sub setLabelOffset {
my $self = shift;
my $offset = shift;
$self->{_labels}->{labelOffset} = $offset;
}
#-------------------------------------------------------------------
=head2 setLabels ( labels )
Sets the labels for the datasets.
=head3 labels
An arrayref containig the labels.
=cut
sub setLabels {
my $self = shift;
my $labels = shift || [];
$self->{_labels}->{data} = $labels;
}
#-------------------------------------------------------------------
=head2 wrapLabelToWidth ( text, maxWidth, [ properties ] )
Wraps a text string onto multiple lines having a width of maxWidth.
=head3 text
The text you want to wrap.
=head3 maxWidth
The width the string should have after wrapping/
=head3 properties
An optional hashref containing imagemagick's Annotate properties.
=cut
sub wrapLabelToWidth {
my (@words, $part, @lines);
my $self = shift;
my $text = shift;
my $maxWidth = shift;
my $properties = shift;
@words = split(/ +/, $text);
foreach (@words) {
if ($self->getLabelDimensions("$part $_", $properties)->{width} > $maxWidth) {
if ($part) {
$part =~ s/ $//;
push(@lines, $part);
$part = "$_ ";
} else {
push(@lines, $_);
$part = '';
}
} else {
$part .= "$_ ";
}
}
$part =~ s/ $//;
push(@lines, $part) if ($part);
return join("\n", @lines);
}
1;