--- loncom/interface/statistics/lonstudentassessment.pm 2004/02/12 21:15:47 1.90 +++ loncom/interface/statistics/lonstudentassessment.pm 2004/09/23 17:48:00 1.105 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentassessment.pm,v 1.90 2004/02/12 21:15:47 matthew Exp $ +# $Id: lonstudentassessment.pm,v 1.105 2004/09/23 17:48:00 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,7 @@ 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 '. @@ -490,6 +494,7 @@ 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', @@ -506,6 +511,7 @@ 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', @@ -521,6 +527,7 @@ 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.', @@ -600,6 +607,7 @@ Return a line of the chart for a student my $nodata_count; # The number of students for which there is no data my %prog_state; # progress state used by loncommon PrgWin routines + my $total_sum_width; sub html_initialize { my ($r) = @_; @@ -626,13 +634,19 @@ sub html_initialize { } # # Compute the column widths and output the sequence titles + my $total_count; foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()){ # # 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; + } + $total_count += $sequence->{'num_assess_parts'}; # Use 3 digits for the sum - $sequence->{'width_sum'} = 3; + $sequence->{'width_sum'} += 3; } if ($chosen_output->{'sequence_max'}) { if ($sequence->{'width_sum'}>0) { @@ -642,6 +656,7 @@ sub html_initialize { # 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'}; @@ -650,10 +665,8 @@ sub html_initialize { } $sequence->{'width_total'} = $sequence->{'width_problem'} + $sequence->{'width_sum'}; - if ($sequence->{'width_total'} < length($sequence->{'title'})) { - $sequence->{'width_total'} = length($sequence->{'title'}); - $sequence->{'width_problem'} = - $sequence->{'width_total'} - $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 @@ -662,7 +675,8 @@ sub html_initialize { length($sequence->{'title'}) ).$padding; } - $Str .= "total\n"; + $total_sum_width = length($total_count)+1; + $Str .= " total\n"; $Str .= "
";
     $r->print($Str);
     $r->rflush();
@@ -680,6 +694,10 @@ sub html_outputstudent {
     my @to_show = &get_student_fields_to_show();
     foreach my $field (@to_show) {
         my $title=$student->{$field};
+        # Deal with 'comments' - how I love special cases
+        if ($field eq 'comments') {
+            $title = ''.&mt('Comments').'';
+        }
         my $base = length($title);
         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
         $Str .= $title.' 'x($width-$base).$padding;
@@ -715,20 +733,26 @@ sub html_outputstudent {
                                               $seq,$show_links);
         }
         my $ratio='';
-        if ($chosen_output->{'sequence_sum'}) {
-            $ratio .= sprintf("%3d",$score);
+        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("%3d",$seq_max);
+            $ratio .= sprintf("%3.0f",$seq_max);
         }
         #
         if (! $chosen_output->{'every_problem'}) {
             $performance = '';
+	    $performance_length=0;
         }
-        $performance .= ' 'x($seq->{'width_problem'}-$performance_length).
+        $performance .= ' 'x($seq->{'width_total'}-$performance_length-$seq->{'width_sum'}).
             $ratio;
         #
         $Str .= $performance.$padding;
@@ -738,16 +762,24 @@ 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'};
     }
-    $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
+    if (! defined($score)) {
+        $score = ' ' x $total_sum_width;
+    } else {
+        $score = sprintf("%.0f",$score);
+    }
+    $Str .= ' '.' 'x($total_sum_width-length($score)).$score.' / '.$max;
     $Str .= " \n";
     #
     $r->print($Str);
@@ -773,48 +805,49 @@ 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 $Str = '

'.&mt('Summary Tables').'

'.$/; $Str .= '
'."\n"; - $Str .= "\n"; + $Str .= ''. + ''. + ''. + ''. + ''.$/; 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 > 0) { + $ave = int(100* + ($Statistics->{$seq->{'symb'}}->{'score'}/$num_students) + )/100; } else { $ave = 0; } - $total_ave += $ave; my $max = $Statistics->{$seq->{'symb'}}->{'max'}; - $total_max += $max; $ave = sprintf("%.2f",$ave); $Str .= ''. ''. ''."\n"; } - $total_ave = sprintf('%.2f',$total_ave); # only two digit - $Str .= "
TitleAverageMaximum
'.&mt('Title').''.&mt('Average').''.&mt('Maximum').'
'.$seq->{'title'}.''.$ave.' '.$max.' '.'
\n"; - $Str .= ''."\n"; - $Str .= ''. - "\n"; - $Str .= ''. - ''. - ''. - ''; $Str .= "
Number of StudentsAverageMaximum
'.($num_students-$nodata_count).''.$total_ave.' '.''.$total_max.' '.'
\n"; return $Str; } sub SingleStudentTotal { my $student = &Apache::lonstatistics::current_student(); - my $Str = "

Summary table for ".$student->{'fullname'}." ". - $student->{'username'}.'@'.$student->{'domain'}."

\n"; + my $Str = '

'.&mt('Summary table for [_1] ([_2]@[_3])', + $student->{'fullname'}, + $student->{'username'},$student->{'domain'}).'

'; + $Str .= $/; $Str .= ''."\n"; $Str .= - "\n"; + ''. + ''; + if ($chosen_output->{'base'} eq 'tries') { + $Str .= ''; + } else { + $Str .= ''; + } + $Str .= ''."\n"; my $total = 0; my $total_max = 0; foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { @@ -826,7 +859,7 @@ sub SingleStudentTotal { $total += $value; $total_max +=$max; } - $Str .= ''. + $Str .= ''. ''. '\n"; $Str .= "
Sequence or FolderScoreMaximum
'.&mt('Sequence or Folder').''.&mt('Parts Correct').''.&mt('Score').''.&mt('Maximum').'
Total
'.&mt('Total').''.$total.''.$total_max."
\n"; @@ -865,6 +898,7 @@ my %prog_state; # progress window state my $request_aborted; my $total_formula; +my $maximum_formula; sub excel_initialize { my ($r) = @_; @@ -877,6 +911,7 @@ 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); @@ -888,39 +923,18 @@ sub excel_initialize { # Add 2 because we need a 'sequence_sum' and 'total' column for each $total_columns += 2; } + my $too_many_cols_error_message = + '

'.&mt('Unable to Complete Request').'

'.$/. + '

'.&mt('LON-CAPA is unable to produce your Excel spreadsheet because your selections will result in more than 255 columns. Excel allows only 255 columns in a spreadsheet.').'

'.$/. + '

'.&mt('You may consider reducing the number of Sequences or Folders you have selected.').'

'.$/. + '

'.&mt('LON-CAPA can produce CSV files of this data or Excel files of the Scores Summary data.').'

'.$/; if ($chosen_output->{'base'} eq 'tries' && $total_columns > 255) { - $r->print(<Unable to Complete Request -

-LON-CAPA is unable to produce your Excel spreadsheet because your selections -will result in more than 255 columns. Excel allows only 255 columns in a -spreadsheet. -

-You may consider reducing the number of Sequences or Folders you -have selected. -

-LON-CAPA can produce CSV files of this data or Excel files of the -summary data (Parts Correct or Parts Correct & Totals). -

-END - $request_aborted = 1; + $r->print($too_many_cols_error_message); + $request_aborted = 1; } if ($chosen_output->{'base'} eq 'scores' && $total_columns > 255) { - $r->print(<Unable to Complete Request -

-LON-CAPA is unable to produce your Excel spreadsheet because your selections -will result in more than 255 columns. Excel allows only 255 columns in a -spreadsheet. -

-You may consider reducing the number of Sequences or Folders you -have selected. -

-LON-CAPA can produce CSV files of this data or Excel files of the -Scores Summary data. -

-END - $request_aborted = 1; + $r->print($too_many_cols_error_message); + $request_aborted = 1; } return if ($request_aborted); # @@ -970,23 +984,13 @@ 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($header_row,$cols_output++, $ENV{'course.'.$ENV{'request.course.id'}.'.description'}, @@ -1035,6 +1039,7 @@ 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->{'bold'}); @@ -1112,16 +1117,30 @@ END &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell ($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 ############################################## @@ -1139,11 +1158,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'}}) { @@ -1192,6 +1215,11 @@ END $total_formula,undef, %total_cell_translation); } + 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; ############################################## @@ -1278,7 +1306,12 @@ sub excel_outputstudent { # Write out student data my @to_show = &get_student_fields_to_show(); foreach my $field (@to_show) { - $excel_sheet->write($rows_output,$cols_output++,$student->{$field}); + my $value = $student->{$field}; + if ($field eq 'comments') { + $value = &Apache::lonmsg::retrieve_instructor_comments + ($student->{'username'},$student->{'domain'}); + } + $excel_sheet->write($rows_output,$cols_output++,$value); } # # Get student assessment data @@ -1293,6 +1326,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 @@ -1320,7 +1354,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); + } } } } @@ -1345,7 +1384,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); @@ -1357,6 +1400,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++; @@ -1504,6 +1552,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"; @@ -1520,7 +1572,12 @@ sub csv_outputstudent { # Output student fields my @to_show = &get_student_fields_to_show(); foreach my $field (@to_show) { - $Str .= '"'.&Apache::loncommon::csv_translate($student->{$field}).'",'; + my $value = $student->{$field}; + if ($field eq 'comments') { + $value = &Apache::lonmsg::retrieve_instructor_comments + ($student->{'username'},$student->{'domain'}); + } + $Str .= '"'.&Apache::loncommon::csv_translate($value).'",'; } # # Get student assessment data @@ -1535,6 +1592,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'}){ @@ -1568,10 +1626,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; @@ -1595,7 +1657,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; @@ -1636,6 +1698,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'}}; @@ -1660,6 +1723,7 @@ sub StudentTriesOnSequence { my $tries = 0; if(exists($resource_data->{'resource.'.$partnum.'.tries'})) { $tries = $resource_data->{'resource.'.$partnum.'.tries'}; + $hasdata =1; } # if ($awarded > 0) { @@ -1699,7 +1763,7 @@ sub StudentTriesOnSequence { } } # - if (! defined($tries)) { + if (! defined($tries) || $symbol eq '.') { $tries = $symbol; } push (@TriesData,$tries); @@ -1722,6 +1786,9 @@ sub StudentTriesOnSequence { if ($seq->{'randompick'}) { $max = $seq->{'randompick'}; } + if (! $hasdata && $sum == 0) { + $sum = ' '; + } return ($Str,$performance_length,$sum,$max,\@TriesData); } @@ -1759,6 +1826,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'}}; @@ -1777,13 +1845,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) { @@ -1798,10 +1870,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 = ' '; } @@ -1822,6 +1896,9 @@ sub StudentPerformanceOnSequence { $Str .= $symbol; } } + if (! $hasdata && $score == 0) { + $score = ' '; + } return ($Str,$performance_length,$score,$max,\@ScoreData); }