File:  [LON-CAPA] / loncom / interface / statistics / lonstudentassessment.pm
Revision 1.31: download - view: text, annotated - select for diffs
Mon Mar 3 19:28:29 2003 UTC (21 years, 4 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Big Changes:
   Started adding different output options.
   No longer begins computing before the user has made selections.

The code is being simplified to some degree and obfuscated in other ways.

Each output type has three subroutines associated with it: initialize,
outputstudent, and finish.  Function pointers are used (defaulting the output
mode to 'html').  Currently excel and csv output both result in
"Not Implemented".

Renamed &CreateTableHeadings to &html_initialize, &ChartOutputStudent to
&html_outputstudent, and added &html_finish.  Each of these routines was
changed to make it responsible for *all* html output of the chart, other than
the selection form.

Still remaining to be filled in are the routines for csv and excel output.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonstudentassessment.pm,v 1.31 2003/03/03 19:28:29 matthew Exp $
    4: #
    5: # Copyright Michigan State University Board of Trustees
    6: #
    7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    8: # LON-CAPA is free software; you can redistribute it and/or modify
    9: # it under the terms of the GNU General Public License as published by
   10: # the Free Software Foundation; either version 2 of the License, or
   11: # (at your option) any later version.
   12: #
   13: # LON-CAPA is distributed in the hope that it will be useful,
   14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16: # GNU General Public License for more details.
   17: #
   18: # You should have received a copy of the GNU General Public License
   19: # along with LON-CAPA; if not, write to the Free Software
   20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   21: #
   22: # /home/httpd/html/adm/gpl.txt
   23: #
   24: # http://www.lon-capa.org/
   25: #
   26: # (Navigate problems for statistical reports
   27: #
   28: #######################################################
   29: #######################################################
   30: 
   31: =pod
   32: 
   33: =head1 NAME
   34: 
   35: lonstudentassessment
   36: 
   37: =head1 SYNOPSIS
   38: 
   39: Presents assessment data about a student or a group of students.
   40: 
   41: =head1 Subroutines
   42: 
   43: =over 4 
   44: 
   45: =cut
   46: 
   47: #######################################################
   48: #######################################################
   49: 
   50: package Apache::lonstudentassessment;
   51: 
   52: use strict;
   53: use Apache::lonstatistics;
   54: use Apache::lonhtmlcommon;
   55: use Apache::loncoursedata;
   56: use Apache::lonnet; # for logging porpoises
   57: use Spreadsheet::WriteExcel;
   58: 
   59: #######################################################
   60: #######################################################
   61: =pod
   62: 
   63: =item Package Variables
   64: 
   65: =over 4
   66: 
   67: =item $Statistics Hash ref to store student data.  Indexed by symb,
   68:       contains hashes with keys 'score' and 'max'.
   69: 
   70: =cut
   71: 
   72: #######################################################
   73: #######################################################
   74: 
   75: my $Statistics;
   76: 
   77: #######################################################
   78: #######################################################
   79: 
   80: =pod
   81: 
   82: =item $show_links 'yes' or 'no' for linking to student performance data
   83: 
   84: =item $output_mode 'html', 'excel', or 'csv' for output mode
   85: 
   86: =item $show 'all' or 'totals' determines how much data is output
   87: 
   88: =cut
   89: 
   90: #######################################################
   91: #######################################################
   92: my $show_links;
   93: my $output_mode;
   94: my $show;
   95: 
   96: #######################################################
   97: #######################################################
   98: # End of package variable declarations
   99: 
  100: =pod
  101: 
  102: =back
  103: 
  104: =cut
  105: 
  106: #######################################################
  107: #######################################################
  108: 
  109: =pod
  110: 
  111: =item &BuildStudentAssessmentPage()
  112: 
  113: Inputs: 
  114: 
  115: =over 4
  116: 
  117: =item $r Apache Request
  118: 
  119: =item $c Apache Connection 
  120: 
  121: =back
  122: 
  123: =cut
  124: 
  125: #######################################################
  126: #######################################################
  127: sub BuildStudentAssessmentPage {
  128:     my ($r,$c)=@_;
  129:     undef($Statistics);
  130:     #
  131:     # Print out the HTML headers for the interface
  132:     #    This also parses the output mode selector
  133:     #    This step must always be done.
  134:     $r->print(&CreateInterface());
  135:     $r->print('<input type="hidden" name="notfirstrun" value="true" />');
  136:     $r->rflush();
  137:     if (! exists($ENV{'form.notfirstrun'})) {
  138:         $r->print(<<ENDMSG);
  139: <p>
  140: <font size="+1">
  141: Please make your selections in the boxes above and hit 
  142: the button marked &quot;Update&nbsp;Display&quot;.
  143: </font>
  144: </p>
  145: ENDMSG
  146:         return;
  147:     }
  148:     #
  149:     #
  150:     my $initialize     = \&html_initialize;
  151:     my $output_student = \&html_outputstudent;
  152:     my $finish         = \&html_finish;
  153:     #
  154:     if ($output_mode eq 'excel') {
  155:         $initialize     = \&excel_initialize;
  156:         $output_student = \&excel_outputstudent;
  157:         $finish         = \&excel_finish;
  158:     } elsif ($output_mode eq 'csv') {
  159:         $initialize     = \&csv_initialize;
  160:         $output_student = \&csv_outputstudent;
  161:         $finish         = \&csv_finish;
  162:     }
  163:     #
  164:     if($c->aborted()) {  return ; }
  165:     #
  166:     # Call the initialize routine selected above
  167:     $initialize->($r);
  168:     foreach my $student (@Apache::lonstatistics::Students) {
  169:         if($c->aborted()) { 
  170:             $finish->($r);
  171:             return ; 
  172:         }
  173:         # Call the output_student routine selected above
  174:         $output_student->($r,$student);
  175:     }
  176:     # Call the "finish" routine selected above
  177:     $finish->($r);
  178:     #
  179:     return;
  180: }
  181: 
  182: #######################################################
  183: #######################################################
  184: 
  185: sub get_student_fields_to_show {
  186:     my @to_show = @Apache::lonstatistics::SelectedStudentData;
  187:     foreach (@to_show) {
  188:         if ($_ eq 'all') {
  189:             @to_show = @Apache::lonstatistics::StudentDataOrder;
  190:             last;
  191:         }
  192:     }
  193:     return @to_show;
  194: }
  195: 
  196: sub get_sequences_to_show {
  197:     my @Sequences;
  198:     foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {
  199:         foreach my $sequence (@Apache::lonstatistics::Sequences) {
  200:             next if ($sequence->{'symb'} ne $map_symb && $map_symb ne 'all');
  201:             next if ($sequence->{'num_assess'} < 1);
  202:             push (@Sequences,$sequence);
  203:         }
  204:     }
  205:     return @Sequences;
  206: }
  207: 
  208: 
  209: #######################################################
  210: #######################################################
  211: 
  212: =pod
  213: 
  214: =item &CreateInterface()
  215: 
  216: Called by &BuildStudentAssessmentPage to create the top part of the
  217: page which displays the chart.
  218: 
  219: Inputs: None
  220: 
  221: Returns:  A string containing the HTML for the headers and top table for 
  222: the chart page.
  223: 
  224: =cut
  225: 
  226: #######################################################
  227: #######################################################
  228: sub CreateInterface {
  229:     my $Str = '';
  230: #    $Str .= &CreateLegend();
  231:     $Str .= '<table cellspacing="5">'."\n";
  232:     $Str .= '<tr>';
  233:     $Str .= '<td align="center"><b>Sections</b></td>';
  234:     $Str .= '<td align="center"><b>Student Data</b></td>';
  235:     $Str .= '<td align="center"><b>Sequences and Folders</b></td>';
  236:     $Str .= '<td align="center"><b>Output Format</b></td>';
  237:     $Str .= '</tr>'."\n";
  238:     #
  239:     $Str .= '<tr><td align="center">'."\n";
  240:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
  241:     $Str .= '</td><td align="center">';
  242:     my $only_seq_with_assessments = sub { 
  243:         my $s=shift;
  244:         if ($s->{'num_assess'} < 1) { 
  245:             return 0;
  246:         } else { 
  247:             return 1;
  248:         }
  249:     };
  250:     $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
  251:                                                       5,undef);
  252:     $Str .= '</td><td>'."\n";
  253:     $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
  254:                                               $only_seq_with_assessments);
  255:     $Str .= '</td><td>'."\n";
  256:     $Str .= &CreateAndParseOutputSelector();
  257:     $Str .= '</td></tr>'."\n";
  258:     $Str .= '</table>'."\n";
  259:     return $Str;
  260: }
  261: 
  262: #######################################################
  263: #######################################################
  264: 
  265: =pod
  266: 
  267: =item &CreateAndParseOutputSelector()
  268: 
  269: =cut
  270: 
  271: #######################################################
  272: #######################################################
  273: sub CreateAndParseOutputSelector {
  274:     my $Str = '';
  275:     my $elementname = 'outputmode';
  276:     #
  277:     # Format for output options is 'mode, restrictions';
  278:     my @Options = ('html, with links','html, without links',
  279:                    'html, totals only','excel, totals only',
  280:                    'csv, totals only','csv, everything');
  281:     my $selected = 'html, with links';
  282:     if (exists($ENV{'form.'.$elementname})) {
  283:         if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
  284:             $selected = $ENV{'form.'.$elementname}->[0];
  285:         } else {
  286:             $selected = $ENV{'form.'.$elementname};
  287:         }
  288:     }
  289:     #
  290:     # Set package variables describing output mode
  291:     $show_links  = 'no';
  292:     $output_mode = 'html';
  293:     $show        = 'all';
  294:     my ($mode,$restriction) = split(',',$selected);
  295:     $restriction =~ s/^\s*//;
  296:     if ($mode =~ /^(html|excel|csv)$/) {
  297:         $output_mode = $mode;
  298:     } else {
  299:         $output_mode = 'html';
  300:     }
  301:     if ($restriction eq 'with links') {
  302:         $show_links = 'yes';
  303:     } else {
  304:         $show_links = 'no';
  305:     }
  306:     if ($restriction eq 'totals only') {
  307:         $show = 'totals';
  308:     } else {
  309:         $show = 'everything';
  310:     }
  311:     #
  312:     # Build the form element
  313:     $Str = qq/<select size="5" name="$elementname">/;
  314:     foreach my $option (@Options) {
  315:         $Str .= qq/\n    <option value="$option"/;
  316:         $Str .= " selected " if ($option eq $selected);
  317:         $Str .= ">$option<\/option>";
  318:     }
  319:     $Str .= "\n</select>";
  320:     return $Str;
  321: }
  322: 
  323: #######################################################
  324: #######################################################
  325: 
  326: =pod
  327: 
  328: =head2 HTML output routines
  329: 
  330: =item &html_initialize($r)
  331: 
  332: Create labels for the columns of student data to show.
  333: 
  334: =item &html_outputstudent($r,$student)
  335: 
  336: Return a line of the chart for a student.
  337: 
  338: =item &html_finish($r)
  339: 
  340: =cut
  341: 
  342: #######################################################
  343: #######################################################
  344: {
  345:     my $padding;
  346:     my $count;
  347: 
  348: sub html_initialize {
  349:     my ($r) = @_;
  350:     #
  351:     $padding = ' 'x3;
  352:     $count = 1;
  353:     #
  354:     my $Str = "<pre>\n";
  355:     # First, the @StudentData fields need to be listed
  356:     my @to_show = &get_student_fields_to_show();
  357:     foreach my $field (@to_show) {
  358:         my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
  359:         my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
  360:         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
  361:         $Str .= $title.' 'x($width-$base).$padding;
  362:     }
  363:     # Now the selected sequences need to be listed
  364:     foreach my $sequence (&get_sequences_to_show) {
  365:         my $title = $sequence->{'title'};
  366:         my $base  = $sequence->{'base_width'};
  367:         my $width = $sequence->{'width'};
  368:         $Str .= $title.' 'x($width-$base).$padding;
  369:     }
  370:     $Str .= "total (of shown problems)</pre>\n";
  371:     $Str .= "<pre>";
  372:     $r->print($Str);
  373:     $r->rflush();
  374:     return;
  375: }
  376: 
  377: sub html_outputstudent {
  378:     my ($r,$student) = @_;
  379:     my $Str = '';
  380:     # First, the @StudentData fields need to be listed
  381:     my @to_show = &get_student_fields_to_show();
  382:     foreach my $field (@to_show) {
  383:         my $title=$student->{$field};
  384:         my $base = length($title);
  385:         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
  386:         $Str .= $title.' 'x($width-$base).$padding;
  387:     }
  388:     # Get ALL the students data
  389:     my %StudentsData;
  390:     my @tmp = &Apache::loncoursedata::get_current_state
  391:         ($student->{'username'},$student->{'domain'},undef,
  392:          $ENV{'request.course.id'});
  393:     if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
  394:         %StudentsData = @tmp;
  395:     }
  396:     if (scalar(@tmp) < 1) {
  397:         $Str .= '<font color="blue">No Course Data</font>'."\n";
  398:         $r->print($Str);
  399:         $r->rflush();
  400:         return;
  401:     }
  402:     #
  403:     # By sequence build up the data
  404:     my $studentstats;
  405:     my $PerformanceStr = '';
  406:     foreach my $seq (&get_sequences_to_show) {
  407:         my ($performance,$score,$seq_max) =
  408:             &StudentPerformanceOnSequence($student,\%StudentsData,
  409:                                           $seq,$show_links);
  410:         my $ratio = $score.'/'.$seq_max;
  411:         #
  412:         if ($show eq 'totals') {
  413:             $performance = ' 'x(length($seq_max)-length($score)).$ratio;
  414:             $performance .= ' 'x($seq->{'width'}-length($performance));
  415:         } else {
  416:             # Pad with extra spaces
  417:             $performance .= ' 'x($seq->{'width'}-$seq_max-
  418:                                  length($ratio)
  419:                                  ).$ratio;
  420:         }
  421:         #
  422:         $Str .= $performance.$padding;
  423:         #
  424:         $studentstats->{$seq->{'symb'}}->{'score'}= $score;
  425:         $studentstats->{$seq->{'symb'}}->{'max'}  = $seq_max;
  426:     }
  427:     #
  428:     # Total it up and store the statistics info.
  429:     my ($score,$max) = (0,0);
  430:     while (my ($symb,$seq_stats) = each (%{$studentstats})) {
  431:         $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
  432:         $Statistics->{$symb}->{'max'}   += $seq_stats->{'max'};
  433:         $score += $seq_stats->{'score'};
  434:         $max   += $seq_stats->{'max'};
  435:     }
  436:     $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
  437:     $Str .= " \n";
  438:     $r->print($Str);
  439:     #
  440:     $count++;
  441:     if($count % 5 == 0) {
  442:         $r->print("</pre><pre>");
  443:     }
  444:     #
  445:     $r->rflush();
  446:     return;
  447: }    
  448: 
  449: sub html_finish {
  450:     my ($r) = @_;
  451:     $r->print("</pre>\n"); 
  452:     $r->rflush();
  453:     return;
  454: }
  455: 
  456: }
  457: 
  458: #######################################################
  459: #######################################################
  460: 
  461: =pod
  462: 
  463: =head2 EXCEL subroutines
  464: 
  465: =item &excel_initialize($r)
  466: 
  467: =item &excel_outputstudent($r,$student)
  468: 
  469: =item &excel_finish($r)
  470: 
  471: =cut
  472: 
  473: #######################################################
  474: #######################################################
  475: {
  476: 
  477: my $excel_sheet;
  478: 
  479: sub excel_initialize {
  480:     my ($r) = @_;
  481:     #
  482:     $r->print("<h1>Not implemented yet</h1>");
  483:     return;
  484:     my $filename = '/prtspool/'.
  485:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
  486:             time.'_'.rand(1000000000).'.xls';
  487:     $excel_sheet = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
  488:     if (! defined($excel_sheet)) {
  489:         $r->log_error("Error creating excel spreadsheet $filename: $!");
  490:         $r->print("Problems creating new Excel file.  ".
  491:                   "This error has been logged.  ".
  492:                   "Please alert your LON-CAPA administrator");
  493:         return 0;
  494:     }
  495:     #
  496:     # The excel spreadsheet stores temporary data in files, then put them
  497:     # together.  If needed we should be able to disable this (memory only).
  498:     # The temporary directory must be specified before calling 'addworksheet'.
  499:     # File::Temp is used to determine the temporary directory.
  500:     $excel_sheet->set_tempdir($Apache::lonnet::tmpdir);
  501:     #
  502:     # Determine the name to give the worksheet
  503: #    $excel_sheet->addworksheet();
  504: 
  505:     return;
  506: }
  507: 
  508: sub excel_outputstudent {
  509:     my ($r,$student) = @_;
  510: }
  511: 
  512: sub excel_finish {
  513:     my ($r) = @_;
  514: }
  515: 
  516: }
  517: #######################################################
  518: #######################################################
  519: 
  520: =pod
  521: 
  522: =head2 CSV output routines
  523: 
  524: =item &csv_initialize($r)
  525: 
  526: =item &csv_outputstudent($r,$student)
  527: 
  528: =item &csv_finish($r)
  529: 
  530: =cut
  531: 
  532: #######################################################
  533: #######################################################
  534: {
  535: 
  536: sub csv_initialize{
  537:     my ($r) = @_;
  538:     $r->print("<h1>Not implemented yet</h1>");
  539:     return;
  540: }
  541: 
  542: sub csv_outputstudent {
  543:     my ($r,$student) = @_;
  544: }
  545: 
  546: sub csv_finish {
  547:     my ($r) = @_;
  548: }
  549: 
  550: }
  551: 
  552: #######################################################
  553: #######################################################
  554: 
  555: =pod
  556: 
  557: =item &StudentPerformanceOnSequence()
  558: 
  559: Inputs:
  560: 
  561: =over 4
  562: 
  563: =item $student
  564: 
  565: =item $studentdata Hash ref to all student data
  566: 
  567: =item $seq Hash ref, the sequence we are working on
  568: 
  569: =item $links if defined we will output links to each resource.
  570: 
  571: =back
  572: 
  573: =cut
  574: 
  575: #######################################################
  576: #######################################################
  577: sub StudentPerformanceOnSequence {
  578:     my ($student,$studentdata,$seq,$links,$totalonly) = @_;
  579:     $totalonly = 0 if (! defined($totalonly));
  580:     $links = 'no' if (! defined($links));
  581:     my $Str = '';
  582:     my ($sum,$max) = (0,0);
  583:     foreach my $resource (@{$seq->{'contents'}}) {
  584:         next if ($resource->{'type'} ne 'assessment');
  585:         my $resource_data = $studentdata->{$resource->{'symb'}};
  586:         my $value = '';
  587:         foreach my $partnum (@{$resource->{'parts'}}) {
  588:             $max++;
  589:             my $symbol = ' '; # default to space
  590:             #
  591:             if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
  592:                 my $status = $resource_data->{'resource.'.$partnum.'.solved'};
  593:                 if ($status eq 'correct_by_override') {
  594:                     $symbol = '+';
  595:                     $sum++;
  596:                 } elsif ($status eq 'incorrect_by_override') {
  597:                     $symbol = '-';
  598:                 } elsif ($status eq 'ungraded_attempted') {
  599:                     $symbol = '#';
  600:                 } elsif ($status eq 'incorrect_attempted')  {
  601:                     $symbol = '.';
  602:                 } elsif ($status eq 'excused') {
  603:                     $symbol = 'x';
  604:                     $max--;
  605:                 } elsif ($status eq 'correct_by_student' &&
  606:                     exists($resource_data->{'resource.'.$partnum.'.tries'})){
  607:                     my $num = $resource_data->{'resource.'.$partnum.'.tries'};
  608:                     if ($num > 9) {
  609:                         $symbol = '*';
  610:                     } elsif ($num > 0) {
  611:                         $symbol = $num;
  612:                     } else {
  613:                         $symbol = ' ';
  614:                     }
  615:                     $sum++;
  616:                 } else {
  617:                     $symbol = ' ';
  618:                 }
  619:             } else {
  620:                 # Unsolved.  Did they try?
  621:                 if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
  622:                     $symbol = '.';
  623:                 } else {
  624:                     $symbol = ' ';
  625:                 }
  626:             }
  627:             #
  628:             if ($links eq 'yes' && $symbol ne ' ') {
  629:                 $symbol = '<a href="/adm/grades'.
  630:                     '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
  631:                         '&student='.$student->{'username'}.
  632:                             '&domain='.$student->{'domain'}.
  633:                                 '&command=submission">'.$symbol.'</a>';
  634:             }
  635:             $value .= $symbol;
  636:         }
  637:         $Str .= $value;
  638:     }
  639:     return ($Str,$sum,$max);
  640: }
  641: 
  642: #######################################################
  643: #######################################################
  644: sub StudentAverageTotal {
  645:     my ($cache, $students, $sequenceKeys)=@_;
  646:     my $Str = "\n<b>Summary Tables:</b>\n";
  647:     my %Correct = ();
  648:     my $ProblemsSolved = 0;
  649:     my $TotalProblems = 0;
  650:     my $StudentCount = 0;
  651: 
  652:     foreach my $name (@$students) {
  653:         $StudentCount++;
  654:         foreach my $sequence (@$sequenceKeys) {
  655:             $Correct{$sequence} +=
  656: 	       $cache->{$name.':'.$sequence.':problemsCorrect'};
  657:         }
  658: 	$ProblemsSolved += $cache->{$name.':problemsSolved'};
  659:         $TotalProblems += $cache->{$name.':totalProblems'};
  660:     }
  661:     if ($StudentCount) { 
  662:         $ProblemsSolved = sprintf( "%.2f", 
  663:                              $ProblemsSolved/$StudentCount);
  664:         $TotalProblems /= $StudentCount;
  665:     } else {
  666:         $ProblemsSolved = 0;
  667:         $TotalProblems  = 0;
  668:     }
  669: 
  670:     $Str .= '<table border=2 cellspacing="1">'."\n";
  671:     $Str .= '<tr><td><b>Students Count</b></td><td><b>'.
  672:             $StudentCount.'</b></td></tr>'."\n";
  673:     $Str .= '<tr><td><b>Total Problems</b></td><td><b>'.
  674:             $TotalProblems.'</b></td></tr>'."\n";
  675:     $Str .= '<tr><td><b>Average Correct</b></td><td><b>'.
  676:             $ProblemsSolved.'</b></td></tr>'."\n";
  677:     $Str .= '</table>'."\n";
  678: 
  679:     $Str .= '<table border=2 cellspacing="1">'."\n";
  680:     $Str .= '<tr><th>Title</th><th>Total Problems</th>'.
  681:             '<th>Average Correct</th></tr>'."\n";
  682:     foreach my $S(@$sequenceKeys) {
  683:         my $title=$cache->{$S.':title'};
  684: 	#$Str .= $cache->{$S.':problems'};
  685: 	#my @problems=split(':', $cache->{$S.':problems'});
  686: 	#my $pCount=scalar @problems;
  687: 	my $pCount=MaxSeqPr($cache,@$students[0],$S);
  688:         my $crr;
  689: 	if ($StudentCount) {
  690:             $crr=sprintf( "%.2f", $Correct{$S}/$StudentCount );
  691:         } else {
  692:             $crr="0.00";
  693:         }
  694:         $Str .= '<tr><td>'.$title.
  695:                 '</td><td align=center>'.$pCount.
  696:                 '</td><td align=center>'.$crr.
  697:                 '</td></tr>'."\n";
  698:     }
  699: 
  700:     $Str .= '</table>'."\n";
  701: 
  702:     return $Str;
  703: }
  704: 
  705: #######################################################
  706: #######################################################
  707: 
  708: =pod
  709: 
  710: =item &CreateLegend()
  711: 
  712: This function returns a formatted string containing the legend for the
  713: chart.  The legend describes the symbols used to represent grades for
  714: problems.
  715: 
  716: =cut
  717: 
  718: #######################################################
  719: #######################################################
  720: sub CreateLegend {
  721:     my $Str = "<p><pre>".
  722:               "   1  correct by student in 1 try\n".
  723:               "   7  correct by student in 7 tries\n".
  724:               "   *  correct by student in more than 9 tries\n".
  725: 	      "   +  correct by hand grading or override\n".
  726:               "   -  incorrect by override\n".
  727: 	      "   .  incorrect attempted\n".
  728: 	      "   #  ungraded attempted\n".
  729:               "      not attempted (blank field)\n".
  730: 	      "   x  excused".
  731:               "</pre><p>";
  732:     return $Str;
  733: }
  734: 
  735: #######################################################
  736: #######################################################
  737: 
  738: =pod 
  739: 
  740: =back
  741: 
  742: =cut
  743: 
  744: #######################################################
  745: #######################################################
  746: 
  747: 1;
  748: 
  749: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>