package WebGUI::Image::Graph::XYGraph::Bar;

use strict;
use WebGUI::Image::Graph::XYGraph;
use List::Util;
use POSIX;
use WebGUI::Utility;

our @ISA = qw(WebGUI::Image::Graph::XYGraph);

=head1 NAME

Package WebGUI::Image::Graph::XYGraph::Bar

=head1 DESCRIPTION

Package for creating bar graphs.

=head1 SYNOPSIS

This package privides the logic for drawing 2d bar graphs, 3d bars are in the
pipeline but not yet ready for prime time. 

This module can draw bar graph in two forms: Stacked and Side by Side. The
diffrence is noticable only if more multiple dataset is used, the behaviour is
thus identical in case of one dataset.

Stacked graphs place the bars belonging the same index within diffrent datasets
on top of each other given a grand total for all datasets.

Sid by side graphs place bars with the same index next to each other, grouped by
index. This displays a better comaprison between datasets.

=head1 METHODS

These methods are available from this class:

=cut

#-------------------------------------------------------------------

=head2 configurationForm

Creates the configuration form for this plugin. See WebGUI::Image::Graph for
more information.

=cut

sub configurationForm {
	my $self = shift;

	my $i18n = WebGUI::International->new($self->session, 'Image_Graph_XYGraph_Bar');

	my $configForms = $self->SUPER::configurationForm;
my	$f = WebGUI::HTMLForm->new($self->session);
	$f->trClass('Graph_XYGraph_Bar');
	$f->float(
		name	=> 'xyGraph_bar_barSpacing',
		value	=> $self->getBarSpacing,
		label	=> $i18n->get('bar spacing'),
		hoverHelp => $i18n->get('bar spacing description'),
	);
	$f->float(
		name	=> 'xyGraph_bar_groupSpacing',
		value	=> $self->getGroupSpacing,
		label	=> $i18n->get('group spacing'),
		hoverHelp => $i18n->get('group spacing description'),
	);

	$configForms->{'graph_xygraph_bar'} = $f->printRowsOnly;

	return $configForms;
}

#-------------------------------------------------------------------

=head2 drawBar ( bar, location, barWidth )

Draws a bar defined by bar and with width barWidth at location.

=head3 bar

A hashref defining the bar. Must contain keys 'height', 'strokeColor' and
'fillColor'.

=head3 location

A hashref containing the location of the bottom-left corner of the bar. Keys 'x'
and 'y' must specify the x- and y-coordinates respectively.

=head3 barWidth

The width of the bar in pixels.

=cut

sub drawBar {
	my $self = shift;
	my $bar = shift;
	my $location = shift;
	my $barWidth = shift;

	my $barHeight = $bar->{height} * $self->getPixelsPerUnit;

	$self->image->Draw(
		primitive	=> 'Path',
		stroke		=> $bar->{strokeColor},
		points		=> 
			" M ".$location->{x}.",".$location->{y}.
			" L ".$location->{x}.",".($location->{y}-$barHeight).
			" L ".($location->{x}+$barWidth).",".($location->{y}-$barHeight).
			" L ".($location->{x}+$barWidth).",".$location->{y},
		fill		=> $bar->{fillColor},
	);
}

#-------------------------------------------------------------------

=head2 drawGraph

Draws all the bars.

=cut

sub drawGraph {
	my %location;
	my $self = shift;

	$self->processDataSet;

	my $numberOfGroups = List::Util::max(map {scalar @$_} @{$self->{_datasets}});
	my $numberOfDatasets = scalar @{$self->{_datasets}};
	my $groupWidth = ($self->getChartWidth - ($numberOfGroups-1) * $self->getGroupSpacing) / $numberOfGroups;

	my $barWidth = $groupWidth;
	$barWidth = ($groupWidth - ($numberOfDatasets - 1) * $self->getBarSpacing) / $numberOfDatasets if ($self->getDrawMode eq 'sideBySide');

	$location{x} = $self->getChartOffset->{x} ;
	$location{y} = $self->getChartOffset->{y} + $self->getChartHeight;
	foreach my $currentBar (@{$self->{_bars}}) {
		if ($self->getDrawMode eq 'stacked') {
			$self->drawStackedBar($currentBar, \%location, $barWidth);
		} else {
			$self->drawSideBySideBar($currentBar, \%location, $barWidth);
		}

		$location{x} += $groupWidth + $self->getGroupSpacing;
	}	
}

#-------------------------------------------------------------------

=head2 drawSideBySideBar ( bars, location, barWidth )

Draws the bars in side by side mode. Meaning that per datsetindex the bars
representing a single dataset are grouped.

=head3 bars

An arrayref containing all the bar description hashrefs as described in drawBar.

=head3 location

Hashref containing the initial coordinates of the lower-left corner of the
chart. Pass coords in keys 'x' and 'y'.

=head3 barWidth

The width of each bar in pixels.

=cut

sub drawSideBySideBar {
	my $self = shift;
	my $bars = shift;
	my $location = shift;
	my $barWidth = shift;

	my %thisLocation = %$location;

	foreach (@$bars) {
		$self->drawBar($_, \%thisLocation, $barWidth);
		$thisLocation{x} += $barWidth + $self->getBarSpacing;
	}
}

#-------------------------------------------------------------------

=head2 drawStackedBar ( bars, location, barWidth )

Draws the bars in side by side mode. Meaning that per datset-index the bars
representing a single dataset are stacked on top of each other.

=head3 bars

An arrayref containing all the bar description hashrefs as described in drawBar.

=head3 location

Hashref containing the initial coordinates of the lower-left corner of the
chart. Pass coords in keys 'x' and 'y'.

=head3 barWidth

The width of each bar in pixels.

=cut



sub drawStackedBar {
	my $self = shift;
	my $bars = shift;
	my $location = shift;
	my $barWidth = shift;

	my %thisLocation = %$location;
	foreach (@$bars) {
		$self->drawBar($_, \%thisLocation, $barWidth);
		$thisLocation{y} -= $_->{height} * $self->getPixelsPerUnit;
	}

}

#-------------------------------------------------------------------

=head2 formNamespace

Returns the form namespace of this plugin. See WegBUI::Image::Graph for
more elaborate information.

=cut

sub formNamespace {
	my $self = shift;

	return $self->SUPER::formNamespace.'_Bar';
}

#-------------------------------------------------------------------

=head2 getAnchorSpacing

Returns the distance in pixels between two anchors on the x axis that define teh
placement of bars and labels.

=cut

sub getAnchorSpacing {
	my $self = shift;

	my $numberOfGroups = List::Util::max(map {scalar @$_} @{$self->getDataset});

	my $spacing = round(($self->getChartWidth - ($numberOfGroups-1) * $self->getGroupSpacing) / $numberOfGroups + $self->getGroupSpacing);

	return {
		x	=> $spacing,
		y	=> 0,
	};
}

#-------------------------------------------------------------------

=head2 getBarSpacing

Returns the width of the gap between two bars within a group in pixels.

=cut

sub getBarSpacing {
	my $self = shift;

	return $self->{_barProperties}->{barSpacing} || 0;
}

#-------------------------------------------------------------------

=head2 getConfiguration

Returns the configuration hashref for this plugin. Refer to WebGUI::IMage::Graph
for a more detailed description.

=cut

sub getConfiguration {
	my $self = shift;

	my $config = $self->SUPER::getConfiguration;

	$config->{xyGraph_bar_barSpacing}	= $self->getBarSpacing;
	$config->{xyGraph_bar_groupSpacing}	= $self->getGroupSpacing;

	return $config;
}

#-------------------------------------------------------------------

=head2 getGroupSpacing

Returns the width of the gap between two groups of bars in pixels.

=cut

sub getGroupSpacing {
	my $self = shift;

	return $self->{_barProperties}->{groupSpacing} || $self->getBarSpacing;
}

#-------------------------------------------------------------------

=head2 getFirstAnchorLocation

Returns a hashref containing the location of the leftmost x-axis anchor.
Location coordinates are encoded in keys 'x' and 'y'.

=cut

sub getFirstAnchorLocation {
	my $self = shift;

	return {
		x	=> round($self->getChartOffset->{x} + ($self->getAnchorSpacing->{x} - $self->getGroupSpacing) / 2),
		y	=> $self->getChartOffset->{y} + $self->getChartHeight
	}
}

#-------------------------------------------------------------------

=head2 processDataSet

Processes the dataset. Used by drawGraph.

=cut

sub processDataSet {
	my ($barProperties);
	my $self = shift;

	my $palette = $self->getPalette;

	my $maxElements = List::Util::max(map {scalar @$_} @{$self->{_datasets}});
	my $numberOfDatasets = scalar @{$self->{_datasets}};

	for my $currentElement (0 .. $maxElements-1) {
		my @thisSet = ();
		for my $currentDataset (0 .. $numberOfDatasets - 1) {
            my $color = $palette->getColor($currentDataset);
            if ($numberOfDatasets == 1) {
                $color = $palette->getNextColor;
            }
			push(@thisSet, {
				height => $self->{_datasets}->[$currentDataset]->[$currentElement] || 0,
				fillColor => $color->getFillColor,
				strokeColor => $color->getStrokeColor,
			});
		}
		push(@{$self->{_bars}}, [ @thisSet ]);
	}
}

#-------------------------------------------------------------------

=head2 setBarSpacing ( gap )

Sets the distance between two bars in a group in pixels.

=head3 gap

The distance in pixels.

=cut

sub setBarSpacing {
	my $self = shift;
	my $gap = shift;

	$self->{_barProperties}->{barSpacing} = $gap;
}

#-------------------------------------------------------------------

=head2 setConfiguration ( config )

Applies the given configuration hash to this plugin. See WebGUI::Image::Graph
for more info.

=head3 config

The configuration hash.

=cut

sub setConfiguration {
	my $self = shift;
	my $config = shift;

	$self->SUPER::setConfiguration($config);

	$self->setBarSpacing($config->{xyGraph_bar_barSpacing});
	$self->setGroupSpacing($config->{xyGraph_bar_groupSpacing});

	return $config;
}

#-------------------------------------------------------------------

=head2 setGroupSpacing ( gap )

Sets the distance between two groups of bars in pixels.

=head3 gap

The distance in pixels.

=cut

sub setGroupSpacing {
	my $self = shift;
	my $gap = shift;

	$self->{_barProperties}->{groupSpacing} = $gap;
}

1;

