in reply to Cheap'n'cheerful Graph Drawer

I've expanded upon the work of both moot and eric256. It's still pretty basic but adds a few additional features: colored lines, scroll bars, zoom, centering and error trapping/reporting. Since it uses Math::Trig, you can use any of its trigonometric functions.

To zoom in, click the left mouse button over the graph. To zoom out, click the right mouse button over the graph. To center on a point, click the middle mouse button over the spot that be at the center of the graph.

Enjoy!

Update: I've added the ability to redraw the axes based on the values in X Min, X Max, and Step (there is no input validation for these yet). The axes subroutine still needs some work too.

Update: Posted the correct refactoring attempt that doesn't use $x_axis, $y_axis or @ticks.

#!/usr/bin/perl use strict; use warnings; use Tk; use Tk::WorldCanvas; require Tk::Dialog; use Graphics::ColorNames; use Math::Trig; # define our color set my $colors = Graphics::ColorNames->new( 'HTML' ); my %colors = %{ $colors->{SCHEMES}[0] }; delete $colors{fuscia}; # this is the misspelling of fuschia delete $colors{black}; # color of our axes delete $colors{white}; # kind of hard to see delete $colors{silver}; # kind of hard to see delete $colors{gray}; # kind of hard to see my @colors = keys %colors; # array of colors for the lines my $maxcolors = @colors; # mod the line count with this later my @IDs; # store line IDs so that we can delete them without removing +axes # main window my $top = new MainWindow(-width => 800, -height => 600, -title => 'Gra +phing Calculator'); # dialog box for error messages my $d = $top->DialogBox(-title => "Calculation Error", -buttons => ["O +K"]); # scrolled worldcanvas # left mouse click zooms in, middle mouse click centers, right mouse c +lick zooms out my $cvs = $top->Scrolled('WorldCanvas', -scrollbars => 'se', -width => + 800, -height => 550); $cvs->CanvasBind('<1>' => sub { $cvs->zoom(1.25) }); $cvs->CanvasBind('<3>' => sub { $cvs->zoom(0.8) }); $cvs->CanvasBind('<2>' => sub { $cvs->CanvasFocus; $cvs->center($cvs-> +eventLocation) }); # normal canvas with labels and text entries my $frm = $top->Canvas(-width => 800, -height => 20); my $txt_txt = $frm->Label(-text => 'Expression'); my $txt = $frm->Entry(-width => 40); my $min_txt = $frm->Label(-text => 'X Min'); my $min = $frm->Entry(-width => 5); my $max_txt = $frm->Label(-text => 'X Max'); my $max = $frm->Entry(-width => 5); my $stp_txt = $frm->Label(-text => 'Step'); my $stp = $frm->Entry(-width => 5 ); my $redraw = $frm->Button(-text => 'Redraw Axes', -command => [ \&axes + ]); my $clr = $frm->Button(-text => 'Clear', -command => [ sub { $cvs->de +lete(@IDs); @IDs = (); } ]); # bind the enter key to call the evaluate subroutine $txt->bind('<KeyPress-Return>', [ \&evaluate ]); # form up $frm->pack(-side => 'bottom'); $min_txt->pack(-side => 'left'); $min->pack(-side => 'left'); $max_txt->pack(-side => 'left'); $max->pack(-side => 'left'); $stp_txt->pack(-side => 'left'); $stp->pack(-side => 'left'); $redraw->pack(-side => 'left'); $txt_txt->pack(-side => 'left'); $clr->pack(-side => 'right'); $txt->pack(-side => 'right'); $cvs->pack(-side => 'top'); # preload our text boxes $min->insert(0, "0"); $max->insert(0, "100"); $stp->insert(0, "1"); axes(); MainLoop; sub evaluate { my $text = $txt->get(); my $func = lc $text; $func =~ s/(\d+)(pi|x)/$1 * $2/g; $func =~ s/([xy])/\$$1/g; $func =~ s/\^/**/g; my $mini = $min->get(); my $maxi = $max->get(); my $step = $stp->get(); # what's our new color? my $color = $colors->hex($colors[@IDs % $maxcolors], '#'); my @errors; my @points; for ( my $i = $mini ; $i <= $maxi ; $i += $step ) { my $y = 0; my $x = $i; eval "$func"; if ($@) { # do we have an eval error? # strip the ' at (eval XXX) line XXX' part of the error $@ =~ s/ at \(eval \d+\) line \d+//; push @errors, "$@\n"; } # only graph real points if ( $y =~ /i/ ) { push @errors, "Calculation '$text' resulted in y containing an i +maginary number."; } else { push @points, $x, $y; } } if ( @errors ) { # create dialog box with errors # if there are more than 5, only show the first one my $errors = @errors; my $msg; if ( @errors > 5 ) { $msg = "Showing only the first of $errors errors:\n$errors[0]"; } else { $msg = join( "\n", @errors ); } my $DialogRef = $top->Dialog( -title => 'Calculation Error', -text => $msg, -bitmap => 'warning', -buttons => [ 'OK' ], ); my $selected = $DialogRef->Show(); # I don't use $selected but may + in the future } else { # no errors # create the line and grab the ID my $ID = $cvs->createLine( @points, -smooth => 1, -fill => $color +); # keep track of all IDs so that we can delete them push( @IDs, $ID ); $cvs->viewFit( @IDs ); } } sub axes { $cvs->delete('all'); my $minx = $min->get(); my $maxx = $max->get(); my $step = $stp->get(); my $tick = (abs($minx) + abs($maxx)) / 10; my $maxi = 2 * $maxx; my $mini = -$maxi; my $scale = 10 * $step / $tick; $cvs->createLine($mini, 0, $maxi, 0); $cvs->createLine(0, $mini, 0, $maxi); for ( my $i = $mini; $i <= $maxi; $i += $tick ) { $cvs->createLine($i, -$scale, $i, $scale); $cvs->createLine(-$scale, $i, $scale, $i); } }

Replies are listed 'Best First'.
Re^2: Cheap'n'cheerful Graph Drawer
by zentara (Cardinal) on Jan 29, 2005 at 13:26 UTC
    When I first ran your code, I got:
    Global symbol "$x_axis" requires explicit package name at ./426208.pl +line 152. Global symbol "$y_axis" requires explicit package name at ./426208.pl +line 153. Global symbol "@ticks" requires explicit package name at ./426208.pl l +ine 155.
    After putting a few "my" 's in there, it works nice. Very clever axis shifting.

    I'm not really a human, but I play one on earth. flash japh

      D'oh! I posted the wrong refactoring attempt. I'll update it again.