--- loncom/interface/statistics/lonstudentassessment.pm 2004/02/11 21:15:01 1.87 +++ loncom/interface/statistics/lonstudentassessment.pm 2004/05/18 17:43:36 1.101 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentassessment.pm,v 1.87 2004/02/11 21:15:01 matthew Exp $ +# $Id: lonstudentassessment.pm,v 1.101 2004/05/18 17:43:36 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -153,6 +153,8 @@ sub BuildStudentAssessmentPage { # $single_student_mode = 0; $single_student_mode = 1 if ($ENV{'form.SelectedStudent'}); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['selectstudent']); if ($ENV{'form.selectstudent'}) { &Apache::lonstatistics::DisplayClasslist($r); return; @@ -306,6 +308,7 @@ the chart page. ####################################################### sub CreateInterface { my $Str = ''; + $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Chart'); # $Str .= &CreateLegend(); $Str .= ''."\n"; $Str .= ''; @@ -401,7 +404,7 @@ my @OutputOptions = }, { name => 'CSV', value => 'csv', - description => 'Output a comma seperated values file suitable for '. + description => 'Output a comma separated values file suitable for '. 'import into a spreadsheet program. Using this method as opposed '. 'to Excel output allows you to organize your data before importing'. ' it into a spreadsheet program.', @@ -473,6 +476,9 @@ my @OutputDataOptions = sequence_sum => 1, sequence_max => 1, grand_total => 1, + grand_maximum => 1, + summary_table => 1, + maximum_row => 1, shortdesc => 'Total Score and Maximum Possible for each '. 'Sequence or Folder', longdesc => 'The score of each student as well as the '. @@ -488,6 +494,9 @@ my @OutputDataOptions = sequence_sum => 1, sequence_max => 1, grand_total => 1, + grand_maximum => 1, + summary_table => 1, + maximum_row => 1, shortdesc => 'Score on each Problem Part', longdesc =>'The students score on each problem part, computed as'. 'the part weight * part awarded', @@ -502,6 +511,9 @@ my @OutputDataOptions = sequence_sum => 0, sequence_max => 0, grand_total => 0, + grand_maximum => 0, + summary_table => 0, + maximum_row => 0, shortdesc => 'Number of Tries before success on each Problem Part', longdesc =>'The number of tries before success on each problem part.', }, @@ -515,6 +527,9 @@ my @OutputDataOptions = sequence_sum => 1, sequence_max => 1, grand_total => 1, + grand_maximum => 1, + summary_table => 1, + maximum_row => 0, shortdesc => 'Number of Problem Parts completed successfully.', longdesc => 'The Number of Problem Parts completed successfully and '. 'the maximum possible for each student', @@ -616,12 +631,46 @@ sub html_initialize { my $width=$Apache::lonstatistics::StudentData{$field}->{'width'}; $Str .= $title.' 'x($width-$base).$padding; } - # Now the selected sequences need to be listed + # + # Compute the column widths and output the sequence titles foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()){ - my $title = $sequence->{'title'}; - my $base = $sequence->{'base_width'}; - my $width = $sequence->{'width'}; - $Str .= $title.' 'x($width-$base).$padding; + # + # Comptue column widths + $sequence->{'width_sum'} = 0; + if ($chosen_output->{'sequence_sum'}) { + if ($chosen_output->{'every_problem'}) { + # Use 1 digit for a space + $sequence->{'width_sum'} += 1; + } + # Use 3 digits for the sum + $sequence->{'width_sum'} += 3; + } + if ($chosen_output->{'sequence_max'}) { + if ($sequence->{'width_sum'}>0) { + # One digit for the '/' + $sequence->{'width_sum'} +=1; + } + # Use 3 digits for the total + $sequence->{'width_sum'}+=3; + } + # + if ($chosen_output->{'every_problem'}) { + # one problem per digit + $sequence->{'width_problem'} = $sequence->{'num_assess_parts'}; + } else { + $sequence->{'width_problem'} = 0; + } + $sequence->{'width_total'} = $sequence->{'width_problem'} + + $sequence->{'width_sum'}; + if ($sequence->{'width_total'} < length(&HTML::Entities::decode($sequence->{'title'}))) { + $sequence->{'width_total'} = length(&HTML::Entities::decode($sequence->{'title'})); + } + # + # Output the sequence titles + $Str .= + $sequence->{'title'}.' 'x($sequence->{'width_total'}- + length($sequence->{'title'}) + ).$padding; } $Str .= "total\n"; $Str .= "
";
@@ -675,17 +724,28 @@ sub html_outputstudent {
                 &StudentPerformanceOnSequence($student,\%StudentsData,
                                               $seq,$show_links);
         }
-        my $ratio = sprintf("%3d",$score).'/'.sprintf("%3d",$seq_max);
+        my $ratio='';
+        if ($chosen_output->{'every_problem'}) {
+            $ratio .= ' ';
+        }
+        if ($chosen_output->{'sequence_sum'} && $score ne ' ') {
+            $ratio .= sprintf("%3.0f",$score);
+        } elsif($chosen_output->{'sequence_sum'}) {
+            $ratio .= ' 'x3;
+        }
+        if ($chosen_output->{'sequence_max'}) {
+            if ($chosen_output->{'sequence_sum'}) {
+                $ratio .= '/';
+            }
+            $ratio .= sprintf("%3.0f",$seq_max);
+        }
         #
-        if ($chosen_output->{'sequence_sum'}) {
-            $performance  = $ratio;
-            $performance .= ' 'x($seq->{'width'}-length($ratio));
-        } else {
-            # Pad with extra spaces
-            $performance .= ' 'x($seq->{'width'}-$performance_length-
-                                 length($ratio)
-                                 ).$ratio;
+        if (! $chosen_output->{'every_problem'}) {
+            $performance = '';
+	    $performance_length=0;
         }
+        $performance .= ' 'x($seq->{'width_total'}-$performance_length-$seq->{'width_sum'}).
+            $ratio;
         #
         $Str .= $performance.$padding;
         #
@@ -694,15 +754,21 @@ sub html_outputstudent {
     }
     #
     # Total it up and store the statistics info.
-    my ($score,$max) = (0,0);
+    my ($score,$max);
     while (my ($symb,$seq_stats) = each (%{$studentstats})) {
         $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
         if ($Statistics->{$symb}->{'max'} < $seq_stats->{'max'}) {
             $Statistics->{$symb}->{'max'} = $seq_stats->{'max'};
         }
-        $score += $seq_stats->{'score'};
+        if ($seq_stats->{'score'} ne ' ') {
+            $score += $seq_stats->{'score'};
+            $Statistics->{$symb}->{'num_students'}++;
+        }
         $max   += $seq_stats->{'max'};
     }
+    if (! defined($score)) {
+        $score = ' 'x3;
+    }
     $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
     $Str .= " \n";
     #
@@ -717,10 +783,12 @@ sub html_finish {
     #
     # Check for suppressed output and close the progress window if so
     $r->print("
\n"); - if ($single_student_mode) { - $r->print(&SingleStudentTotal()); - } else { - $r->print(&StudentAverageTotal()); + if ($chosen_output->{'summary_table'}) { + if ($single_student_mode) { + $r->print(&SingleStudentTotal()); + } else { + $r->print(&StudentAverageTotal()); + } } $r->rflush(); return; @@ -728,37 +796,42 @@ sub html_finish { sub StudentAverageTotal { my $Str = "

Summary Tables

\n"; - my $num_students = scalar(@Apache::lonstatistics::Students); - my $total_ave = 0; - my $total_max = 0; +# my $max_students; +# my $total_ave = 0; +# my $total_max = 0; $Str .= '
'."\n"; $Str .= "\n"; foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { my $ave; - if ($num_students > $nodata_count) { - $ave = int(100*($Statistics->{$seq->{'symb'}}->{'score'}/ - ($num_students-$nodata_count)))/100; + my $num_students = $Statistics->{$seq->{'symb'}}->{'num_students'}; +# if ($num_students > $max_students) { +# $max_students = $num_students; +# } + if ($num_students > 0) { + $ave = int(100* + ($Statistics->{$seq->{'symb'}}->{'score'}/$num_students) + )/100; } else { $ave = 0; } - $total_ave += $ave; +# $total_ave += $ave; my $max = $Statistics->{$seq->{'symb'}}->{'max'}; - $total_max += $max; +# $total_max += $max; $ave = sprintf("%.2f",$ave); $Str .= ''. ''. ''."\n"; } - $total_ave = sprintf('%.2f',$total_ave); # only two digit - $Str .= "
TitleAverageMaximum
'.$seq->{'title'}.''.$ave.' '.$max.' '.'
\n"; - $Str .= ''."\n"; - $Str .= ''. - "\n"; - $Str .= ''. - ''. - ''. - ''; +# $total_ave = sprintf('%.2f',$total_ave); # only two digit $Str .= "
Number of StudentsAverageMaximum
'.($num_students-$nodata_count).''.$total_ave.' '.''.$total_max.' '.'
\n"; +# $Str .= ''."\n"; +# $Str .= ''. +# "\n"; +# $Str .= ''. +# ''. +# ''. +# ''; +# $Str .= "
Number of StudentsAverageMaximum
'.$max_students.''.$total_ave.' '.''.$total_max.' '.'
\n"; return $Str; } @@ -819,6 +892,7 @@ my %prog_state; # progress window state my $request_aborted; my $total_formula; +my $maximum_formula; sub excel_initialize { my ($r) = @_; @@ -831,11 +905,17 @@ sub excel_initialize { undef (%prog_state); undef ($request_aborted); undef ($total_formula); + undef ($maximum_formula); # my $total_columns = scalar(&get_student_fields_to_show()); + my $num_students = scalar(@Apache::lonstatistics::Students); + # foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + if ($chosen_output->{'every_problem'}) { + $total_columns += $seq->{'num_assess_parts'}; + } # Add 2 because we need a 'sequence_sum' and 'total' column for each - $total_columns += $seq->{'num_assess_parts'}+2; + $total_columns += 2; } if ($chosen_output->{'base'} eq 'tries' && $total_columns > 255) { $r->print(<{'summary_table'}) { + $summary_header_row = $rows_output++; + $rows_output+= scalar(&Apache::lonstatistics::Sequences_with_Assess()); + $rows_output++; + } + my $sequence_name_row = $rows_output++; + my $resource_name_row = $rows_output++; + my $maximum_data_row = $rows_output++; + if (! $chosen_output->{'maximum_row'}) { + $rows_output--; + } + my $first_data_row = $rows_output++; + # # Create sheet $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); # @@ -901,25 +999,15 @@ END # File::Temp is used to determine the temporary directory. $excel_workbook->set_tempdir($Apache::lonnet::tmpdir); # + my $format = &Apache::loncommon::define_excel_formats($excel_workbook); + # # Add a worksheet my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; $sheetname = &Apache::loncommon::clean_excel_name($sheetname); $excel_sheet = $excel_workbook->addworksheet($sheetname); # - # Define some potentially useful formats - my $format; - $format->{'header'} = $excel_workbook->add_format(bold => 1, - bottom => 1, - align => 'center'); - $format->{'bold'} = $excel_workbook->add_format(bold=>1); - $format->{'h1'} = $excel_workbook->add_format(bold=>1, size=>18); - $format->{'h2'} = $excel_workbook->add_format(bold=>1, size=>16); - $format->{'h3'} = $excel_workbook->add_format(bold=>1, size=>14); - $format->{'date'} = $excel_workbook->add_format(num_format=> - 'mmm d yyyy hh:mm AM/PM'); - # # Put the course description in the header - $excel_sheet->write($rows_output,$cols_output++, + $excel_sheet->write($header_row,$cols_output++, $ENV{'course.'.$ENV{'request.course.id'}.'.description'}, $format->{'h1'}); $cols_output += 3; @@ -941,24 +1029,21 @@ END $sectionstring = "Section ".$Sections[0]; } } - $excel_sheet->write($rows_output,$cols_output++,$sectionstring, + $excel_sheet->write($header_row,$cols_output++,$sectionstring, $format->{'h3'}); $cols_output += scalar(@Sections); # # Put the date in there too - $excel_sheet->write($rows_output++,$cols_output++, + $excel_sheet->write($header_row,$cols_output++, 'Compiled on '.localtime(time),$format->{'h3'}); # $cols_output = 0; - $excel_sheet->write($rows_output++,$cols_output++, + $excel_sheet->write($description_row,$cols_output++, $chosen_output->{'shortdesc'}, $format->{'h3'}); - # - # Figure out the rows we need - my $sequence_name_row = $rows_output+1; - my $resource_name_row = $sequence_name_row+1; - my $maximum_data_row = $sequence_name_row+2; - my $first_data_row = $sequence_name_row+3; + ############################################## + # Output headings for the raw data + ############################################## # # Add the student headers $cols_output = 0; @@ -969,13 +1054,14 @@ END # # Add the remaining column headers my $total_formula_string = '=0'; + my $maximum_formula_string = '=0'; foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { $excel_sheet->write($sequence_name_row,, - $cols_output,$seq->{'title'},$format->{'h3'}); + $cols_output,$seq->{'title'},$format->{'bold'}); # Determine starting cell $seq->{'Excel:startcell'}= &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell - ($maximum_data_row,$cols_output); + ($first_data_row,$cols_output); $seq->{'Excel:startcol'}=$cols_output; my $count = 0; if ($chosen_output->{'every_problem'}) { @@ -1009,7 +1095,7 @@ END } else { $seq->{'Excel:endcell'} = &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell - ($maximum_data_row,$cols_output-1); + ($first_data_row,$cols_output-1); $seq->{'Excel:endcol'} = $cols_output-1; } # Create the formula for summing up this sequence @@ -1023,7 +1109,7 @@ END # Determine cell the score is held in $seq->{'Excel:scorecell'} = &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell - ($maximum_data_row,$cols_output); + ($first_data_row,$cols_output); $seq->{'Excel:scorecol'}=$cols_output; if ($chosen_output->{'base'} eq 'parts correct total') { $excel_sheet->write($resource_name_row,$cols_output++, @@ -1044,21 +1130,36 @@ END # $total_formula_string.='+'. &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell - ($maximum_data_row,$cols_output-1); + ($first_data_row,$cols_output-1); if ($chosen_output->{'sequence_max'}) { - $excel_sheet->write($resource_name_row,$cols_output++, + $excel_sheet->write($resource_name_row,$cols_output, 'maximum', $format->{'bold'}); + $seq->{'Excel:maxcell'} = + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$cols_output); + $seq->{'Excel:maxcol'}=$cols_output; + $maximum_formula_string.='+'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$cols_output); + $cols_output++; + } } if ($chosen_output->{'grand_total'}) { $excel_sheet->write($resource_name_row,$cols_output++,'Total', $format->{'bold'}); } + if ($chosen_output->{'grand_maximum'}) { + $excel_sheet->write($resource_name_row,$cols_output++,'Max. Total', + $format->{'bold'}); + } $total_formula = $excel_sheet->store_formula($total_formula_string); - # + $maximum_formula = $excel_sheet->store_formula($maximum_formula_string); + ############################################## # Output a row for MAX, if appropriate - if ($chosen_output->{'scores'}) { + ############################################## + if ($chosen_output->{'maximum_row'}) { $cols_output = 0; foreach my $field (&get_student_fields_to_show()) { if ($field eq 'username' || $field eq 'fullname' || @@ -1072,11 +1173,15 @@ END # # Add the maximums for each sequence or assessment my %total_cell_translation; + my %maximum_cell_translation; foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { $cols_output=$seq->{'Excel:startcol'}; $total_cell_translation{$seq->{'Excel:scorecell'}} = &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell ($maximum_data_row,$seq->{'Excel:scorecol'}); + $maximum_cell_translation{$seq->{'Excel:maxcell'}} = + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($maximum_data_row,$seq->{'Excel:maxcol'}); my $weight; my $max = 0; foreach my $resource (@{$seq->{'contents'}}) { @@ -1125,8 +1230,62 @@ END $total_formula,undef, %total_cell_translation); } - } # End of MAXIMUM row output if ($chosen_output->{'scores'}) { + if ($chosen_output->{'grand_maximum'}) { + $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, + $maximum_formula,undef, + %total_cell_translation); + } + } # End of MAXIMUM row output if ($chosen_output->{'maximum_row'}) { $rows_output = $first_data_row; + ############################################## + # Output summary table, which actually is above the sequence name row. + ############################################## + if ($chosen_output->{'summary_table'}) { + $cols_output = 0; + $excel_sheet->write($summary_header_row,$cols_output++, + 'Summary Table',$format->{'bold'}); + if ($chosen_output->{'maximum_row'}) { + $excel_sheet->write($summary_header_row,$cols_output++, + 'Maximum',$format->{'bold'}); + } + $excel_sheet->write($summary_header_row,$cols_output++, + 'Average',$format->{'bold'}); + $excel_sheet->write($summary_header_row,$cols_output++, + 'Median',$format->{'bold'}); + $excel_sheet->write($summary_header_row,$cols_output++, + 'Std Dev',$format->{'bold'}); + my $row = $summary_header_row+1; + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + $cols_output = 0; + $excel_sheet->write($row,$cols_output++, + $seq->{'title'}, + $format->{'bold'}); + if ($chosen_output->{'maximum_row'}) { + $excel_sheet->write + ($row,$cols_output++, + '='. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($maximum_data_row,$seq->{'Excel:scorecol'}) + ); + } + my $range = + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$seq->{'Excel:scorecol'}). + ':'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row+$num_students-1,$seq->{'Excel:scorecol'}); + $excel_sheet->write($row,$cols_output++, + '=AVERAGE('.$range.')'); + $excel_sheet->write($row,$cols_output++, + '=MEDIAN('.$range.')'); + $excel_sheet->write($row,$cols_output++, + '=STDEV('.$range.')'); + $row++; + } + } + ############################################## + # Take care of non-excel initialization + ############################################## # # Let the user know what we are doing my $studentcount = scalar(@Apache::lonstatistics::Students); @@ -1177,6 +1336,7 @@ sub excel_outputstudent { # # Write out sequence scores and totals data my %total_cell_translation; + my %maximum_cell_translation; foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { $cols_output = $seq->{'Excel:startcol'}; # Keep track of cells to translate in total cell @@ -1204,7 +1364,12 @@ sub excel_outputstudent { } } else { foreach my $value (@$rawdata) { - $excel_sheet->write($rows_output,$cols_output++,$value); + if ($score eq ' ' || !defined($value)) { + $cols_output++; + } else { + $excel_sheet->write($rows_output,$cols_output++, + $value); + } } } } @@ -1229,7 +1394,11 @@ sub excel_outputstudent { %replaceCells); } } elsif ($chosen_output->{'sequence_sum'}) { - $excel_sheet->write($rows_output,$cols_output++,$score); + if ($score eq ' ') { + $cols_output++; + } else { + $excel_sheet->write($rows_output,$cols_output++,$score); + } } if ($chosen_output->{'sequence_max'}) { $excel_sheet->write($rows_output,$cols_output++,$seq_max); @@ -1241,6 +1410,11 @@ sub excel_outputstudent { $total_formula,undef, %total_cell_translation); } + if ($chosen_output->{'grand_maximum'}) { + $excel_sheet->repeat_formula($rows_output,$cols_output++, + $maximum_formula,undef, + %total_cell_translation); + } # # Bookkeeping $rows_output++; @@ -1388,6 +1562,10 @@ END $sequence_row.= '"",'; $resource_row.= '"Total",'; } + if ($chosen_output->{'grand_maximum'}) { + $sequence_row.= '"",'; + $resource_row.= '"Maximum",'; + } chomp($sequence_row); chomp($resource_row); print $outputfile $sequence_row."\n"; @@ -1419,6 +1597,7 @@ sub csv_outputstudent { # # Output performance data my $total = 0; + my $maximum = 0; foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { my ($performance,$performance_length,$score,$seq_max,$rawdata); if ($chosen_output->{'tries'}){ @@ -1452,10 +1631,14 @@ sub csv_outputstudent { $Str .= '"'.$seq_max.'",'; } $total+=$score; + $maximum += $seq_max; } if ($chosen_output->{'grand_total'}) { $Str .= '"'.$total.'",'; } + if ($chosen_output->{'grand_maximum'}) { + $Str .= '"'.$maximum.'",'; + } chop($Str); $Str .= "\n"; print $outputfile $Str; @@ -1479,7 +1662,7 @@ sub csv_finish { # # Tell the user where to get their csv file $r->print('
'. - 'Your csv file.'."\n"); + ''.&mt('Your csv file.').''."\n"); $r->rflush(); return; @@ -1520,6 +1703,7 @@ sub StudentTriesOnSequence { my $performance_length = 0; my @TriesData = (); my $tries; + my $hasdata = 0; # flag - true if the student has any data on the sequence foreach my $resource (@{$seq->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); my $resource_data = $studentdata->{$resource->{'symb'}}; @@ -1544,6 +1728,7 @@ sub StudentTriesOnSequence { my $tries = 0; if(exists($resource_data->{'resource.'.$partnum.'.tries'})) { $tries = $resource_data->{'resource.'.$partnum.'.tries'}; + $hasdata =1; } # if ($awarded > 0) { @@ -1606,6 +1791,9 @@ sub StudentTriesOnSequence { if ($seq->{'randompick'}) { $max = $seq->{'randompick'}; } + if (! $hasdata && $sum == 0) { + $sum = ' '; + } return ($Str,$performance_length,$sum,$max,\@TriesData); } @@ -1643,6 +1831,7 @@ sub StudentPerformanceOnSequence { my $symbol; my @ScoreData = (); my $partscore; + my $hasdata = 0; # flag, 0 if there were no submissions on the sequence foreach my $resource (@{$seq->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); my $resource_data = $studentdata->{$resource->{'symb'}}; @@ -1661,13 +1850,17 @@ sub StudentPerformanceOnSequence { $performance_length++; # one character per part $symbol = ' '; # default to space # - my $awarded = 0; + my $awarded; if (exists($resource_data->{'resource.'.$part.'.awarded'})) { $awarded = $resource_data->{'resource.'.$part.'.awarded'}; $awarded = 0 if (! $awarded); + $hasdata = 1; } # $partscore = $weight*$awarded; + if (! defined($awarded)) { + $partscore = undef; + } $score += $partscore; $symbol = $partscore; if (abs($symbol - sprintf("%.0f",$symbol)) < 0.001) { @@ -1682,10 +1875,12 @@ sub StudentPerformanceOnSequence { $symbol = 'x'; $max -= $weight; # Do not count 'excused' problems. } + $hasdata = 1; } else { # Unsolved. Did they try? if (exists($resource_data->{'resource.'.$part.'.tries'})){ $symbol = '.'; + $hasdata = 1; } else { $symbol = ' '; } @@ -1706,6 +1901,9 @@ sub StudentPerformanceOnSequence { $Str .= $symbol; } } + if (! $hasdata && $score == 0) { + $score = ' '; + } return ($Str,$performance_length,$score,$max,\@ScoreData); }