Gtk2-Ex-Graph-GD

 view release on metacpan or  search on metacpan

lib/Gtk2/Ex/Graph/GD.pm  view on Meta::CPAN

		}
	);
	$eventbox->signal_connect ('button-press-event' => 
		sub {
			my ($widget, $event) = @_;
			my ($x, $y) = ($event->x, $event->y);
			my @imageallocatedsize = $self->{graphimage}->allocation->values;
			$x -= ($imageallocatedsize[2] - $self->{imagesize}->[0])/2;
			$y -= ($imageallocatedsize[3] - $self->{imagesize}->[1])/2;
			my $hotspot;
			if ($self->{graphtype} eq 'bars') {
				$hotspot = $self->_check_bars_hotspot($x,$y);
			} elsif ($self->{graphtype} eq 'lines' or $self->{graphtype} eq 'linespoints') {
				$hotspot = $self->_check_lines_hotspot($x,$y);
			}
			&{ $self->{signals}->{'clicked'} } ($hotspot)
					if $self->{signals}->{'clicked'} && $hotspot;
			return FALSE unless $event->button == 3;
			$self->{optionsmenu}->popup(
				undef, # parent menu shell
				undef, # parent menu item
				undef, # menu pos func
				undef, # data
				$event->button,
				$event->time
			);
		}
	);
	return $eventbox;
}

sub set {
	my ($self, %hash) = @_;
	$self->{graphhash} = \%hash;
	$self->{graph}->set(%hash);
}

sub _set_type {
	my ($self, $type) = @_;
	my ($width, $height) = @{$self->{imagesize}};
	$self->{graphtype} = $type;
	my $graph;
	if ($type eq 'bars') {
		$graph = GD::Graph::bars->new($width, $height);
	} elsif ($type eq 'lines') {
		$graph = GD::Graph::lines->new($width, $height);
	} elsif ($type eq 'linespoints') {
		$graph = GD::Graph::linespoints->new($width, $height);
	} elsif ($type eq 'area') {
		$graph = GD::Graph::area->new($width, $height);
	} elsif ($type eq 'pie') {
		$graph = GD::Graph::pie->new($width, $height);
	}
	$self->{graph} = undef;
	$self->{graph} = $graph;
}

sub _refresh {
	my ($self) = @_;
	$self->{graph}->set(%{$self->{graphhash}}) if $self->{graphhash};
	$self->set_legend(@{$self->{graphlegend}}) if $#{@{$self->{graphlegend}}} >= 0;
	$self->get_image($self->{graphdata});
}

sub _init_tooltip {
	my ($self) = @_;
	my $tooltip_label = Gtk2::Label->new;
	my $tooltip = Gtk2::Window->new('popup');
	$tooltip->set_decorated(0);
	$tooltip->set_position('mouse'); # We'll choose this to start with.
	$tooltip->modify_bg ('normal', Gtk2::Gdk::Color->parse('yellow')); # The obligatory yellow
	$tooltip->add($tooltip_label);
	$self->{tooltip}->{window} = $tooltip;
	$self->{tooltip}->{displayed} = FALSE;
	$self->{tooltip}->{label} = $tooltip_label;	
}

sub set_legend {
	my ($self, @legend_keys) = @_;
	return if ($self->{graphtype} eq 'pie');
	$self->{graph}->set_legend(@legend_keys);
	$self->{graphlegend} = \@legend_keys;
}

sub get_image {
	my ($self, $data) = @_;
	$self->{graphdata} = $data;
	my $graph = $self->{graph};
	$graph->plot($data) or warn $graph->error;
	my $loader = Gtk2::Gdk::PixbufLoader->new;
	$loader->write ($graph->gd->png);
	$loader->close;
	my $image = Gtk2::Image->new_from_pixbuf($loader->get_pixbuf);
	$self->{graphimage} = $image;
	my $hotspotlist;
	if ($self->{graphtype} eq 'bars' or
		$self->{graphtype} eq 'lines' or
		$self->{graphtype} eq 'linespoints') {
		foreach my $hotspot ($graph->get_hotspot) {
			push @$hotspotlist, $hotspot if $hotspot;
		}
	}
	$self->{hotspotlist} = $hotspotlist;
	my $eventbox = $self->{eventbox};
	my @children = $eventbox->get_children;
	foreach my $child (@children) {
		$eventbox->remove($child);
	}
	$eventbox->add ($image);

	$eventbox->signal_connect ('button-press-event' => 
		sub {
			my ($widget, $event) = @_;
			return TRUE;
			return FALSE unless $event->button == 3;
			$self->{optionsmenu}->popup(
				undef, # parent menu shell
				undef, # parent menu item
				undef, # menu pos func
				undef, # data
				$event->button,
				$event->time
			);
		}
	);	
	$eventbox->show_all;
	return $eventbox;
}

sub _show_tooltip {
	my ($self, $tooltipstring) = @_;
	$self->{tooltip}->{label}->set_label($tooltipstring);
	if (!$self->{tooltip}->{displayed}) {
		$self->{tooltip}->{window}->show_all;
		my ($thisx, $thisy) = $self->{tooltip}->{window}->window->get_origin;
		# I want the window to be a bit away from the mouse pointer.
		# Just a personal choice
		$self->{tooltip}->{window}->move($thisx, $thisy-20);
		$self->{tooltip}->{displayed} = TRUE;
	}	
}

sub _check_lines_hotspot {
	my ($self, $x, $y) = @_;
	my $i=0;
	my $hotspotlist = $self->{hotspotlist};
	foreach my $datameasure (@$hotspotlist){
		my $j=0;
		foreach my $hotspot (@$datameasure) {
			my ($name, @coords) = @$hotspot;
			if (_on_the_line($x, $y, @coords)) {
				my $xvalue0 = $self->{graphdata}->[0]->[$j-1];
				my $yvalue0 = $self->{graphdata}->[$i+1]->[$j-1];
				my $xvalue1 = $self->{graphdata}->[0]->[$j];
				my $yvalue1 = $self->{graphdata}->[$i+1]->[$j];
				my $measure = $self->{graphlegend}->[$i];
				return [$measure, $xvalue0, $yvalue0, $xvalue1, $yvalue1];
			}
			$j++;
		} 
		$i++;	
	}
	$self->{tooltip}->{window}->hide;
	$self->{tooltip}->{displayed} = FALSE;
}

sub _on_the_line {
	my ($x, $y, @linecoords) = @_;
	if (($x <= $linecoords[0] and  $x <= $linecoords[2]) or
		($x >= $linecoords[0] and  $x >= $linecoords[2]) or
		($y <= $linecoords[1] and  $y <= $linecoords[3]) or
		($y >= $linecoords[1] and  $y >= $linecoords[3]) ){
		return FALSE;
	}
	my $slope_diff = 
		($linecoords[1]-$linecoords[3])/($linecoords[0]-$linecoords[2]) 
		- ($linecoords[3]-$y)/($linecoords[2]-$x);
	if ($slope_diff > -0.1 and $slope_diff < 0.1) {
		return TRUE;
	}
	return FALSE;	
}

sub _check_bars_hotspot {
	my ($self, $x, $y) = @_;
	my $i=0;
	my $hotspotlist = $self->{hotspotlist};
	foreach my $datameasure (@$hotspotlist){
		my $j=0;
		foreach my $hotspot (@$datameasure) {			
			my ($name, @coords) = @$hotspot;
			if ($x >= $coords[0] && $x <= $coords[2] && $y >= $coords[1] && $y <= $coords[3]) {
				my $xvalue = $self->{graphdata}->[0]->[$j];
				my $yvalue = $self->{graphdata}->[$i+1]->[$j];
				my $measure = $self->{graphlegend}->[$i];
				return [$measure, $xvalue, $yvalue];
			} 
			$j++;	
		}
		$i++;
	}
	$self->{tooltip}->{window}->hide;
	$self->{tooltip}->{displayed} = FALSE;
}

sub _create_optionsmenu {
	my ($self) = @_;
	my $menu = Gtk2::Menu->new();

	my $bars = Gtk2::MenuItem->new("bars");
	my $lines = Gtk2::MenuItem->new("lines");
	my $linespoints = Gtk2::MenuItem->new("lines with points");
	my $area = Gtk2::MenuItem->new("area");
	my $pie = Gtk2::MenuItem->new("pie");
	my $cumulate = Gtk2::MenuItem->new("cumulate");

	$bars->signal_connect(activate => 
		sub { 
			$self->_set_type('bars');
			$self->_refresh;
		}
	);
	$lines->signal_connect(activate => 
		sub { 
			$self->_set_type('lines');
			$self->_refresh;
		}
	);
	$linespoints->signal_connect(activate => 
		sub { 
			$self->_set_type('linespoints');
			$self->_refresh;
		}
	);
	$area->signal_connect(activate => 
		sub { 
			$self->_set_type('area');
			$self->_refresh;
		}
	);
	$pie->signal_connect(activate => 
		sub { 
			$self->_set_type('pie');
			$self->_refresh;
		}
	);
	$cumulate->signal_connect(activate => 
		sub {
			if (exists($self->{graphhash}->{cumulate})) {
				$self->{graphhash}->{cumulate} = !$self->{graphhash}->{cumulate};
			} else {
				$self->{graphhash}->{cumulate} = TRUE;
			}
			$self->_set_type($self->{graphtype});
			$self->_refresh;

lib/Gtk2/Ex/Graph/GD.pm  view on Meta::CPAN

	$menu->append($lines);
	$menu->append($linespoints);
	$menu->append($area);
	$menu->append($pie);
	$menu->append($cumulate);
	
	return $menu;
}

1;

__END__

=head1 NAME

Gtk2::Ex::Graph::GD is a thin wrapper around the good-looking GD::Graph module. Wrapping
using Gtk2 allows the GD::Graph object to respond to events such as mouse movements.

This allows functionalities such as mouse-over-tooltip and right-click menus on the Graph.

=head1 SYNOPSIS

	my $graph = Gtk2::Ex::Graph::GD->new(500, 300, 'bars');
	my $data = GD::Graph::Data->new([
		["1st","2nd","3rd","4th","5th","6th","7th", "8th", "9th"],
		[    1,    2,    5,    6,    3,  1.5,    1,     3,     4],
		[    1.4,  4,   15,    6,    13,  1.5,    11,     3,     4],
	]) or die GD::Graph::Data->error;
	my $image = $graph->get_image($data);
	my $window = Gtk2::Window->new;
	$window->signal_connect(destroy => sub { Gtk2->main_quit; });
	$window->set_default_size(700, 500);
	$window->add($image);
	$window->show_all;
	Gtk2->main;

=head1 FUNCTIONS

=head2 $graph = Gtk2::Ex::Graph::GD->new($width, $height, $type)

Creates a new Gtk2::Ex::Graph::GD object with the specified dimensions and type.
The type can be 'bars', 'lines', 'pie'.

	$graph = Gtk2::Ex::Graph::GD->new(500, 300, 'bars');

=head2 $graph->set($attr1 => $value1, $attr2 => $value2,...)

This is just a thin wrapper on the C<GD::Graph->set> method. 
All the properties set here go straight into the GD::Graph::* object created inside.
Therefore, any property acceptable to the GD::Graph::* object can be passed through here

	$graph->set (
		x_label         => 'X Label',
		y_label         => 'Y label',
		title           => 'A Simple Bar Chart',
		bar_spacing     => 1,
		shadowclr       => 'dred',
		transparent     => 0,
	);

=head2 $graph->set_legend(@legend_keys)

This is just a thin wrapper on the C<GD::Graph->set_legend> method. However, this method
extracts the C<@legend_keys> and uses them in the mouse-over tooltip text.

	my @legend_keys = ('First', 'Second');
	$graph->set_legend(@legend_keys);

=head2 $graph->get_image($data)

The C<$data> object used here is a C<GD::Graph::Data> object. This method internally calls
the C<GD::Graph->plot($data)> and then exports the output into a png. The png is then wrapped
into a Gtk2::Image and then into a Gtk2::EventBox and returned here. You can go on and
pack this C<$image> into the window.

	my $image = $graph->get_image($data);

=head2 signal_connect($signal, $callback)

Two signals are supported

	'mouse-over'
	'clicked'
	
You can bind to these signals just like how you would bind to any normal Gtk2 widget signal.

	$graph->signal_connect ('clicked' =>
		sub {
			print Dumper @_;
		}
	);
	
If the graph is of type C<bars> then the return values are

	my ($measure, $xvalue, $yvalue) = @_

If the graph is of type C<lines> then the return values are

	my ($measure, $xvalue0, $yvalue0, $xvalue1, $yvalue1) = @_

These callbacks are not currently supported for other graph types. I may add them later on.
=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 by Ofey Aikon

This library is free software; you can redistribute it and/or modify it under 
the terms of the GNU Library General Public License as published by the 
Free Software Foundation; 

This library is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE. See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public License along 
with this library; if not, write to the 
Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA.

=head1 ACKNOWLEDGEMENTS

To the wonderful gtk-perl-list.

=head1 SEE ALSO

GD::Graph

=cut



( run in 0.534 second using v1.01-cache-2.11-cpan-39bf76dae61 )