--- loncom/xml/lonplot.pm 2002/01/08 21:41:20 1.34 +++ loncom/xml/lonplot.pm 2002/01/30 13:27:51 1.46 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Dynamic plot # -# $Id: lonplot.pm,v 1.34 2002/01/08 21:41:20 matthew Exp $ +# $Id: lonplot.pm,v 1.46 2002/01/30 13:27:51 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -28,7 +28,9 @@ # 12/15/01 Matthew # 12/17 12/18 12/19 12/20 12/21 12/27 12/28 12/30 12/31 Matthew # 01/01/02 Matthew -# 01/02 01/03 01/04 Matthew +# 01/02 01/03 01/04 01/07 01/08 01/09 Matthew +# 01/21 Matthew + package Apache::lonplot; use strict; @@ -87,13 +89,13 @@ my %linestyles = errorbars => 3, xerrorbars => [3,4], yerrorbars => [3,4], - xyerrorbars => [4,6,7], + xyerrorbars => [4,6], boxes => 3, - boxerrorbars => [3,4,5], - boxxyerrorbars => [4,6,7], - financebars => 5, - candlesticks => 5, - vector => 2 +# boxerrorbars => [3,4,5], +# boxxyerrorbars => [4,6,7], +# financebars => 5, +# candlesticks => 5, + vector => 4 ); my $int_test = sub {$_[0]=~s/\s+//g;$_[0]=~/^\d+$/}; @@ -112,50 +114,54 @@ my $words_test = sub {$_[0]=~s/\s+/ ## ## ################################################################### my @plot_edit_order = - qw/bgcolor fgcolor height width font transparent grid border/; + qw/bgcolor fgcolor height width font transparent grid border align/; my %plot_defaults = ( height => { default => 200, test => $int_test, description => 'height of image (pixels)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, width => { default => 200, test => $int_test, description => 'width of image (pixels)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, bgcolor => { default => 'xffffff', test => $color_test, description => 'background color of image (xffffff)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, fgcolor => { default => 'x000000', test => $color_test, description => 'foreground color of image (x000000)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, transparent => { default => 'off', test => $onoff_test, description => 'Transparent image', - edit_type => 'on_off' + edit_type => 'onoff' }, grid => { default => 'off', test => $onoff_test, description => 'Display grid', - edit_type => 'on_off' + edit_type => 'onoff' }, border => { default => 'on', test => $onoff_test, description => 'Draw border around plot', - edit_type => 'on_off' + edit_type => 'onoff' }, font => { default => 'medium', @@ -179,13 +185,14 @@ my %key_defaults = default => '', test => $words_test, description => 'Title of key', - edit_type => 'entry' + edit_type => 'entry', + size => '40' }, box => { default => 'off', test => $onoff_test, description => 'Draw a box around the key?', - edit_type => 'on_off' + edit_type => 'onoff' }, pos => { default => 'top right', @@ -203,13 +210,15 @@ my %label_defaults = default => 0, test => $real_test, description => 'x position of label (graph coordinates)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, ypos => { default => 0, test => $real_test, description => 'y position of label (graph coordinates)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, justify => { default => 'left', @@ -220,37 +229,81 @@ my %label_defaults = } ); +my @tic_edit_order = ('location','mirror','start','increment','end'); +my %tic_defaults = + ( + location => { + default => 'border', + test => sub {$_[0]=~/^(border|axis)$/}, + description => 'Location of tick marks', + edit_type => 'choice', + choices => ['border','axis'] + }, + mirror => { + default => 'on', + test => $onoff_test, + description => 'mirror ticks on opposite axis?', + edit_type => 'onoff' + }, + start => { + default => '-10.0', + test => $real_test, + description => 'Start ticks at', + edit_type => 'entry', + size => '10' + }, + increment => { + default => '1.0', + test => $real_test, + description => 'Place a tick every', + edit_type => 'entry', + size => '10' + }, + end => { + default => ' 10.0', + test => $real_test, + description => 'Stop ticks at ', + edit_type => 'entry', + size => '10' + }, + ); + my %axis_defaults = ( color => { default => 'x000000', test => $color_test, description => 'color of axes (x000000)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, xmin => { default => '-10.0', test => $real_test, description => 'minimum x-value shown in plot', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, xmax => { default => ' 10.0', test => $real_test, description => 'maximum x-value shown in plot', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, ymin => { default => '-10.0', test => $real_test, description => 'minimum y-value shown in plot', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, ymax => { default => ' 10.0', test => $real_test, description => 'maximum y-value shown in plot', - edit_type => 'entry' + edit_type => 'entry', + size => '10' } ); @@ -260,24 +313,22 @@ my %curve_defaults = default => 'x000000', test => $color_test, description => 'color of curve (x000000)', - edit_type => 'entry' + edit_type => 'entry', + size => '10' }, name => { default => '', test => $words_test, description => 'name of curve to appear in key', - edit_type => 'entry' + edit_type => 'entry', + size => '20' }, linestyle => { default => 'lines', test => $linestyle_test, - description => 'Style of the axis lines', + description => 'Line style', edit_type => 'choice', - choices => ['lines','linespoints','dots','points','steps', - 'fsteps','histeps','errorbars','xerrorbars', - 'yerrorbars','xyerrorbars','boxes','boxerrorbars', - 'boxxyerrorbars','financebars','candlesticks', - 'vector'] + choices => [keys(%linestyles)] } ); @@ -286,17 +337,19 @@ my %curve_defaults = ## parsing and edit rendering ## ## ## ################################################################### -my (%plot,%key,%axis,$title,$xlabel,$ylabel,@labels,@curves); +my (%plot,%key,%axis,$title,$xlabel,$ylabel,@labels,@curves,%xtics,%ytics); sub start_plot { %plot = (); %key = (); %axis = (); $title = undef; $xlabel = undef; $ylabel = undef; $#labels = -1; $#curves = -1; + %xtics = (); %ytics = (); # my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result=''; &Apache::lonxml::register('Apache::lonplot', - ('title','xlabel','ylabel','key','axis','label','curve')); + ('title','xlabel','ylabel','key','axis','label','curve', + 'xtics','ytics')); push (@Apache::lonxml::namespace,'lonplot'); if ($target eq 'web') { my $inside = &Apache::lonxml::get_all_text("/plot",$$parser[-1]); @@ -313,7 +366,6 @@ sub start_plot { ($token,$parstack,$safeeval,keys(%plot_defaults)); if ($constructtag) { $result = &Apache::edit::rebuild_tag($token); -# $result.= &Apache::edit::handle_insert(); } } return $result; @@ -334,9 +386,7 @@ sub end_plot { my $filename = $ENV{'user.name'}.'_'.$ENV{'user.domain'}. '_'.time.'_'.$$.int(rand(1000)).'_plot.data'; ## Write the plot description to the file - my $fh=Apache::File->new(">$tmpdir$filename"); - print $fh &write_gnuplot_file(); - close($fh); + &write_gnuplot_file($tmpdir,$filename); ## return image tag for the plot $result .= <<"ENDIMAGE"; [-1]); + } elsif ($target eq 'edit') { + $result .= &Apache::edit::tag_start($target,$token,'xtics'); + $result .= &edit_attributes($target,$token,\%tic_defaults, + \@tic_edit_order); + } elsif ($target eq 'modified') { + my $constructtag=&Apache::edit::get_new_args + ($token,$parstack,$safeeval,keys(%tic_defaults)); + if ($constructtag) { + $result = &Apache::edit::rebuild_tag($token); + } + } + return $result; +} + +sub end_xtics { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + my $result = ''; + if ($target eq 'web') { + } elsif ($target eq 'edit') { + $result.=&Apache::edit::tag_end($target,$token); + } + return $result; +} + +##--------------------------------------------------------------- ytics +sub start_ytics { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + my $result=''; + if ($target eq 'web') { + &get_attributes(\%ytics,\%tic_defaults,$parstack,$safeeval, + $tagstack->[-1]); + } elsif ($target eq 'edit') { + $result .= &Apache::edit::tag_start($target,$token,'ytics'); + $result .= &edit_attributes($target,$token,\%tic_defaults, + \@tic_edit_order); + } elsif ($target eq 'modified') { + my $constructtag=&Apache::edit::get_new_args + ($token,$parstack,$safeeval,keys(%tic_defaults)); + if ($constructtag) { + $result = &Apache::edit::rebuild_tag($token); + } + } + return $result; +} + +sub end_ytics { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + my $result = ''; + if ($target eq 'web') { + } elsif ($target eq 'edit') { + $result.=&Apache::edit::tag_end($target,$token); + } + return $result; +} + + ##----------------------------------------------------------------- key sub start_key { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; @@ -366,7 +480,6 @@ sub start_key { ($token,$parstack,$safeeval,keys(%key_defaults)); if ($constructtag) { $result = &Apache::edit::rebuild_tag($token); - $result.= &Apache::edit::handle_insert(); } } return $result; @@ -394,10 +507,12 @@ sub start_title { } elsif ($target eq 'edit') { $result.=&Apache::edit::tag_start($target,$token,'Plot Title'); my $text=&Apache::lonxml::get_all_text("/title",$$parser[-1]); - $result.=''. + $result.=&Apache::edit::end_row(). + &Apache::edit::start_spanning_row(). &Apache::edit::editfield('',$text,'',60,1); } elsif ($target eq 'modified') { my $text=$$parser[-1]->get_text("/title"); + $result.=&Apache::edit::rebuild_tag($token); $result.=&Apache::edit::modifiedfield($token); } return $result; @@ -424,10 +539,12 @@ sub start_xlabel { } elsif ($target eq 'edit') { $result.=&Apache::edit::tag_start($target,$token,'Plot Xlabel'); my $text=&Apache::lonxml::get_all_text("/xlabel",$$parser[-1]); - $result.=''. + $result.=&Apache::edit::end_row(). + &Apache::edit::start_spanning_row(). &Apache::edit::editfield('',$text,'',60,1); } elsif ($target eq 'modified') { my $text=$$parser[-1]->get_text("/xlabel"); + $result.=&Apache::edit::rebuild_tag($token); $result.=&Apache::edit::modifiedfield($token); } return $result; @@ -455,10 +572,12 @@ sub start_ylabel { } elsif ($target eq 'edit') { $result .= &Apache::edit::tag_start($target,$token,'Plot Ylabel'); my $text = &Apache::lonxml::get_all_text("/ylabel",$$parser[-1]); - $result .= ''. + $result .= &Apache::edit::end_row(). + &Apache::edit::start_spanning_row(). &Apache::edit::editfield('',$text,'',60,1); } elsif ($target eq 'modified') { my $text=$$parser[-1]->get_text("/ylabel"); + $result.=&Apache::edit::rebuild_tag($token); $result.=&Apache::edit::modifiedfield($token); } return $result; @@ -490,15 +609,13 @@ sub start_label { $result .= &Apache::edit::tag_start($target,$token,'Plot Label'); $result .= &edit_attributes($target,$token,\%label_defaults); my $text = &Apache::lonxml::get_all_text("/label",$$parser[-1]); - $result .= ''. + $result .= &Apache::edit::end_row(). + &Apache::edit::start_spanning_row(). &Apache::edit::editfield('',$text,'',60,1); } elsif ($target eq 'modified') { - my $constructtag=&Apache::edit::get_new_args + &Apache::edit::get_new_args ($token,$parstack,$safeeval,keys(%label_defaults)); - if ($constructtag) { - $result = &Apache::edit::rebuild_tag($token); - $result.= &Apache::edit::handle_insert(); - } + $result.=&Apache::edit::rebuild_tag($token); my $text=$$parser[-1]->get_text("/label"); $result.=&Apache::edit::modifiedfield($token); } @@ -531,7 +648,7 @@ sub start_curve { $result .= &edit_attributes($target,$token,\%curve_defaults); } elsif ($target eq 'modified') { my $constructtag=&Apache::edit::get_new_args - ($token,$parstack,$safeeval,keys(%label_defaults)); + ($token,$parstack,$safeeval,keys(%curve_defaults)); if ($constructtag) { $result = &Apache::edit::rebuild_tag($token); $result.= &Apache::edit::handle_insert(); @@ -564,12 +681,13 @@ sub start_function { $curves[-1]->{'function'} = &Apache::lonxml::get_all_text("/function",$$parser[-1]); } elsif ($target eq 'edit') { - $result .= &Apache::edit::tag_start($target,$token,'Curve Function'); + $result .= &Apache::edit::tag_start($target,$token,'Gnuplot compatible curve function'); my $text = &Apache::lonxml::get_all_text("/function",$$parser[-1]); - $result .= ''. + $result .= &Apache::edit::end_row(). + &Apache::edit::start_spanning_row(). &Apache::edit::editfield('',$text,'',60,1); } elsif ($target eq 'modified') { - # Why do I do this? + $result.=&Apache::edit::rebuild_tag($token); my $text=$$parser[-1]->get_text("/function"); $result.=&Apache::edit::modifiedfield($token); } @@ -598,12 +716,17 @@ sub start_data { delete($curves[-1]->{'function'}); } my $datatext = &Apache::lonxml::get_all_text("/data",$$parser[-1]); + # Deal with cases where we're given an array... + if ($datatext =~ /^\@/) { + $datatext = &Apache::run::run('return "'.$datatext.'"', + $safeeval,1); + } $datatext =~ s/\s+/ /g; # Need to do some error checking on the @data array - # make sure it's all numbers and make sure each array # is of the same length. my @data; - if ($datatext =~ /,/) { + if ($datatext =~ /,/) { # comma deliminated @data = split /,/,$datatext; } else { # Assume it's space seperated. @data = split / /,$datatext; @@ -624,13 +747,22 @@ sub start_data { $data[$i] = 1.15572734979092; } } + # complain if the number of data points is not the same as + # in previous sets of data. + if (($curves[-1]->{'data'}) && ($#data != $#{@{$curves[-1]->{'data'}->[0]}})){ + &Apache::lonxml::warning + ('Number of data points is not consistent with previous '. + 'number of data points'); + } push @{$curves[-1]->{'data'}},\@data; } elsif ($target eq 'edit') { - $result .= &Apache::edit::tag_start($target,$token,'Curve Data'); + $result .= &Apache::edit::tag_start($target,$token,'Comma or space deliminated curve data'); my $text = &Apache::lonxml::get_all_text("/data",$$parser[-1]); - $result .= ''. + $result .= &Apache::edit::end_row(). + &Apache::edit::start_spanning_row(). &Apache::edit::editfield('',$text,'',60,1); } elsif ($target eq 'modified') { + $result.=&Apache::edit::rebuild_tag($token); my $text=$$parser[-1]->get_text("/data"); $result.=&Apache::edit::modifiedfield($token); } @@ -662,7 +794,6 @@ sub start_axis { ($token,$parstack,$safeeval,keys(%axis_defaults)); if ($constructtag) { $result = &Apache::edit::rebuild_tag($token); - $result.= &Apache::edit::handle_insert(); } } return $result; @@ -714,8 +845,10 @@ sub get_attributes{ } return ; } + ##------------------------------------------------------- write_gnuplot_file sub write_gnuplot_file { + my ($tmpdir,$filename)= @_; my $gnuplot_input = ''; my $curve; # Collect all the colors @@ -734,16 +867,34 @@ sub write_gnuplot_file { $gnuplot_input .= $plot{'font'} . ' '; $gnuplot_input .= 'size '.$plot{'width'}.','.$plot{'height'}.' '; $gnuplot_input .= "@Colors\n"; + # set output + $gnuplot_input .= "set output\n"; # grid $gnuplot_input .= 'set grid'.$/ if ($plot{'grid'} eq 'on'); # border $gnuplot_input .= ($plot{'border'} eq 'on'? 'set border'.$/ : 'set noborder'.$/ ); # title, xlabel, ylabel - $gnuplot_input .= "set output\n"; + # titles $gnuplot_input .= "set title \"$title\"\n" if (defined($title)) ; $gnuplot_input .= "set xlabel \"$xlabel\"\n" if (defined($xlabel)); $gnuplot_input .= "set ylabel \"$ylabel\"\n" if (defined($ylabel)); + # tics + if (%xtics) { + $gnuplot_input .= "set xtics $xtics{'location'} "; + $gnuplot_input .= ( $xtics{'mirror'} eq 'on'?"mirror ":"nomirror "); + $gnuplot_input .= "$xtics{'start'}, "; + $gnuplot_input .= "$xtics{'increment'}, "; + $gnuplot_input .= "$xtics{'end'}\n"; + } + if (%ytics) { + $gnuplot_input .= "set ytics $ytics{'location'} "; + $gnuplot_input .= ( $ytics{'mirror'} eq 'on'?"mirror ":"nomirror "); + $gnuplot_input .= "$ytics{'start'}, "; + $gnuplot_input .= "$ytics{'increment'}, "; + $gnuplot_input .= "$ytics{'end'}\n"; + } + # axis if (%axis) { $gnuplot_input .= "set xrange \[$axis{'xmin'}:$axis{'xmax'}\]\n"; $gnuplot_input .= "set yrange \[$axis{'ymin'}:$axis{'ymax'}\]\n"; @@ -752,7 +903,7 @@ sub write_gnuplot_file { if (%key) { $gnuplot_input .= 'set key '.$key{'pos'}.' '; if ($key{'title'} ne '') { - $gnuplot_input .= 'title "'.$key{'title'}.'" '; + $gnuplot_input .= 'title " '.$key{'title'}.'" '; } $gnuplot_input .= ($key{'box'} eq 'on' ? 'box ' : 'nobox ').$/; } else { @@ -766,7 +917,6 @@ sub write_gnuplot_file { } # curves $gnuplot_input .= 'plot '; - my $datatext = ''; for (my $i = 0;$i<=$#curves;$i++) { $curve = $curves[$i]; $gnuplot_input.= ', ' if ($i > 0); @@ -776,9 +926,12 @@ sub write_gnuplot_file { $curve->{'name'}.'" with '. $curve->{'linestyle'}; } elsif (exists($curve->{'data'})) { - $gnuplot_input.= '\'-\' title "'. - $curve->{'name'}.'" with '. - $curve->{'linestyle'}; + # Store data values in $datatext + my $datatext = ''; + # get new filename + my $datafilename = "$tmpdir/$filename.$i"; + my $fh=Apache::File->new(">$datafilename"); + # Compile data my @Data = @{$curve->{'data'}}; my @Data0 = @{$Data[0]}; for (my $i =0; $i<=$#Data0; $i++) { @@ -788,11 +941,21 @@ sub write_gnuplot_file { } $datatext .= $/; } - $datatext .=$/; + # write file + print $fh $datatext; + close ($fh); + # generate gnuplot text + $gnuplot_input.= '"'.$datafilename.'" title "'. + $curve->{'name'}.'" with '. + $curve->{'linestyle'}; } } - $gnuplot_input .= $/.$datatext; - return $gnuplot_input; + # Write the output to a file. + my $fh=Apache::File->new(">$tmpdir$filename"); + print $fh $gnuplot_input; + close($fh); + # That's all folks. + return ; } #---------------------------------------------- check_inputs @@ -829,23 +992,19 @@ sub edit_attributes { @keys = sort(keys(%$defaults)); } foreach my $attr (@keys) { + # append a ' ' to the description if it doesn't have one already. + my $description = $defaults->{$attr}->{'description'}; + $description .= ' ' if ($description !~ / $/); if ($defaults->{$attr}->{'edit_type'} eq 'entry') { - $result .= &Apache::edit::text_arg( - $defaults->{$attr}->{'description'}, - $attr, - $token); + $result .= &Apache::edit::text_arg + ($description,$attr,$token, + $defaults->{$attr}->{'size'}); } elsif ($defaults->{$attr}->{'edit_type'} eq 'choice') { - $result .= &Apache::edit::select_arg( - $defaults->{$attr}->{'description'}, - $attr, - $defaults->{$attr}->{'choices'}, - $token); - } elsif ($defaults->{$attr}->{'edit_type'} eq 'on_off') { - $result .= &Apache::edit::select_arg( - $defaults->{$attr}->{'description'}, - $attr, - ['on','off'], - $token); + $result .= &Apache::edit::select_arg + ($description,$attr,$defaults->{$attr}->{'choices'},$token); + } elsif ($defaults->{$attr}->{'edit_type'} eq 'onoff') { + $result .= &Apache::edit::select_arg + ($description,$attr,['on','off'],$token); } $result .= '
'; } @@ -869,20 +1028,46 @@ sub insert_plot { } $result .= ">\n"; # Add the components - $result .= &insert_key(); - $result .= &insert_axis(); - $result .= &insert_title(); - $result .= &insert_xlabel(); - $result .= &insert_ylabel(); + # $result .= &insert_key(); + # $result .= &insert_axis(); + # $result .= &insert_title(); + # $result .= &insert_xlabel(); + # $result .= &insert_ylabel(); $result .= &insert_curve(); # close up the $result .= "\n"; return $result; } +sub insert_tics { + my $result; + $result .= &insert_xtics() . &insert_ytics; + return $result; +} + +sub insert_xtics { + my $result; + $result .= "\n {'default'}\" "; + } + $result .= "/>\n"; + return $result; +} + +sub insert_ytics { + my $result; + $result .= "\n {'default'}\" "; + } + $result .= "/>\n"; + return $result; +} + sub insert_key { my $result; - $result .= " {'default'}\"\n"; } @@ -892,7 +1077,7 @@ sub insert_key { sub insert_axis{ my $result; - $result .= ' {'default'}\"\n"; } @@ -900,13 +1085,13 @@ sub insert_axis{ return $result; } -sub insert_title { return " \n"; } -sub insert_xlabel { return " \n"; } -sub insert_ylabel { return " \n"; } +sub insert_title { return "\n \n"; } +sub insert_xlabel { return "\n \n"; } +sub insert_ylabel { return "\n \n"; } sub insert_label { my $result; - $result .= '