Image::BoxModel::Chart - Charts using Image::BoxModel (Incomplete)


Image-BoxModel documentation Contained in the Image-BoxModel distribution.

Index


Code Index:

NAME

Top

Image::BoxModel::Chart - Charts using Image::BoxModel (Incomplete)

SYNOPSIS

Top

 use Image::BoxModel::Chart;

 my $image = new Image::BoxModel::Chart (
	width => 800, 
	height => 400, 
	lib=> "GD", 			#your backend
 );	
 #all parameters are optional!

 $image -> Chart (dataset_01 => [1,5,3,10,-10]);

 $image -> Save(file=> "chart.png");

DESCRIPTION

Top

Image::BoxModel::Chart will implement different sorts of charts.

bars, points, stapled points, lines

Methods

Chart

Draw chart. Documentation is incomplete because the interface is due to frequent changes.

Legend

 $image -> Legend(
	#mandatory:
	font 			=> (path to font file),
	name 			=> (name of box in which the legend lives)
	values_annotations => (name of your datasets)

	#optional (dafaults preset):
	textsize 		=> [number],
	rotate 			=> [number], 
	colors 			=> (color names of datasets), 	#nice: 'colors => DefaultColors()' sets default colors
	position 		=> ['right'|'left],
	orientation 	=> 'vertical',					#horizontal is unimplemented so far
	resize 			=> (name of box to be resized),
	background 		=> (color),

	padding_left 	=> [number],
	padding_right 	=> [number],
	padding_top 	=> [number],
	padding_bottom 	=> [number],

	spacing_left 	=> [number],
	spacing_top 	=> [number],
	spacing_right 	=> [number],
	spacing_bottom 	=> [number],

	border 			=> [number],
	border_color 	=> (color),
 );

Draw Legend.


Image-BoxModel documentation Contained in the Image-BoxModel distribution.
package Image::BoxModel::Chart;

use warnings;
use strict;
use Image::BoxModel::Chart::Data;
use Image::BoxModel;

use POSIX;
use Carp;
our @ISA = ("Image::BoxModel::Chart::Data", "Image::BoxModel");

sub Chart{
	my $image = shift;
	my %in = @_;
	my %p = (
		box => 'free',
		
		style => 'bar',
		
		orientation => 'vertical',
		
		scale_annotation_size       => 12,
		scale_annotation_rotate     => 0,
		scale_skip                  => 1,
		scale_annotation_show       => 1,
		scale_position              => 'left',
		#~ scale_annotation_background => $image->{background},
		scale_annotation_padding    => '10',
		scale_annotation_skip		=> 1,
		scale_ticks_length			=> 5,
		scale_ticks_thickness		=> 1,
		scale_expand_to_grid		=> 1,	# if scale is expanded to a value which's scale annotation, tick and grid is printed / drawn
		
		values_annotation_size       => 12,
		values_annotation_show       => 1,
		values_annotation_position   => 'bottom',
		#~ values_annotation_background => $image->{background},
		values_annotation_padding    => '10',
		values_annotation_rotate     => '-90',
		values_annotation_skip		 => 1,
		values_ticks_length			 => 5,
		values_ticks_thickness		 => 1,
		
		box_border       => 0,
		box              => 'free',
		color            => $image-> DefaultColors(),
		border_color     => "black",
		border_thickness => 1, 
		background_color => 'grey90',
		grid_color       => 'grey50',
		bar_thickness    => ".75",	#how much breakfast the bars have had: 1 = touching each other, .5 = half as thick , >1 overlapping, etc. (bug! paints out of its box if >1!); 0.01 or something for debug (exact positioning..)

		draw_from_base   => 1,		#if the bars should be drawn from the base or not
		base             => 0,		#"normally" one would like to draw the bars from zero upwards or downwards (negative values), but possibly someone wants to draw from 5 e.g.
		
		%in
	);
	
	my ($max_values, $dataset_ref, $colors_ref, $border_colors_ref);
	($dataset_ref, $colors_ref, $border_colors_ref, $max_values, %p) = $image -> PopulateArrays(%p);
	my @datasets = @{$dataset_ref};
	my @colors = @{$colors_ref};
	my @border_colors = @{$border_colors_ref};
	
	croak "Mandatory parameter 'values.+' not specified. Howto call value-parameters: values[any_character_or_number]. No chart drawn.\n" unless @datasets;
	
	$p{thickness} = 20 unless (exists $p{thickness} and $p{thickness}); # Ensures that points can be drawn. Can't draw circles with an undefined or 0 size.
	
	
	#change this as soon as orientation 'horizontal' is implemented
	return "Sorry, orientation $p{orientation} unimplemented" unless ($p{orientation} =~ /vertical/i);
	
	#~ return "box_border must be > 0. No chart drawn.\n" unless $p{box_border} > 0;
	
	unless ($p{values_annotations}){	#if there are no values-annotations we just guess and set them to 1,2,3,..
		$p{values_annotations}[$_] = $_+1 foreach (0..$max_values-1);
	}
	
	unless (exists $in{scale_skip} and $in{scale_skip} and $in{scale_skip} > 0){ # scale_skip *must* be a positive value. 0 is deadly later on, negatives son't make much sense to me..
		$p{scale_skip} =$image -> ScaleSkip(%p);
	}
	
	# points and lines need some space.
	# this can be improved, because if scale is extended and after this there is enough space, there is no necessity for this.
	$p{box_border} += $p{thickness}/ 1.5 if ($p{style} =~ /point/i or $p{style} =~ /line/); 
		
		
	if ($p{scale_expand_to_grid}){	#expand highest&lowest to ensure that the chart ends at a value which's scale-annotation is printed (if desired :-)
		$p{highest} = $image-> ExpandToGrid (value => $p{highest}, skip => $p{scale_skip}, base => $p{base});
		$p{lowest}  = $image-> ExpandToGrid (value => $p{lowest},  skip => $p{scale_skip}, base => $p{base});
	}

	my @scale_array = $image->BuildScaleArray (base => $p{base}, highest => $p{highest}, lowest => $p{lowest}, skip => $p{scale_skip});
	
	$p{lowest} = $scale_array[0] if ($p{lowest} > $scale_array[0]);		#if base is lower than the lowest value, we need to set lowest to base. (BuilsScaleArray includes base)
	

	
	#if the chart lives in the topmost box of the image it needs some free space at the top, otherwise the scale annotation (or whatever) wouldn't fit into the picture..
	if ($image->{$p{box}}{top} == 0){
		if ($p{orientation} =~ /vertical/i){
			$image -> Box (
				resize 	=> $p{box},
				height 	=> $p{scale_annotation_size}/2,	#half the size of the scale_annotation should be ok.
				position=> 'top',
				name 	=> "$p{box}_auto_padding_top",
				background=> $image -> {background}
			);
		}
		else{
			#unimplemented
		}
	}
	
	#..and here the same if chart touches bottom
	if ($image -> {$p{box}}{bottom} == $image->{height}){
		if ($p{orientation} =~ /vertical/i){
			$image -> Box (
				resize 	=> $p{box},
				height 	=> $p{scale_annotation_size}/2,	#half the size of the scale_annotation should be ok.
				position=> 'bottom',
				name 	=> "$p{box}_auto_padding_bottom",
				background=> $image -> {background}
			);
		}
		else{
			#unimplemented
		}
	}
	$p{highest} = $scale_array[-1] if ($p{highest} < $scale_array[-1]);	#and the same for highest
	
	#boxes for scale-annotation
	if ($p{scale_annotation_show} and $p{style} !~ /stapled_points/i){
		$image -> ArrayBox (
			values_ref 	=> \@scale_array, 
			textsize 	=> $p{scale_annotation_size}, 
			rotate		=> $p{scale_annotation_rotate}, 
			resize 		=> $p{box}, 
			position	=> $p{scale_position},
			name		=> "$p{box}_scale_annotation", 
			background 	=> $p{scale_annotation_background},
			skip		=> $p{scale_annotation_skip}
		);
		
		$image -> Box(
			resize 		=> $p{box}, 
			position 	=> $p{scale_position}, 
			width		=> $p{scale_annotation_padding}, 
			height 		=> $p{scale_annotation_padding}, 
			name		=> "$p{box}_scale_annotation_padding",
			#~ background 	=> $image ->{background}
		) if $p{scale_annotation_padding};
	}
	
	# box for scale-ticks_length
	if ($p{scale_ticks_length} and $p{style} !~ /stapled_points/i){
		$image -> Box(
			resize 		=> $p{box},
			position	=> $p{scale_position},
			width		=> $p{scale_ticks_length},
			height		=> $p{scale_ticks_length},
			name		=> "$p{box}_scale_ticks",
			#~ background	=> $image->{background}
		);	
	}
	
	# boxes for values_annotation
	if ($p{values_annotation_show} ){
		$image -> ArrayBox (
			values_ref 	=> $p{values_annotations},
			textsize 	=> $p{values_annotation_size}, 
			rotate		=> $p{values_annotation_rotate}, 
			resize		=> $p{box}, 
			position 	=> $p{values_annotation_position},
			name		=> "$p{box}_values_annotation", 
			background 	=> $p{values_annotation_background},
			skip		=> $p{values_annotation_skip}
		);
		$image -> Box(
			resize     => $p{box}, 
			position   => $p{values_annotation_position}, 
			width      => $p{values_annotation_padding}, 
			height     => $p{values_annotation_padding}, 
			name       => "$p{box}_values_annotation_padding",
			#~ background => $image ->{background}
		) if $p{values_annotation_padding};
	}
	
	# box for values_ticks
	if ($p{values_ticks_length}){
		$image -> Box(
			resize 		=> $p{box},
			position	=> $p{values_annotation_position},
			width		=> $p{values_ticks_length},
			height		=> $p{values_ticks_length},
			name		=> "$p{box}_values_ticks",
			#~ background	=> $image->{background}
		);	
	}
	
	#after the steps are calculated, lets draw the annotations
	if ($p{scale_annotation_show} and $p{style} !~ /stapled_points/i){ #it makes no sense to draw a scale if stapled points..
		$image -> PrintScale(
			array 		=> \@scale_array,
			lowest		=> $p{lowest},
			highest 	=> $p{highest},
			textsize 	=> $p{scale_annotation_size},
			rotate 		=> $p{scale_annotation_rotate},
			chart_box 	=> $p{box},
			box 		=> "$p{box}_scale_annotation",
			box_border 	=> $p{box_border},
			font		=> $p{font},
			skip		=> $p{scale_annotation_skip},
			ticks_length=> $p{scale_ticks_length},
		);
	}
	
	if ($p{scale_ticks_length} and $p{style} !~ /stapled_points/i ){ # stapled points are piled up, not spread over the whole chart..
		$image -> DrawTicks(
			array			=> \@scale_array,
			orientation 	=> $p{orientation},
			lowest 			=> $p{lowest},
			highest 		=> $p{highest},
			box_border 		=> $p{box_border},
			thickness		=> $p{scale_ticks_thickness},
			box_to_draw_on	=> "$p{box}_scale_ticks",
			box_to_measure_from => $p{box}
		);
	}
	
	if ($p{values_ticks_length}){
		$image -> DrawTicks(
			array			=> $p{values_annotations},
			orientation 	=> 'horizontal', # fix this!
			lowest 			=> 0,
			highest 		=> scalar(@{$p{values_annotations}}),
			box_border 		=> $p{box_border},
			thickness		=> $p{values_ticks_thickness},
			box_to_draw_on	=> "$p{box}_values_ticks",
			box_to_measure_from => $p{box}
		);
	}
	
	if ($p{values_annotation_show}){
		$image -> PrintScale(
			array 		=> $p{values_annotations},
			textsize 	=> $p{values_annotation_size},
			rotate 		=> $p{values_annotation_rotate},
			chart_box 	=> $p{box},
			box 		=> "$p{box}_values_annotation",
			box_border 	=> $p{box_border},
			orientation => 'horizontal',
			font		=> $p{font},
			skip		=> $p{values_annotation_skip}
		);
	}
	
	$image-> DrawRectangle(	#Draw a nice rectangle around the chart
		left 			=> $image->{$p{box}}{left}, 
		right 			=> $image->{$p{box}}{right}, 
		top 			=> $image->{$p{box}}{top}, 
		bottom 			=> $image->{$p{box}}{bottom}, 
		fill_color 		=> $p{background_color},
		border_color 	=> $p{border_color},
		border_thickness=> $p{border_thickness},
	);
	
	$image -> DrawGrid(
		array			=> \@scale_array, 
		orientation 	=> $p{orientation},
		grid_color 		=> $p{grid_color},
		lowest 			=> $p{lowest},
		highest 		=> $p{highest},
		box_border 		=> $p{box_border},
		border_thickness=> $p{border_thickness},
		box 			=> $p{box}
	) unless ($p{style} =~ /stapled_points/i); #no grid if stapled points, because these are just stapled..
	
	####
	# Print the chart (finally, after all surroundings are properly done)
	####
	if ($p{orientation} =~ /vertical/i){
		
		my @coordinates if ($p{style} =~ /line/i); # for lines we need to wait until we have all points and draw a whole dataset at once.
		
		foreach my $number_of_data_element (0 .. $max_values-1){
			
			#left border of "reserved" space for bars / points etc. of given dataset
			
			my $x_leftmost = where_between(
				pos_min => $image->{$p{box}}{left}  + $p{box_border},
				pos_max => $image->{$p{box}}{right} - $p{box_border},
				val_min => 0,
				val_max => $max_values,
				val     => $number_of_data_element +.5 # half a step inside -> middle of the space of this value
							- $p{bar_thickness} / 2    # - half of the space for all the bars
			);
			
			my $x_rightmost = where_between(
				pos_min => $image->{$p{box}}{left}  + $p{box_border},
				pos_max => $image->{$p{box}}{right} - $p{box_border},
				val_min => 0,
				val_max => $max_values,
				val     => $number_of_data_element +.5 + $p{bar_thickness} / 2  
			);
			
			foreach (0.. $#datasets){ # now the correspondent slice of every dataset is printed
				
				my $x1 = where_between(
					pos_min => $x_leftmost,
					pos_max => $x_rightmost,
					val_min => 0,
					val_max => scalar (@datasets),
					val     => $_
				);
				
				my $x2 = where_between(
					pos_min => $x_leftmost,
					pos_max => $x_rightmost,
					val_min => 0,
					val_max => scalar (@datasets),
					val     => $_+1
				);
				
				$datasets[$_][$number_of_data_element] = $p{base} unless ($datasets[$_][$number_of_data_element]);
				
				my $y1 = where_between (
					pos_min => $image->{$p{box}}{bottom} - $p{box_border},
					pos_max => $image->{$p{box}}{top} + $p{box_border},
					val_min => $p{lowest},
					val_max => $p{highest},
					val     => $datasets[$_][$number_of_data_element]
				);
				
				my $y2;

				if ($p{draw_from_base} == 0){	#if from lowest value, we can draw from the bottom of the box. (more or less..)
					$y2 = $image->{$p{box}}{bottom} - $p{box_border};
				}
				else{						#otherwise we count upwards as many steps as there are from lowest_value to zero
						
					$y2 = where_between (
						pos_min => $image->{$p{box}}{bottom} - $p{box_border},
						pos_max => $image->{$p{box}}{top} + $p{box_border},
						val_min => $p{lowest},
						val_max => $p{highest},
						val     => $p{base} 
					);
				}
				
				if ($p{style} =~ /bar/i){
					
					$image -> DrawRectangle (
						top 	=> $y1, 
						bottom 	=> $y2, 
						left 	=> $x1, 
						right 	=> $x2, 
						fill_color => $colors[$_], 
						border_color=>$p{border_color}, 
						border_thickness => $p{border_thickness}
					);
				}
				elsif ($p{style} =~ /stapled_points/i){ 
					
					my $shrink_to_fit = 1;
					
					$shrink_to_fit /= 2 while ($y2 - ($x2-$x1)/2 -  ($x2-$x1)* $datasets[$_][$number_of_data_element]*$shrink_to_fit - ($x2-$x1)/2 
						< $image -> {$p{box}}{top});
														
					for (my $e = 0; $e < $datasets[$_][$number_of_data_element];$e++){
						$image -> DrawCircle (
							top			=> $y2 - ($x2-$x1)/2 - ($x2-$x1)*$e*$shrink_to_fit - ($x2-$x1)/2,	#first 1/2 of width off 0, then $e*width, then half width
							bottom		=> $y2 - ($x2-$x1)/2 - ($x2-$x1)*$e*$shrink_to_fit + ($x2-$x1)/2,
							left		=> $x1,
							right		=> $x2,
							fill_color => $colors[$_], 
							border_color=>$p{border_color}, 
							border_thickness => $p{border_thickness}
						);
					}
				}
				elsif ($p{style} =~ /point/i){
					
					$image -> DrawCircle (
						top 	=> $y1 + ($p{thickness}) / 2, 		
						bottom 	=> $y1 - ($p{thickness}) / 2, 
						left 	=> $x1 + ($x2 - $x1)/2 + ($p{thickness}) / 2, 
						right 	=> $x1 + ($x2 - $x1)/2 - ($p{thickness}) / 2,
						fill_color => $colors[$_], 
						border_color=>$p{border_color}, 
						border_thickness => $p{border_thickness}
					);
				}
				elsif ($p{style} =~ /line/i){ 	# just push all coordinates into @coordinates and draw afterwards.
												# This might perhaps be a good idea for all styles..
												
					if (exists $p{offset} and defined $p{offset} and $p{offset}){
						$coordinates[$_][$number_of_data_element]{x} = $x1         + ($x2 		   - $x1		) / 2;
					}
					else{
						$coordinates[$_][$number_of_data_element]{x} = $x_leftmost + ($x_rightmost - $x_leftmost) / 2;
					}
						$coordinates[$_][$number_of_data_element]{y} = $y1;
				}
				else {
					print "Sorry. Style $p{style} is (still) unimplemented.\n";
				}
			}
			
			if ($p{style} =~ /line/i){
				foreach my $dataset (0 .. scalar(@coordinates)-1){
					
					foreach my $number (0 .. scalar (@{$coordinates[$dataset]})-1){
						
						if ($p{thickness} and $p{thickness} > 1){
							$image -> DrawCircle(
								top 	=> $coordinates[$dataset][$number]{y} - $p{thickness} / 2,
								bottom	=> $coordinates[$dataset][$number]{y} + $p{thickness} / 2,
								left	=> $coordinates[$dataset][$number]{x} - $p{thickness} / 2,
								right	=> $coordinates[$dataset][$number]{x} + $p{thickness} / 2,
								color 	=> $colors[$dataset]
							);
						}
						
						if ($number > 0){
							$image -> DrawLine(
								x1 		=> $coordinates[$dataset][$number-1]{x},
								y1 		=> $coordinates[$dataset][$number-1]{y},
								x2 		=> $coordinates[$dataset][$number]{x},
								y2 		=> $coordinates[$dataset][$number]{y},
								color 	=> $colors[$dataset],
								thickness=>$p{thickness}
							);
						}
					}
				}
			}
		}
	}
	elsif ($p{orientation} =~ /horizontal/i){
		print "horizontal $p{style}s unimplemented. sorry";
	}
	else{
		die "Unknown orientation $p{orientation}. Should be 'horizontal' or 'vertical' where only vertical is implemented so far";
	}
	
	return;
}


###
# Print Scale Annotations
###

sub PrintScale{
	my $image = shift;
	my %p = (
		orientation => 'vertical',
		@_
	);
	
	$p{skip} = 1 unless ($p{skip}); # assure that skip cannot become 0
	
	my $c = 0;
	foreach (@{$p{array}}){
		
		my ($w, $h) = $image -> GetTextSize(text => $_, textsize => $p{textsize}, font=> $p{font}, rotate => $p{rotate});
		my ($x1, $x2, $y1, $y2);
		
		if ($p{orientation} =~ /vertical/i){ 
			
			$y1 = where_between(
				pos_min => $image->{$p{chart_box}}{bottom} - $p{box_border},
				pos_max => $image->{$p{chart_box}}{top} + $p{box_border},
				val_min => $p{lowest},
				val_max => $p{highest},
				val     => $_ 
			) - $h/2;
		
			$y2 = $y1 + $h;
						
			$x1 = int ($image->{$p{box}}{right}-$w);	#bad: assumes align = right	
			$x2 =      $image->{$p{box}}{right};		

		}
		elsif ($p{orientation} =~ /horizontal/i){
			
			$x1 = where_between(	# fix this. Only works on vertical charts. Then this is values_annotation. What if horizontal is the scale?!
				pos_min => $image->{$p{chart_box}}{left} + $p{box_border},
				pos_max => $image->{$p{chart_box}}{right} - $p{box_border},
				val_min => 0,
				val_max => scalar(@{$p{array}}),
				val     => $c+.5 # half a step inside
			)- $w/2;
			
			$x2 = $x1 + $w;
			
			$y2 = int ($image->{$p{box}}{top}+$h);	#bad: assumes align = right); 
			$y1 =      $image->{$p{box}}{top};
		}
		else{
			Carp::confess ("bad parameter orientation '$p{orientation}'");
		}		
		
		print $image -> FloatBox(name => "$p{box}_$c", top => $y1, bottom => $y2, right => $x2, left => $x1); 
		print $image -> Text (box=> "$p{box}_$c", text => $_, textsize => $p{textsize}, rotate => $p{rotate}, font=>$p{font});
		$c++;
	}
}

sub DrawGrid{
	my $image = shift;
	my %p = @_;
	
	if ($p{orientation} =~ /vertical/){	
		foreach (@{$p{array}}){

			my $y = where_between (
				pos_min => $image->{$p{box}}{bottom} - $p{box_border},
				pos_max => $image->{$p{box}}{top} 	 + $p{box_border},
				val_min => $p{lowest},
				val_max => $p{highest},
				val     => $_
			);
			
			next unless ($y < $image->{$p{box}}{bottom} and $y > $image->{$p{box}}{top});	#dont's draw if the grid is exactly on the border of the whole chart. 
			
			$image -> DrawRectangle (
				top 	=> $y 	-					($p{border_thickness}-1)/2, #-1 because otherwise the grid would be too fat, / 2 because it's done twice. Perhaps border_thickness is not the appropriate parameter anyway.
				bottom 	=> $y	+ 					($p{border_thickness}-1)/2, 
				right 	=> $image->{$p{box}}{right} -$p{border_thickness}, 
				left 	=> $image->{$p{box}}{left}  +$p{border_thickness}, 
				color 	=> $p{grid_color}
			);
		}
	}
	else{
		#unimplemented
		print "horizontal grid unimplemented. sorry.";
	}
}

sub DrawTicks{
	my $image = shift;
	my %p = @_;
	
	if ($p{orientation} =~ /vertical/){	
		foreach (@{$p{array}}){
			
			my $y = where_between (
				pos_min => $image->{$p{box_to_measure_from}}{bottom} - $p{box_border},
				pos_max => $image->{$p{box_to_measure_from}}{top} 	 + $p{box_border},
				val_min => $p{lowest},
				val_max => $p{highest},
				val		=> $_
			);
			
			$image -> DrawRectangle (
				top 	=> $y - ($p{thickness}-1)/2, # see above, DrawGrid
				bottom 	=> $y + ($p{thickness}-1)/2, 
				right 	=> $image -> {$p{box_to_draw_on}}{right},
				left 	=> $image -> {$p{box_to_draw_on}}{left},
				color 	=> 'black' # to be dony by parameter
			);
		}
	}
	else{
		for my $c (0 .. scalar(@{$p{array}})-1){
			my $x = where_between (
				pos_min => $image->{$p{box_to_measure_from}}{left}  + $p{box_border},
				pos_max => $image->{$p{box_to_measure_from}}{right} - $p{box_border},
				val_min => $p{lowest},
				val_max => $p{highest},
				val		=> $c +.5 # middle of the bar / point / whatever is in the middle between 2 borders..
			);
			
			$image -> DrawRectangle (
				left 	=> $x-($p{thickness}-1)/2, # see above, DrawGrid
				right 	=> $x+ ($p{thickness}-1)/2, 
				bottom 	=> $image -> {$p{box_to_draw_on}}{bottom},
				top 	=> $image -> {$p{box_to_draw_on}}{top},
				color 	=> 'black' # to be dony by parameter
			);
		}
	}
}

sub Legend{
	my $image = shift;
	my %p = (
		textsize 		=> 12,
		rotate 			=> 0, 
		colors 			=> DefaultColors(),
		position 		=> 'right',
		orientation 	=> 'vertical',
		resize 			=> 'free',
		background 		=> $image->{background},
		
		padding_left 	=> 10,
		padding_right 	=> 10,
		padding_top 	=> 10,
		padding_bottom 	=> 10,
		
		spacing_left 	=> 10,
		spacing_top 	=> 10,
		spacing_right 	=> 10,
		spacing_bottom 	=> 10,
		
		border 			=> 1,
		border_color	=> 'black',
		
		@_
	);
	
	if (exists $p{values_annotations} and $p{values_annotations}){
		$p{values_ref} = $p{values_annotations};
	}
	else{
		croak __PACKAGE__, ": Mandatory parameter 'values_annotations' missing";
	}
	
	croak __PACKAGE__, ": Mandatory parameter 'name' missing" unless (exists $p{name} and $p{name});
	
	my $square_size = int ($p{textsize} * .8);	#to be done by some intelligently set parameters later on..

	my ($w, $h) = $image -> ArrayBox (resize => $p{name},
		name 		=> "$p{name}_text",
		background 	=> $p{background},
		position 	=> $p{position},
		orientation => $p{orientation},
		values_ref 	=> $p{values_ref},
		textsize 	=> $p{textsize},
		rotate 		=> $p{rotate},
		font 		=> $p{font},
		no_box 		=> 1
	);
	
	#~ print "Width: $w, height: $h\n";
	
	#idea: have a big box into which the smaller boxes for legend etc go.
	
	$image -> Box (
		name 		=> "$p{name}",
		width 		=> $p{padding_left} + $p{border} + $p{spacing_left} + $square_size + $p{spacing_left} + $w + $p{spacing_right} + $p{border} + $p{padding_right}+6,
		height 		=> $p{padding_top} + $p{border} + $p{spacing_top} + $h + $p{spacing_bottom} + $p{border} + $p{padding_bottom}+4,
		position 	=> $p{position},
		resize 		=> $p{resize},
	);
	
	#~ print "Top: $image->{$p{name}}{top}, bottom: $image->{$p{name}}{bottom}\n";
	
	foreach ('left', 'right', 'top', 'bottom'){	#padding: 4 little (big) boxes outside the border, one at each corner
		$image -> Box (
			resize 	=> "$p{name}",
			width 	=> $p{"padding_$_"},
			height 	=> $p{"padding_$_"},
			name 	=> "$p{name}_padding_$_",
			position=> "$_",
		);
	}
	
	foreach ('left', 'right', 'top', 'bottom'){	#spacing: 4 little (big) boxes inside the border, one at each corner
		$image -> Box (
			resize 	=> "$p{name}",
			width 	=> $p{"spacing_$_"} + $p{border},	#to reserve space for the border as well..
			height 	=> $p{"spacing_$_"} + $p{border},
			name 	=> "$p{name}_spacing_$_",
			position=> "$_",
		);
	}
	
	$image -> ArrayBox (		#reserve space for the text
		resize 		=> $p{name},
		name 		=> "$p{name}_text",
		background 	=> $p{background},
		position 	=> 'right',	# Text is *always" right of little squares, wherever the legend is put.
		orientation => $p{orientation},
		values_ref 	=> $p{values_ref},
		textsize 	=> $p{textsize},
		rotate 		=> $p{rotate},
		font 		=> $p{font},
	);
	
	$image -> Box(			#some spacing between text & squares
		resize 		=> $p{name},
		name		=> "$p{name}_spacing_text_squares",
		width 		=> $p{spacing_left},
		position 	=> 'right'
	);
	
	$image -> Box(				#box for squares
		resize 		=> $p{name},
		name		=> "$p{name}_squares",
		width 		=> $square_size,
		height 		=> $square_size,
		position 	=> $p{position},
	);
	
	
	$image -> DrawRectangle(		#a rectangle as border of the legend
		top 		=> $image ->{"$p{name}_spacing_top"}{top},
		bottom 		=> $image->{"$p{name}_spacing_top"}{top}+ $p{border} * 2 + $h+ $p{spacing_top} + $p{spacing_bottom}, # Calculate space needed. 
		left 		=> $image->{"$p{name}_spacing_left"}{left}, 
		right 		=> $image->{"$p{name}_spacing_right"}{right},  
		fill_color 	=> $p{background}, 
		border_color=> $p{border_color},
		border_thickness => $p{border}
	)if ($p{border});
	
	#~ print $image->{"$p{name}_spacing_top"}{top}, "\t", $p{border} * 2 ,"\t", $h, "\t", $p{spacing_top} ,"\t", $p{spacing_bottom},"\n";
	#~ print $image->{"$p{name}_spacing_top"}{top}+ $p{border} * 2 + $h+ $p{spacing_top} + $p{spacing_bottom}, "\n";
	
	
	
	
	#~ print  $image->{"$p{name}_spacing_top"}{top}+ $p{border} * 2 + $p{spacing_top} + $h + $p{spacing_bottom};
	
	foreach (0.. scalar(@{$p{values_ref}})-1){
		#~ #print @{$p{colors}}[$_], "\t", @{$p{values_ref}}[$_], "\n";
		
		my ($width, $height) = $image -> GetTextSize(
			text 		=> @{$p{values_ref}}[$_],
			textsize 	=> $p{textsize},
			rotate	 	=> $p{rotate},
			font 		=> $p{font}
		);
		
		#there will be a distinction between vertically and horizontally drawn legends as soon as this is implemented
		
		my $e = $image -> Annotate(
			resize 		=>"$p{name}_text",
			text 		=> @{$p{values_ref}}[$_], 
			textsize 	=> $p{textsize},
			rotate 		=> $p{rotate},
			align 		=> 'left', 
			text_position=> 'west',
			font 		=> $p{font},
			
		);
	
		
		my $center_of_minibox = ($image->{$e}{top} + $image->{$e}{bottom}) / 2;
		
		$image -> DrawRectangle(
			top 		=> $center_of_minibox - $square_size / 2, 
			bottom 		=> $center_of_minibox + $square_size / 2, 
			#~ #top 	=> $image->{$e}{top},
			#~ #bottom 	=> $image->{$e}{bottom},
			left 		=> $image->{"$p{name}_squares"}{left}, 
			right		=> $image->{"$p{name}_squares"}{right},  
			fill_color 	=> @{$p{colors}}[$_], 
			border_color=> 'black'
		);
	}
}

sub where_between{#calculates where on a picture a value has to be painted between two points
	my %p = @_;
	
	foreach ('pos_min', 'pos_max', 'val_min', 'val_max', 'val'){
		Carp::croak ("where_between: missing parameter $_") unless (exists $p{$_});
	}
	
	my $position =  (
		$p{pos_min} 					#minimum position 
		+
		($p{pos_max} - $p{pos_min})		#distance between max & min
		*
		
		($p{val} - $p{val_min})			#difference between value and minimum value (numbers, not position!)
		/ 
		($p{val_max} - $p{val_min})		#difference between max & minimum
		#3 lines above result in a factor between 0 an 1, 0 if val = min, 1 if val = max, .5 if val in the middle between the both.
		
		#the distance between max & min (which are pixels or whatsoever) are multiplied by the factor (0-1).
		#this way, the distance between min and position are calculated
		
		#if max < min the result of max-min is negative so that a negative number is added to min. and everybody is happy without any if().
	);
	
	return $position;
}


sub DefaultColors{
	my $image = shift;
	return ['red', 'orange', 'yellow', 'LightGreen', 'green', 'blue', 'DarkBlue', 'DarkRed'];
}

1;