#!/usr/bin/perl use strict; use warnings; use Tk; use Tk::Canvas; my $self = { }; # configuration my $n_circles = 500; my $c_width = 600; my $c_height = 400; my $r = 3; # initialise data my @circles; for (1..$n_circles) { my $x = rand; my $y = rand; push (@circles, [$x, $y]); } $self -> {'circles'} = \@circles; # initial zoom $self -> {'max_x'} = 1.0; $self -> {'min_x'} = 0.0; $self -> {'max_y'} = 1.0; $self -> {'min_y'} = 0.0; $self -> {'can_w'} = $c_width; $self -> {'can_h'} = $c_height; $self -> {'mode'} = ''; &compute_transform($self); # draw the main window and the canvas my $mw = Tk::MainWindow -> new(); my $can = $mw -> Canvas(-width => $c_width, -height => $c_height, ); my $butcan = $mw -> Canvas(); $self -> {'button_zoom'} = $butcan -> Button (-text => 'Zoom', -command => [\&zoom, $self]); $self -> {'button_zoomall'} = $butcan -> Button (-text => 'Zoom all', -command => [\&zoomall, $self] ); $self -> {'button_zoom'} -> pack(-side => 'left'); $self -> {'button_zoomall'} -> pack(-after => $self -> {'button_zoom'}, -side => 'left'); $butcan -> pack (-side => 'top', -fill => 'x'); $can -> pack(-side => 'bottom', -expand => 1, -fill => 'both'); $self -> {'can'} = $can; $can -> Tk::bind ('<1>' => [\&mouse1, Ev('x'), Ev('y'), $self]); $can -> Tk::bind ('<2>' => [\&mouse2, Ev('x'), Ev('y'), $self]); $can -> Tk::bind ('' => [\&motion, Ev('x'), Ev('y'), $self]); &draw ($self); MainLoop(); sub draw { my ($self) = @_; foreach (@{$self -> {'circles'}}) { my ($x_c, $y_c) = &real_to_screen ($self, @$_); $self -> {'can'} -> createOval($x_c - 2, $y_c - 2, $x_c + 2, $y_c + 2); } return; } sub real_to_screen { my ($self, $x_r, $y_r) = @_; my $x_s = $self -> {'can_w'} / 2.0 + $self -> {'sca_x'} * ($x_r - $self -> {'mid_x'}); my $y_s = $self -> {'can_h'} / 2.0 - $self -> {'sca_y'} * ($y_r - $self -> {'mid_y'}); return ($x_s, $y_s); } sub screen_to_real { my ($self, $x_s, $y_s) = @_; my $x_r = ($x_s - $self -> {'can_w'} / 2.0) / $self -> {'sca_x'} + $self -> {'mid_x'}; my $y_r = ($self -> {'can_h'} / 2.0 - $y_s) / $self -> {'sca_y'} + $self -> {'mid_y'}; return ($x_r, $y_r); } sub compute_transform { my $self = shift; $self -> {'sca_x'} = $self -> {'can_w'} / ($self -> {'max_x'} - $self -> {'min_x'}); $self -> {'sca_y'} = $self -> {'can_h'} / ($self -> {'max_y'} - $self -> {'min_y'}); $self -> {'mid_x'} = ($self -> {'max_x'} + $self -> {'min_x'}) / 2.0; $self -> {'mid_y'} = ($self -> {'max_y'} + $self -> {'min_y'}) / 2.0; return; } sub zoom { my ($self) = @_; $self -> {'button_zoom'} -> configure(-state => 'active'); $self -> {'mode'} = 'zoom0'; return; } sub zoomall { my $self = shift; $self -> {'min_x'} = 0; $self -> {'max_x'} = 1; $self -> {'min_y'} = 0; $self -> {'max_y'} = 1; $self -> {'can'} -> delete('all'); &compute_transform ($self); &draw ($self); return; } sub mouse1 { my (undef, $x, $y, $self) = @_; if ($self -> {'mode'} eq 'zoom0') { $self -> {'window'} = [$x, $y, $x, $y]; $self -> {'window_id'} = $self -> {'can'} -> createRectangle(@{$self -> {'window'}}, -dash => '-'); $self -> {'mode'} = 'zoom1'; print "Window created.\n"; } elsif ($self -> {'mode'} eq 'zoom1') { print "zooming to $x, $y\n"; $self -> {'can'} -> delete ($self -> {'window_id'}); ($self -> {'min_x'}, $self -> {'min_y'}) = &screen_to_real ($self, $self -> {'window'} -> [0], $self -> {'window'} -> [1]); ($self -> {'max_x'}, $self -> {'max_y'}) = &screen_to_real ($self, $self -> {'window'} -> [2], $self -> {'window'} -> [3]); &compute_transform($self); $self -> {'can'} -> delete('all'); &draw ($self); $self -> {'mode'} = ''; } return; } sub motion { my (undef, $x, $y, $self) = @_; if ($self -> {'mode'} eq 'zoom1') { $self -> {'can'} -> delete ($self -> {'window_id'}); $self -> {'window'} -> [2] = $x; $self -> {'window'} -> [3] = $y; $self -> {'window_id'} = $self -> {'can'} -> createRectangle(@{$self -> {'window'}}, -dash => '-'); } return; }