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

use strict;
use WebGUI::Image::Graph;
use WebGUI::International;
use List::Util;
use POSIX;

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

=head1 NAME

Package WebGUI::Image::Graph::XYGraph

=head1 DESCRIPTION

Base class for flat xy charts.

=head1 SYNOPSIS

XY charts are graphs that have a x and a y coordinate. Examples are Line and Bar
graphs. 

This package provides basics needs for such graphs like methods for drawing
axis, labels, rulers and the likes. Also it has methods to set parameters
belonging to xy charts in general such as setting chart width.

=head1 METHODS

These methods are available from this class:

=cut

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

=head2 configurationForm ( )

The configuration form part for this object. See WebGUI::Image::Graph for
documentation.

=cut

sub configurationForm {
	my ($configForms, $f);
	my $self = shift;

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

	$configForms = $self->SUPER::configurationForm;

	$f = WebGUI::HTMLForm->new($self->session);
	$f->trClass('Graph_XYGraph');
	$f->integer(
		name	=> 'xyGraph_chartWidth',
		value	=> $self->getChartWidth,
		label	=> $i18n->get('chart width'),
		hoverHelp => $i18n->get('chart width description'),
	);
	$f->integer(
		name	=> 'xyGraph_chartHeight',
		value	=> $self->getChartHeight,
		label	=> $i18n->get('chart height'),
		hoverHelp => $i18n->get('chart height description'),
	);
	$f->yesNo(
		name	=> 'xyGraph_drawLabels',
		value	=> $self->showLabels,
		label	=> $i18n->get('draw labels'),
		hoverHelp => $i18n->get('draw labels description'),
	);
	$f->yesNo(
		name	=> 'xyGraph_drawAxis',
		value	=> $self->showAxis,
		label	=> $i18n->get('draw axis'),
		hoverHelp => $i18n->get('draw axis description'),
	);
	$f->color(
		name	=> 'xyGraph_axisColor',
		value	=> $self->getAxisColor,
		label	=> $i18n->get('axis color'),
		hoverHelp => $i18n->get('axis color description'),
	);
	$f->yesNo(
		name	=> 'xyGraph_drawRulers',
		value	=> $self->showRulers,
		label	=> $i18n->get('draw rulers'),
		hoverHelp => $i18n->get('draw rulers description'),
	);
	$f->color(
		name	=> 'xyGraph_rulerColor',
		value	=> $self->getRulerColor,
		label	=> $i18n->get('ruler color'),
		hoverHelp => $i18n->get('ruler color description'),
	);
	$f->selectBox(
		name	=> 'xyGraph_drawMode',
		value	=> [ $self->getDrawMode ],
		label	=> $i18n->get('draw mode'),
		hoverHelp => $i18n->get('draw mode description'),
		multiple=> 0,
		options	=> {
			sideBySide	=> 'Side by side',
			stacked		=> 'Stacked (cumulative',
		},
	);
	$f->float(
		name	=> 'xyGraph_yGranularity',
		value	=> $self->getYGranularity,
		label	=> $i18n->get('y granularity'),	
		hoverHelp => $i18n->get('y granularity description'),
	);

	$configForms->{'graph_xygraph'} = $f->printRowsOnly;
	return $configForms;
}

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

=head2 draw ( )

Draws the graph.

=cut

sub draw {
	my $self = shift;

	# Automagically set the chart offset.
	my $maxYLabelWidth = List::Util::max(map {$self->getLabelDimensions($_)->{width}} @{$self->getYLabels});
	$self->setChartOffset({
		x=> $maxYLabelWidth + 2*$self->getLabelOffset,
		y=> $self->getLabelOffset
	});

	$self->drawRulers if ($self->showRulers);
	$self->drawGraph;
	$self->drawAxis if ($self->showAxis);
	$self->drawLabels if ($self->showLabels);
}

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

=head2 drawAxis ( )

Draws the axis.

=cut

sub drawAxis {
	my $self = shift;

	my $chartOffset = $self->getChartOffset;
	$self->image->Draw(
		primitive	=> 'Path',
		stroke		=> $self->getAxisColor,
		points		=> 
			" M ".$chartOffset->{x}.",".$chartOffset->{y}.
			" L ".$chartOffset->{x}.",".($self->getChartHeight + $chartOffset->{y}).
			" L ".($self->getChartWidth + $chartOffset->{x}).",".($self->getChartHeight + $chartOffset->{y}),
        fill        => 'none',
	);
}

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

=head2 drawLabels ( )

Draws the labels.

=cut

sub drawLabels {
	my $self = shift;
	my $location = shift;

	my %anchorPoint = %{$self->getFirstAnchorLocation}; 

	# Draw x-axis labels
	foreach my $text (@{$self->getLabel}) {
		$self->drawLabel($text, (
			alignVertical	=> 'top',
			align		=> 'left',
            rotate      => 90,
			x		=> $anchorPoint{x},
			y		=> $anchorPoint{y},
		));

		$anchorPoint{x} += $self->getAnchorSpacing->{x};
		$anchorPoint{y} += $self->getAnchorSpacing->{y};
	}

	# Draw y-axis labels
	$anchorPoint{x} = $self->getChartOffset->{x} - $self->getLabelOffset;
	$anchorPoint{y} = $self->getChartOffset->{y} + $self->getChartHeight;
	foreach (@{$self->getYLabels}) {
		$self->drawLabel($_, (
			alignHorizontal	=> 'right',
			alignVertical	=> 'center',
			x		=> $anchorPoint{x}, 
			y		=> $anchorPoint{y}, 
		));
		$anchorPoint{y} -= $self->getPixelsPerUnit * $self->getYGranularity
	}
}

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

=head2 drawRulers ( )

Draws the rulers.

=cut

sub drawRulers {
	my $self = shift;

	my $chartOffset = $self->getChartOffset;
	my $dist = $self->getLabelOffset;

	for (1 .. $self->getYRange / $self->getYGranularity) {
		$self->image->Draw(
			primitive	=> 'Path',
			stroke		=> $self->getRulerColor,
			points		=>
				" M ".$chartOffset->{x}.",".($chartOffset->{y}+$self->getChartHeight - $self->getPixelsPerUnit * $_*$self->getYGranularity).
				" L ".($chartOffset->{x}+$self->getChartWidth).",".($chartOffset->{y}+$self->getChartHeight - $self->getPixelsPerUnit * $_*$self->getYGranularity)
		);
	}
}

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

=head2 formNamespace ( )

Extends the form namespace for this object. See WebGUI::Image::Graph for
documentation.

=cut

sub formNamespace {
	my $self = shift;

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

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

=head2 getAnchorSpacing () 

This method MUST be overridden by all sub classes.

=cut

sub getAnchorSpacing {
    die "You were supposed to override this method in the sub class.";
}


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

=head2 getAxisColor ( )

Returns the color triplet for the axis. Defaults to '#222222'.

=cut

sub getAxisColor {
	my $self = shift;

	return $self->{_axisProperties}->{axisColor} || '#222222';
}

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

=head2 getChartHeight ( )

Returns the height of the chart. Defaults to 200.

=cut

sub getChartHeight {
	my $self = shift;

	return $self->{_properties}->{chartHeight} || 200;
}

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

=head2 getChartOffset ( )

Returns the coordinates of the top-left corner of the chart. he coordinates are
contained in a hasref with keys 'x' and 'y'.

=cut

sub getChartOffset {
	my $self = shift;

	return $self->{_properties}->{chartOffset} || { x=>0, y=>0 }
}

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

=head2 getChartWidth ( )

Returns the width of the chart. Defaults to 200.

=cut

sub getChartWidth {
	my $self = shift;

	return $self->{_properties}->{chartWidth} || 200;
}

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

=head2 getConfiguration ( )

Returns a configuration hashref. See WebGUI::Image::Graph for documentation.

=cut

sub getConfiguration {
	my $self = shift;

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

	$config->{xyGraph_chartWidth}	= $self->getChartWidth;
	$config->{xyGraph_chartHeight}	= $self->getChartHeight;
	$config->{xyGraph_drawLabels}	= $self->showLabels;
	$config->{xyGraph_drawAxis}	= $self->showAxis;
	$config->{xyGraph_drawRulers}	= $self->showRulers;
	$config->{xyGraph_drawMode}	= $self->getDrawMode;
	$config->{xyGraph_yGranularity}	= $self->getYGranularity;

	return $config;
}

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

=head2 getDrawMode ( )

Returns the drawmode. Currently supported are 'stacked' and 'sideBySide'.
Defaults to 'sideBySide'.

=cut

sub getDrawMode {
	my $self = shift;

	return $self->{_barProperties}->{drawMode} || 'sideBySide';
}

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

=head2 getPixelsPerUnit ( )

Returns the number of pixels that correspond with one unit of the dataset
values.

=cut

sub getPixelsPerUnit {
	my $self = shift;

	return $self->getChartHeight / $self->getYRange;
}

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

=head2 getRulerColor ( )

Returns the color triplet of the rulers in the graph. Defaults to '#777777'.

=cut

sub getRulerColor {
	my $self = shift;

	return $self->{_axisProperties}->{rulerColor} || '#777777';
}

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

=head2 getYGranularity ( )

Returns the granularity of the labels and rulers in the Y direction. Defaults to
10. This is value is in terms of the values in the dataset and has no direct
relation to pixels.

=cut

sub getYGranularity {
	my $self = shift;

	return $self->{_properties}->{yGranularity} || 10;
}

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

=head2 getYLabels ( )

Returns an arrayref containing the labels for the Y axis.

=cut

sub getYLabels {
	my $self = shift;

	my @yLabels;
	for (0 .. $self->getYRange / $self->getYGranularity) {
		push(@yLabels, $_ * $self->getYGranularity);
	}

	return \@yLabels;
}

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

=head2 getYRange ( )

Returns the maxmimal value of the range that contains a whole number of times
the y granularity and is bigger than the maximum value in the dataset.

=cut

sub getYRange {
	my $self = shift;

	return $self->getYGranularity*ceil($self->getMaxValueFromDataset / $self->getYGranularity) || 1;
}

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

=head2 setAxisColor ( color )

Sets the color of the axis to the supplied value.

=head3 color

The triplet of the color you want to set the axis to. Must have the following
form: #ffffff.

=cut

sub setAxisColor {
	my $self = shift;
	my $color = shift;

	$self->{_axisProperties}->{axisColor} = $color;
}

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

=head2 setChartHeight ( height )

Sets the height of the chart to the specified value.

=head3 height

The desired height in pixels.

=cut

sub setChartHeight {
	my $self = shift;
	my $height = shift;

	$self->{_properties}->{chartHeight} = $height;
}

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

=head2 setChartOffset ( location )

Sets the location of the top-left corner of the graph within the image.

=head3 location

A hashref containing the desired location. Use the 'x' and 'y' as keys for the x
and y coordinate respectively.

=cut

sub setChartOffset {
	my $self = shift;
	my $point = shift;

	$self->{_properties}->{chartOffset} = {%$point};
}

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

=head2 setChartWidth ( width )

Sets the width of the chart to the specified value.

=head3 width

The desired width in pixels.

=cut

sub setChartWidth {
	my $self = shift;
	my $width = shift;

	$self->{_properties}->{chartWidth} =$width;
}

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

=head2 setConfiguration ( config )

Applies the settings in the given configuration hash. See WebGUI::Image::Graph
for more information.

=head3 config

A configuration hash.

=cut

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

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

	$self->setChartWidth($config->{xyGraph_chartWidth});
	$self->setChartHeight($config->{xyGraph_chartHeight});
	$self->setShowLabels($config->{xyGraph_drawLabels});
	$self->setShowAxis($config->{xyGraph_drawAxis});
	$self->setShowRulers($config->{xyGraph_drawRulers});
	$self->setDrawMode($config->{xyGraph_drawMode});
	$self->setYGranularity($config->{xyGraph_yGranularity});
	$self->setAxisColor($config->{xyGraph_axisColor});
	$self->setRulerColor($config->{xyGraph_rulerColor});

	return $config;
}

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

=head2 setDrawMode ( mode )

Set the way the datasets are drawn. Currently supported are 'stacked' and
'sideBySide' which correspond to respectivly cumulative drawing and normal
processing.

=head3 mode

The desired mode. Can be 'sideBySide' or 'stacked'.

=cut

sub setDrawMode {
	my $self = shift;
	my $mode = shift;

	if ($mode eq 'stacked' || $mode eq 'sideBySide') {
		$self->{_barProperties}->{drawMode} = $mode;
	} else {
		$self->{_barProperties}->{drawMode} = 'sideBySide';
	}
}

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

=head2 setRulerColor ( color )

Set the color of the rulers.

=head3 color

The triplet of the desired ruler color. Must be in the following format:
'#ffffff'.

=cut

sub setRulerColor {
	my $self = shift;
	my $color = shift;

	$self->{_axisProperties}->{rulerColor} = $color;
}

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

=head2 setShowAxis ( boolean )

Set whether or not to draw the axis.

=head3 boolean

If set to false the axis won't be drawn.

=cut

sub setShowAxis {
	my $self = shift;
	my $yesNo = shift;

	$self->{_properties}->{showAxis} = $yesNo;
}

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

=head2 setShowLabels ( boolean )

Set whether or not to draw the labels.

=head3 boolean

If set to false the labels won't be drawn.

=cut

sub setShowLabels {
	my $self = shift;
	my $yesNo = shift;

	$self->{_properties}->{showLabels} = $yesNo;
}

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

=head2 setShowRulers ( boolean )

Set whether or not to draw the rulers.

=head3 boolean

If set to false the rulers won't be drawn.

=cut

sub setShowRulers {
	my $self = shift;
	my $yesNo = shift;

	$self->{_properties}->{showRulers} = $yesNo;
}

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

=head2 setYGranularity ( value )

Sets the y granularity. See getYGranularity for explanation of this concept.

=head3 value

The granularity in dataset units, not pixels.

=cut

sub setYGranularity {
	my $self = shift;
	my $granularity = shift;

	$self->{_properties}->{yGranularity} = $granularity;
}

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

=head2 showAxis ( )

Returns a boolean indicating whether to draw the axis.

=cut

sub showAxis {
	my $self = shift;

	return 1 unless (defined $self->{_properties}->{showAxis});
	return $self->{_properties}->{showAxis};
}

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

=head2 showLabels ( )

Returns a boolean indicating whether to draw the labels.

=cut

sub showLabels {
	my $self = shift;

	return 1 unless (defined $self->{_properties}->{showLabels});
	return $self->{_properties}->{showLabels};
}

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

=head2 showRulers ( )

Returns a boolean indicating whether to draw the rulers.

=cut

sub showRulers {
	my $self = shift;

	return 1 unless (defined $self->{_properties}->{showRulers});
	return $self->{_properties}->{showRulers};
}

1;

