![]() ![]() | ![]() |
- update to make use of the moved functions
1: # The LearningOnline Network with CAPA 2: # 3: # $Id: lonstudentassessment.pm,v 1.136 2006/04/08 06:59:44 albertel 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::loncommon(); 56: use Apache::loncoursedata; 57: use Apache::lonnet; # for logging porpoises 58: use Apache::lonlocal; 59: use Apache::grades(); 60: use Apache::lonmsgdisplay(); 61: use Time::HiRes; 62: use Spreadsheet::WriteExcel; 63: use Spreadsheet::WriteExcel::Utility(); 64: 65: ####################################################### 66: ####################################################### 67: =pod 68: 69: =item Package Variables 70: 71: =over 4 72: 73: =item $Statistics Hash ref to store student data. Indexed by symb, 74: contains hashes with keys 'score' and 'max'. 75: 76: =cut 77: 78: ####################################################### 79: ####################################################### 80: 81: my $Statistics; 82: 83: ####################################################### 84: ####################################################### 85: 86: =pod 87: 88: =item $show_links 'yes' or 'no' for linking to student performance data 89: 90: =item $output_mode 'html', 'excel', or 'csv' for output mode 91: 92: =item $show 'all', 'totals', or 'scores' determines how much data is output 93: 94: =item $single_student_mode evaluates to true if we are showing only one 95: student. 96: 97: =cut 98: 99: ####################################################### 100: ####################################################### 101: my $show_links; 102: my $output_mode; 103: my $chosen_output; 104: my $single_student_mode; 105: 106: ####################################################### 107: ####################################################### 108: # End of package variable declarations 109: 110: =pod 111: 112: =back 113: 114: =cut 115: 116: ####################################################### 117: ####################################################### 118: 119: =pod 120: 121: =item &BuildStudentAssessmentPage() 122: 123: Inputs: 124: 125: =over 4 126: 127: =item $r Apache Request 128: 129: =item $c Apache Connection 130: 131: =back 132: 133: =cut 134: 135: ####################################################### 136: ####################################################### 137: sub BuildStudentAssessmentPage { 138: my ($r,$c)=@_; 139: # 140: undef($Statistics); 141: undef($show_links); 142: undef($output_mode); 143: undef($chosen_output); 144: undef($single_student_mode); 145: # 146: my %Saveable_Parameters = ('Status' => 'scalar', 147: 'chartoutputmode' => 'scalar', 148: 'chartoutputdata' => 'scalar', 149: 'Section' => 'array', 150: 'StudentData' => 'array', 151: 'Maps' => 'array'); 152: &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters); 153: &Apache::loncommon::restore_course_settings('chart',\%Saveable_Parameters); 154: # 155: &Apache::lonstatistics::PrepareClasslist(); 156: # 157: $single_student_mode = 0; 158: $single_student_mode = 1 if ($env{'form.SelectedStudent'}); 159: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, 160: ['selectstudent']); 161: if ($env{'form.selectstudent'}) { 162: &Apache::lonstatistics::DisplayClasslist($r); 163: return; 164: } 165: # 166: # Print out the HTML headers for the interface 167: # This also parses the output mode selector 168: # This step must *always* be done. 169: $r->print(&CreateInterface()); 170: $r->print('<input type="hidden" name="notfirstrun" value="true" />'); 171: $r->print('<input type="hidden" name="sort" value="'. 172: $env{'form.sort'}.'" />'); 173: $r->rflush(); 174: # 175: if (! exists($env{'form.notfirstrun'}) && ! $single_student_mode) { 176: return; 177: } 178: $r->print('<h4>'. 179: &Apache::lonstatistics::section_and_enrollment_description(). 180: '</h4>'); 181: # 182: my $initialize = \&html_initialize; 183: my $output_student = \&html_outputstudent; 184: my $finish = \&html_finish; 185: # 186: if ($output_mode eq 'excel') { 187: $initialize = \&excel_initialize; 188: $output_student = \&excel_outputstudent; 189: $finish = \&excel_finish; 190: } elsif ($output_mode eq 'csv') { 191: $initialize = \&csv_initialize; 192: $output_student = \&csv_outputstudent; 193: $finish = \&csv_finish; 194: } 195: # 196: if($c->aborted()) { return ; } 197: # 198: # Determine which students we want to look at 199: my @Students; 200: if ($single_student_mode) { 201: @Students = (&Apache::lonstatistics::current_student()); 202: $r->print(&next_and_previous_buttons()); 203: $r->rflush(); 204: } else { 205: @Students = @Apache::lonstatistics::Students; 206: } 207: # 208: # Perform generic initialization tasks 209: # Since we use lonnet::EXT to retrieve problem weights, 210: # to ensure current data we must clear the caches out. 211: # This makes sure that parameter changes at the student level 212: # are immediately reflected in the chart. 213: &Apache::lonnet::clear_EXT_cache_status(); 214: # 215: # Clean out loncoursedata's package data, just to be safe. 216: &Apache::loncoursedata::clear_internal_caches(); 217: # 218: # Call the initialize routine selected above 219: $initialize->($r); 220: foreach my $student (@Students) { 221: if($c->aborted()) { 222: $finish->($r); 223: return ; 224: } 225: # Call the output_student routine selected above 226: $output_student->($r,$student); 227: } 228: # Call the "finish" routine selected above 229: $finish->($r); 230: # 231: return; 232: } 233: 234: ####################################################### 235: ####################################################### 236: sub next_and_previous_buttons { 237: my $Str = ''; 238: $Str .= '<input type="hidden" name="SelectedStudent" value="'. 239: $env{'form.SelectedStudent'}.'" />'; 240: # 241: # Build the previous student link 242: my $previous = &Apache::lonstatistics::previous_student(); 243: my $previousbutton = ''; 244: if (defined($previous)) { 245: my $sname = $previous->{'username'}.':'.$previous->{'domain'}; 246: $previousbutton .= '<input type="button" value="'. 247: 'Previous Student ('. 248: $previous->{'username'}.'@'.$previous->{'domain'}.')'. 249: '" onclick="document.Statistics.SelectedStudent.value='. 250: "'".$sname."'".';'. 251: 'document.Statistics.submit();" />'; 252: } else { 253: $previousbutton .= '<input type="button" value="'. 254: 'Previous student (none)'.'" />'; 255: } 256: # 257: # Build the next student link 258: my $next = &Apache::lonstatistics::next_student(); 259: my $nextbutton = ''; 260: if (defined($next)) { 261: my $sname = $next->{'username'}.':'.$next->{'domain'}; 262: $nextbutton .= '<input type="button" value="'. 263: 'Next Student ('. 264: $next->{'username'}.'@'.$next->{'domain'}.')'. 265: '" onclick="document.Statistics.SelectedStudent.value='. 266: "'".$sname."'".';'. 267: 'document.Statistics.submit();" />'; 268: } else { 269: $nextbutton .= '<input type="button" value="'. 270: 'Next student (none)'.'" />'; 271: } 272: # 273: # Build the 'all students' button 274: my $all = ''; 275: $all .= '<input type="button" value="All Students" '. 276: '" onclick="document.Statistics.SelectedStudent.value='. 277: "''".';'.'document.Statistics.submit();" />'; 278: $Str .= $previousbutton.(' 'x5).$all.(' 'x5).$nextbutton; 279: return $Str; 280: } 281: 282: ####################################################### 283: ####################################################### 284: 285: sub get_student_fields_to_show { 286: my @to_show = @Apache::lonstatistics::SelectedStudentData; 287: foreach (@to_show) { 288: if ($_ eq 'all') { 289: @to_show = @Apache::lonstatistics::StudentDataOrder; 290: last; 291: } 292: } 293: return @to_show; 294: } 295: 296: ####################################################### 297: ####################################################### 298: 299: =pod 300: 301: =item &CreateInterface() 302: 303: Called by &BuildStudentAssessmentPage to create the top part of the 304: page which displays the chart. 305: 306: Inputs: None 307: 308: Returns: A string containing the HTML for the headers and top table for 309: the chart page. 310: 311: =cut 312: 313: ####################################################### 314: ####################################################### 315: sub CreateInterface { 316: my $Str = ''; 317: $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Chart'); 318: # $Str .= &CreateLegend(); 319: $Str .= '<table cellspacing="5">'."\n"; 320: $Str .= '<tr>'; 321: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>'; 322: $Str .= '<td align="center"><b>'.&mt('Student Data</b>').'</td>'; 323: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>'; 324: $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>'; 325: $Str .= '<td align="center"><b>'.&mt('Output Format').'</b>'. 326: &Apache::loncommon::help_open_topic("Chart_Output_Formats"). 327: '</td>'; 328: $Str .= '<td align="center"><b>'.&mt('Output Data').'</b>'. 329: &Apache::loncommon::help_open_topic("Chart_Output_Data"). 330: '</td>'; 331: $Str .= '</tr>'."\n"; 332: # 333: $Str .= '<tr><td align="center">'."\n"; 334: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); 335: $Str .= '</td><td align="center">'; 336: $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple', 337: 5,undef); 338: $Str .= '</td><td>'."\n"; 339: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); 340: $Str .= '</td><td>'."\n"; 341: $Str .= &Apache::lonstatistics::map_select('Maps','multiple,all',5); 342: $Str .= '</td><td>'."\n"; 343: $Str .= &CreateAndParseOutputSelector(); 344: $Str .= '</td><td>'."\n"; 345: $Str .= &CreateAndParseOutputDataSelector(); 346: $Str .= '</td></tr>'."\n"; 347: $Str .= '</table>'."\n"; 348: $Str .= '<input type="submit" name="Generate Chart" value="'. 349: &mt('Generate Chart').'" />'; 350: $Str .= ' 'x5; 351: $Str .= '<input type="submit" name="selectstudent" value="'. 352: &mt('Select One Student').'" />'; 353: $Str .= ' 'x5; 354: $Str .= '<input type="submit" name="ClearCache" value="'. 355: &mt('Clear Caches').'" />'; 356: $Str .= ' 'x5; 357: $Str .= 358: &mt('Status [_1]', 359: '<input type="text" name="stats_status" size="60" value="" />'); 360: $Str .= '<br />'; 361: return $Str; 362: } 363: 364: ####################################################### 365: ####################################################### 366: 367: =pod 368: 369: =item &CreateAndParseOutputSelector() 370: 371: =cut 372: 373: ####################################################### 374: ####################################################### 375: my @OutputOptions = 376: ({ name => 'HTML, with links', 377: value => 'html, with links', 378: description => 'Output HTML with each symbol linked to the problem '. 379: 'which generated it.', 380: mode => 'html', 381: show_links => 'yes', 382: }, 383: { name => 'HTML, with all links', 384: value => 'html, with all links', 385: description => 'Output HTML with each symbol linked to the problem '. 386: 'which generated it. '. 387: 'This includes links for unattempted problems.', 388: mode => 'html', 389: show_links => 'all', 390: }, 391: { name => 'HTML, without links', 392: value => 'html, without links', 393: description => 'Output HTML. By not including links, the size of the'. 394: ' web page is greatly reduced. If your browser crashes on the '. 395: 'full display, try this.', 396: mode => 'html', 397: show_links => 'no', 398: }, 399: { name => 'Excel', 400: value => 'excel', 401: description => 'Output an Excel file (compatable with Excel 95).', 402: mode => 'excel', 403: show_links => 'no', 404: }, 405: { name => 'CSV', 406: value => 'csv', 407: description => 'Output a comma separated values file suitable for '. 408: 'import into a spreadsheet program. Using this method as opposed '. 409: 'to Excel output allows you to organize your data before importing'. 410: ' it into a spreadsheet program.', 411: mode => 'csv', 412: show_links => 'no', 413: }, 414: ); 415: 416: sub OutputDescriptions { 417: my $Str = ''; 418: $Str .= "<h2>Output Formats</h2>\n"; 419: $Str .= "<dl>\n"; 420: foreach my $outputmode (@OutputOptions) { 421: $Str .=" <dt>".$outputmode->{'name'}."</dt>\n"; 422: $Str .=" <dd>".$outputmode->{'description'}."</dd>\n"; 423: } 424: $Str .= "</dl>\n"; 425: return $Str; 426: } 427: 428: sub CreateAndParseOutputSelector { 429: my $Str = ''; 430: my $elementname = 'chartoutputmode'; 431: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, 432: [$elementname]); 433: # 434: # Format for output options is 'mode, restrictions'; 435: my $selected = (&Apache::loncommon::get_env_multiple('form.'.$elementname))[0]; 436: $selected = 'html, without links' if (!$selected); 437: 438: # 439: # Set package variables describing output mode 440: $show_links = 'no'; 441: $output_mode = 'html'; 442: foreach my $option (@OutputOptions) { 443: next if ($option->{'value'} ne $selected); 444: $output_mode = $option->{'mode'}; 445: $show_links = $option->{'show_links'}; 446: } 447: 448: # 449: # Build the form element 450: $Str = qq/<select size="5" name="$elementname">/; 451: foreach my $option (@OutputOptions) { 452: $Str .= "\n".' <option value="'.$option->{'value'}.'"'; 453: $Str .= " selected " if ($option->{'value'} eq $selected); 454: $Str .= ">".&mt($option->{'name'})."<\/option>"; 455: } 456: $Str .= "\n</select>"; 457: return $Str; 458: } 459: 460: ## 461: ## Data selector stuff 462: ## 463: my @OutputDataOptions = 464: ( 465: { name => 'Scores Summary', 466: base => 'scores', 467: value => 'sum and total', 468: scores => 1, 469: tries => 0, 470: every_problem => 0, 471: sequence_sum => 1, 472: sequence_max => 1, 473: grand_total => 1, 474: grand_maximum => 1, 475: summary_table => 1, 476: maximum_row => 1, 477: ignore_weight => 0, 478: shortdesc => 'Total Score and Maximum Possible for each '. 479: 'Sequence or Folder', 480: longdesc => 'The score of each student as well as the '. 481: ' maximum possible on each Sequence or Folder.', 482: }, 483: { name => 'Scores Per Problem', 484: base => 'scores', 485: value => 'scores', 486: scores => 1, 487: tries => 0, 488: correct => 0, 489: every_problem => 1, 490: sequence_sum => 1, 491: sequence_max => 1, 492: grand_total => 1, 493: grand_maximum => 1, 494: summary_table => 1, 495: maximum_row => 1, 496: ignore_weight => 0, 497: shortdesc => 'Score on each Problem Part', 498: longdesc =>'The students score on each problem part, computed as'. 499: 'the part weight * part awarded', 500: }, 501: { name =>'Tries', 502: base =>'tries', 503: value => 'tries', 504: scores => 0, 505: tries => 1, 506: correct => 0, 507: every_problem => 1, 508: sequence_sum => 0, 509: sequence_max => 0, 510: grand_total => 0, 511: grand_maximum => 0, 512: summary_table => 0, 513: maximum_row => 0, 514: ignore_weight => 0, 515: shortdesc => 'Number of Tries before success on each Problem Part', 516: longdesc =>'The number of tries before success on each problem part.', 517: non_html_notes => 'negative values indicate an incorrect problem', 518: }, 519: { name =>'Parts Correct', 520: base =>'tries', 521: value => 'parts correct total', 522: scores => 0, 523: tries => 0, 524: correct => 1, 525: every_problem => 1, 526: sequence_sum => 1, 527: sequence_max => 1, 528: grand_total => 1, 529: grand_maximum => 1, 530: summary_table => 1, 531: maximum_row => 0, 532: ignore_weight => 1, 533: shortdesc => 'Number of Problem Parts completed successfully.', 534: longdesc => 'The Number of Problem Parts completed successfully and '. 535: 'the maximum possible for each student', 536: }, 537: ); 538: 539: sub HTMLifyOutputDataDescriptions { 540: my $Str = ''; 541: $Str .= "<h2>Output Data</h2>\n"; 542: $Str .= "<dl>\n"; 543: foreach my $option (@OutputDataOptions) { 544: $Str .= ' <dt>'.$option->{'name'}.'</dt>'; 545: $Str .= '<dd>'.$option->{'longdesc'}.'</dd>'."\n"; 546: } 547: $Str .= "</dl>\n"; 548: return $Str; 549: } 550: 551: sub CreateAndParseOutputDataSelector { 552: my $Str = ''; 553: my $elementname = 'chartoutputdata'; 554: # 555: my $selected = (&Apache::loncommon::get_env_multiple('form.'.$elementname))[0]; 556: $selected = 'scores' if (!$selected); 557: 558: # 559: $chosen_output = $OutputDataOptions[0]; 560: foreach my $option (@OutputDataOptions) { 561: if ($option->{'value'} eq $selected) { 562: $chosen_output = $option; 563: } 564: } 565: # 566: # Build the form element 567: $Str = qq/<select size="5" name="$elementname">/; 568: foreach my $option (@OutputDataOptions) { 569: $Str .= "\n".' <option value="'.$option->{'value'}.'"'; 570: $Str .= " selected " if ($option->{'value'} eq $chosen_output->{'value'}); 571: $Str .= ">".&mt($option->{'name'})."<\/option>"; 572: } 573: $Str .= "\n</select>"; 574: return $Str; 575: 576: } 577: 578: ####################################################### 579: ####################################################### 580: sub count_parts { 581: my ($navmap,$sequence) = @_; 582: my @resources = &get_resources($navmap,$sequence); 583: my $count = 0; 584: foreach my $res (@resources) { 585: $count += scalar(@{$res->parts}); 586: } 587: return $count; 588: } 589: 590: sub get_resources { 591: my ($navmap,$sequence) = @_; 592: my @resources = $navmap->retrieveResources($sequence, 593: sub { shift->is_problem(); }, 594: 0,0,0); 595: return @resources; 596: } 597: 598: ####################################################### 599: ####################################################### 600: 601: =pod 602: 603: =head2 HTML output routines 604: 605: =item &html_initialize($r) 606: 607: Create labels for the columns of student data to show. 608: 609: =item &html_outputstudent($r,$student) 610: 611: Return a line of the chart for a student. 612: 613: =item &html_finish($r) 614: 615: =cut 616: 617: ####################################################### 618: ####################################################### 619: { 620: my $padding; 621: my $count; 622: 623: my $nodata_count; # The number of students for which there is no data 624: my %prog_state; # progress state used by loncommon PrgWin routines 625: my $total_sum_width; 626: 627: my %width; # Holds sequence width information 628: my @sequences; 629: my $navmap; # Have to keep this around since weakref is a bit zealous 630: 631: sub html_cleanup { 632: undef(%prog_state); 633: undef(%width); 634: # 635: undef($navmap); 636: undef(@sequences); 637: } 638: 639: sub html_initialize { 640: my ($r) = @_; 641: # 642: $padding = ' 'x3; 643: $count = 0; 644: $nodata_count = 0; 645: &html_cleanup(); 646: ($navmap,@sequences) = 647: &Apache::lonstatistics::selected_sequences_with_assessments(); 648: if (! ref($navmap)) { 649: # Unable to get data, so bail out 650: $r->print("<h3>". 651: &mt('Unable to retrieve course information.'). 652: '</h3>'); 653: } 654: 655: # If we're showing links, show a checkbox to open in new 656: # windows. 657: if ($show_links ne 'no') { 658: $r->print(<<NEW_WINDOW_CHECKBOX); 659: <script type="text/javascript">new_window = true;</script> 660: <p><label>Show links in new window: 661: <input type="checkbox" checked="1" onclick="new_window=this.checked" /> 662: </label></p> 663: NEW_WINDOW_CHECKBOX 664: } 665: 666: # 667: $r->print("<h3>".$env{'course.'.$env{'request.course.id'}.'.description'}. 668: " ".localtime(time)."</h3>"); 669: # 670: if ($chosen_output->{'base'} !~ /^final table/) { 671: $r->print("<h3>".$chosen_output->{'shortdesc'}."</h3>"); 672: } 673: my $Str = "<pre>\n"; 674: # First, the @StudentData fields need to be listed 675: my @to_show = &get_student_fields_to_show(); 676: foreach my $field (@to_show) { 677: my $title=$Apache::lonstatistics::StudentData{$field}->{'title'}; 678: my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'}; 679: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'}; 680: $Str .= $title.' 'x($width-$base).$padding; 681: } 682: # 683: # Compute the column widths and output the sequence titles 684: my $total_count; 685: # 686: # Compute sequence widths 687: my $starttime = Time::HiRes::time; 688: foreach my $seq (@sequences) { 689: my $symb = $seq->symb; 690: my $title = $seq->compTitle; 691: $width{$symb}->{'width_sum'} = 0; 692: # Compute width of sum 693: if ($chosen_output->{'sequence_sum'}) { 694: if ($chosen_output->{'every_problem'}) { 695: # Use 1 digit for a space 696: $width{$symb}->{'width_sum'} += 1; 697: } 698: $total_count += &count_parts($navmap,$seq); 699: # Use 3 digits for the sum 700: $width{$symb}->{'width_sum'} += 3; 701: } 702: # Compute width of maximum 703: if ($chosen_output->{'sequence_max'}) { 704: if ($width{$symb}->{'width_sum'}>0) { 705: # One digit for the '/' 706: $width{$symb}->{'width_sum'} +=1; 707: } 708: # Use 3 digits for the total 709: $width{$symb}->{'width_sum'}+=3; 710: } 711: # 712: if ($chosen_output->{'every_problem'}) { 713: # one problem per digit 714: $width{$symb}->{'width_parts'}= &count_parts($navmap,$seq); 715: $width{$symb}->{'width_problem'} += $width{$symb}->{'width_parts'}; 716: } else { 717: $width{$symb}->{'width_problem'} = 0; 718: } 719: $width{$symb}->{'width_total'} = $width{$symb}->{'width_problem'} + 720: $width{$symb}->{'width_sum'}; 721: if ($width{$symb}->{'width_total'} < length(&HTML::Entities::decode($title))) { 722: $width{$symb}->{'width_total'} = length(&HTML::Entities::decode($title)); 723: } 724: # 725: # Output the sequence titles 726: $Str .= $title.(' 'x($width{$symb}->{'width_total'}- 727: length($title) 728: )).$padding; 729: } 730: $total_sum_width = length($total_count)+1; 731: $Str .= " total</pre>\n"; 732: $Str .= "<pre>"; 733: $r->print($Str); 734: $r->rflush(); 735: 736: $r->print(<<JS); 737: <script> 738: // get the left offset of a given widget as an absolute position 739: function getLeftOffset (element) { 740: return collect(element, "offsetLeft"); 741: } 742: 743: // get the top offset of a given widget as an absolute position 744: function getTopOffset (element) { 745: return collect(element, "offsetTop"); 746: } 747: 748: function collect(element, att) { 749: var val = 0; 750: while(element) { 751: val += element[att]; 752: element = element.offsetParent; 753: } 754: return val; 755: } 756: 757: var currentDiv; 758: var oldBorder; 759: var currentElement; 760: function popup_score(element, score) { 761: popdown_score(); 762: var left = getLeftOffset(element); 763: var top = getTopOffset(element); 764: var div = document.createElement("div"); 765: div.style.border = "1px solid #8888FF"; 766: div.style.backgroundColor = "#CCCCFF"; 767: div.appendChild(document.createTextNode(score)); 768: div.style.position = "absolute"; 769: div.style.top = (top - 25) + "px"; 770: div.style.left = (left - 10) + "px"; 771: currentDiv = div; 772: document.body.insertBefore(div, document.body.childNodes[0]); 773: oldBorder = element.style.border; 774: element.style.border = "1px solid yellow"; 775: currentElement = element; 776: } 777: 778: function popdown_score() { 779: if (currentDiv) { 780: document.body.removeChild(currentDiv); 781: } 782: if (currentElement) { 783: currentElement.style.border = oldBorder; 784: } 785: currentDiv = undefined; 786: } 787: </script> 788: JS 789: 790: # 791: # Let the user know what we are doing 792: my $studentcount = scalar(@Apache::lonstatistics::Students); 793: if ($env{'form.SelectedStudent'}) { 794: $studentcount = '1'; 795: } 796: # 797: # Initialize progress window 798: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin 799: ($r,'HTML Chart Status', 800: 'HTML Chart Progress', $studentcount, 801: 'inline',undef,'Statistics','stats_status'); 802: # 803: &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 804: 'Processing first student'); 805: return; 806: } 807: 808: sub html_outputstudent { 809: my ($r,$student) = @_; 810: my $Str = ''; 811: return if (! defined($navmap)); 812: # 813: if($count++ % 5 == 0 && $count > 0) { 814: $r->print("</pre><pre>"); 815: } 816: # First, the @StudentData fields need to be listed 817: my @to_show = &get_student_fields_to_show(); 818: foreach my $field (@to_show) { 819: my $title=$student->{$field}; 820: # Deal with 'comments' - how I love special cases 821: if ($field eq 'comments') { 822: $title = '<a href="/adm/'.$student->{'domain'}.'/'.$student->{'username'}.'/'.'aboutme#coursecomment">'.&mt('Comments').'</a>'; 823: } 824: my $base = length($title); 825: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'}; 826: $Str .= $title.' 'x($width-$base).$padding; 827: } 828: # Get ALL the students data 829: my %StudentsData; 830: my @tmp = &Apache::loncoursedata::get_current_state 831: ($student->{'username'},$student->{'domain'},undef, 832: $env{'request.course.id'}); 833: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { 834: %StudentsData = @tmp; 835: } 836: if (scalar(@tmp) < 1) { 837: $nodata_count++; 838: $Str .= '<font color="blue">No Course Data</font>'."\n"; 839: $r->print($Str); 840: $r->rflush(); 841: return; 842: } 843: # 844: # By sequence build up the data 845: my $studentstats; 846: my $PerformanceStr = ''; 847: foreach my $seq (@sequences) { 848: my $symb = $seq->symb; 849: my ($performance,$performance_length,$score,$seq_max,$rawdata); 850: if ($chosen_output->{'tries'}) { 851: ($performance,$performance_length,$score,$seq_max,$rawdata) = 852: &student_tries_on_sequence($student,\%StudentsData, 853: $navmap,$seq,$show_links); 854: } else { 855: ($performance,$performance_length,$score,$seq_max,$rawdata) = 856: &student_performance_on_sequence($student,\%StudentsData, 857: $navmap,$seq,$show_links, 858: $chosen_output->{ignore_weight}); 859: } 860: my $ratio=''; 861: if ($chosen_output->{'every_problem'} && 862: $chosen_output->{'sequence_sum'}) { 863: $ratio .= ' '; 864: } 865: if ($chosen_output->{'sequence_sum'} && $score ne ' ') { 866: my $score .= sprintf("%3.0f",$score); 867: $ratio .= (' 'x(3-length($score))).$score; 868: } elsif($chosen_output->{'sequence_sum'}) { 869: $ratio .= ' 'x3; 870: } 871: if ($chosen_output->{'sequence_max'}) { 872: if ($chosen_output->{'sequence_sum'}) { 873: $ratio .= '/'; 874: } 875: $ratio .= sprintf("%3.0f",$seq_max); 876: } 877: # 878: if (! $chosen_output->{'every_problem'}) { 879: $performance = ''; 880: $performance_length=0; 881: } 882: $performance .= ' 'x($width{$symb}->{'width_total'} - 883: $performance_length - 884: $width{$symb}->{'width_sum'}). 885: $ratio; 886: # 887: $Str .= $performance.$padding; 888: # 889: $studentstats->{$symb}->{'score'}= $score; 890: $studentstats->{$symb}->{'max'} = $seq_max; 891: } 892: # 893: # Total it up and store the statistics info. 894: my ($score,$max); 895: while (my ($symb,$seq_stats) = each (%{$studentstats})) { 896: $Statistics->{$symb}->{'score'} += $seq_stats->{'score'}; 897: if ($Statistics->{$symb}->{'max'} < $seq_stats->{'max'}) { 898: $Statistics->{$symb}->{'max'} = $seq_stats->{'max'}; 899: } 900: if ($seq_stats->{'score'} ne ' ') { 901: $score += $seq_stats->{'score'}; 902: $Statistics->{$symb}->{'num_students'}++; 903: } 904: $max += $seq_stats->{'max'}; 905: } 906: if (! defined($score)) { 907: $score = ' ' x $total_sum_width; 908: } else { 909: $score = sprintf("%.0f",$score); 910: $score = (' 'x(3-length($score))).$score; 911: } 912: $Str .= ' '.' 'x($total_sum_width-length($score)).$score.' / '.$max; 913: $Str .= " \n"; 914: # 915: $r->print($Str); 916: # 917: $r->rflush(); 918: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); 919: return; 920: } 921: 922: sub html_finish { 923: my ($r) = @_; 924: return if (! defined($navmap)); 925: # 926: # Check for suppressed output and close the progress window if so 927: $r->print("</pre>\n"); 928: if ($chosen_output->{'summary_table'}) { 929: if ($single_student_mode) { 930: $r->print(&SingleStudentTotal()); 931: } else { 932: $r->print(&StudentAverageTotal()); 933: } 934: } 935: $r->rflush(); 936: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); 937: &html_cleanup(); 938: return; 939: } 940: 941: sub StudentAverageTotal { 942: my $Str = '<h3>'.&mt('Summary Tables').'</h3>'.$/; 943: $Str .= '<table border=2 cellspacing="1">'."\n"; 944: $Str .= '<tr>'. 945: '<th>'.&mt('Title').'</th>'. 946: '<th>'.&mt('Average').'</th>'. 947: '<th>'.&mt('Maximum').'</th>'. 948: '</tr>'.$/; 949: foreach my $seq (@sequences) { 950: my $symb = $seq->symb; 951: my $ave; 952: my $num_students = $Statistics->{$symb}->{'num_students'}; 953: if ($num_students > 0) { 954: $ave = int(100* 955: ($Statistics->{$symb}->{'score'}/$num_students) 956: )/100; 957: } else { 958: $ave = 0; 959: } 960: my $max = $Statistics->{$symb}->{'max'}; 961: $ave = sprintf("%.2f",$ave); 962: $Str .= '<tr><td>'.$seq->compTitle.'</td>'. 963: '<td align="right">'.$ave.' </td>'. 964: '<td align="right">'.$max.' '.'</td></tr>'."\n"; 965: } 966: $Str .= "</table>\n"; 967: return $Str; 968: } 969: 970: sub SingleStudentTotal { 971: return if (! defined($navmap)); 972: my $student = &Apache::lonstatistics::current_student(); 973: my $Str = '<h3>'.&mt('Summary table for [_1] ([_2]@[_3])', 974: $student->{'fullname'}, 975: $student->{'username'},$student->{'domain'}).'</h3>'; 976: $Str .= $/; 977: $Str .= '<table border=2 cellspacing="1">'."\n"; 978: $Str .= 979: '<tr>'. 980: '<th>'.&mt('Sequence or Folder').'</th>'; 981: if ($chosen_output->{'base'} eq 'tries') { 982: $Str .= '<th>'.&mt('Parts Correct').'</th>'; 983: } else { 984: $Str .= '<th>'.&mt('Score').'</th>'; 985: } 986: $Str .= '<th>'.&mt('Maximum').'</th>'."</tr>\n"; 987: my $total = 0; 988: my $total_max = 0; 989: foreach my $seq (@sequences) { 990: my $value = $Statistics->{$seq->symb}->{'score'}; 991: my $max = $Statistics->{$seq->symb}->{'max'}; 992: $Str .= '<tr><td>'.&HTML::Entities::encode($seq->compTitle).'</td>'. 993: '<td align="right">'.$value.'</td>'. 994: '<td align="right">'.$max.'</td></tr>'."\n"; 995: $total += $value; 996: $total_max +=$max; 997: } 998: $Str .= '<tr><td><b>'.&mt('Total').'</b></td>'. 999: '<td align="right">'.$total.'</td>'. 1000: '<td align="right">'.$total_max."</td></tr>\n"; 1001: $Str .= "</table>\n"; 1002: return $Str; 1003: } 1004: 1005: } 1006: 1007: ####################################################### 1008: ####################################################### 1009: 1010: =pod 1011: 1012: =head2 EXCEL subroutines 1013: 1014: =item &excel_initialize($r) 1015: 1016: =item &excel_outputstudent($r,$student) 1017: 1018: =item &excel_finish($r) 1019: 1020: =cut 1021: 1022: ####################################################### 1023: ####################################################### 1024: { 1025: 1026: my $excel_sheet; 1027: my $excel_workbook; 1028: my $format; 1029: 1030: my $filename; 1031: my $rows_output; 1032: my $cols_output; 1033: 1034: my %prog_state; # progress window state 1035: my $request_aborted; 1036: 1037: my $total_formula; 1038: my $maximum_formula; 1039: my %formula_data; 1040: 1041: my $navmap; 1042: my @sequences; 1043: 1044: sub excel_cleanup { 1045: # 1046: undef ($excel_sheet); 1047: undef ($excel_workbook); 1048: undef ($filename); 1049: undef ($rows_output); 1050: undef ($cols_output); 1051: undef (%prog_state); 1052: undef ($request_aborted); 1053: undef ($total_formula); 1054: undef ($maximum_formula); 1055: # 1056: undef(%formula_data); 1057: # 1058: undef($navmap); 1059: undef(@sequences); 1060: } 1061: 1062: sub excel_initialize { 1063: my ($r) = @_; 1064: 1065: &excel_cleanup(); 1066: ($navmap,@sequences) = 1067: &Apache::lonstatistics::selected_sequences_with_assessments(); 1068: if (! ref($navmap)) { 1069: # Unable to get data, so bail out 1070: $r->print("<h3>". 1071: &mt('Unable to retrieve course information.'). 1072: '</h3>'); 1073: } 1074: # 1075: my $total_columns = scalar(&get_student_fields_to_show()); 1076: my $num_students = scalar(@Apache::lonstatistics::Students); 1077: # 1078: foreach my $seq (@sequences) { 1079: if ($chosen_output->{'every_problem'}) { 1080: $total_columns+=&count_parts($navmap,$seq); 1081: } 1082: # Add 2 because we need a 'sequence_sum' and 'total' column for each 1083: $total_columns += 2; 1084: } 1085: my $too_many_cols_error_message = 1086: '<h2>'.&mt('Unable to Complete Request').'</h2>'.$/. 1087: '<p>'.&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.').'</p>'.$/. 1088: '<p>'.&mt('You may consider reducing the number of <b>Sequences or Folders</b> you have selected.').'</p>'.$/. 1089: '<p>'.&mt('LON-CAPA can produce <b>CSV</b> files of this data or Excel files of the <b>Scores Summary</b> data.').'</p>'.$/; 1090: if ($chosen_output->{'base'} eq 'tries' && $total_columns > 255) { 1091: $r->print($too_many_cols_error_message); 1092: $request_aborted = 1; 1093: } 1094: if ($chosen_output->{'base'} eq 'scores' && $total_columns > 255) { 1095: $r->print($too_many_cols_error_message); 1096: $request_aborted = 1; 1097: } 1098: return if ($request_aborted); 1099: # 1100: # 1101: $excel_workbook = undef; 1102: $excel_sheet = undef; 1103: # 1104: $rows_output = 0; 1105: $cols_output = 0; 1106: # 1107: # Determine rows 1108: my $header_row = $rows_output++; 1109: my $description_row = $rows_output++; 1110: my $notes_row = $rows_output++; 1111: $rows_output++; # blank row 1112: my $summary_header_row; 1113: if ($chosen_output->{'summary_table'}) { 1114: $summary_header_row = $rows_output++; 1115: $rows_output+= scalar(@sequences); 1116: $rows_output++; 1117: } 1118: my $sequence_name_row = $rows_output++; 1119: my $resource_name_row = $rows_output++; 1120: my $maximum_data_row = $rows_output++; 1121: if (! $chosen_output->{'maximum_row'}) { 1122: $rows_output--; 1123: } 1124: my $first_data_row = $rows_output++; 1125: # 1126: # Create sheet 1127: ($excel_workbook,$filename,$format)= 1128: &Apache::loncommon::create_workbook($r); 1129: return if (! defined($excel_workbook)); 1130: # 1131: # Add a worksheet 1132: my $sheetname = $env{'course.'.$env{'request.course.id'}.'.description'}; 1133: $sheetname = &Apache::loncommon::clean_excel_name($sheetname); 1134: $excel_sheet = $excel_workbook->addworksheet($sheetname); 1135: # 1136: # Put the course description in the header 1137: $excel_sheet->write($header_row,$cols_output++, 1138: $env{'course.'.$env{'request.course.id'}.'.description'}, 1139: $format->{'h1'}); 1140: $cols_output += 3; 1141: # 1142: # Put a description of the sections listed 1143: my $sectionstring = ''; 1144: my @Sections = &Apache::lonstatistics::get_selected_sections(); 1145: $excel_sheet->write($header_row,$cols_output++, 1146: &Apache::lonstatistics::section_and_enrollment_description('plaintext'), 1147: $format->{'h3'}); 1148: # 1149: # Put the date in there too 1150: $excel_sheet->write($header_row,$cols_output++, 1151: 'Compiled on '.localtime(time),$format->{'h3'}); 1152: # 1153: $cols_output = 0; 1154: $excel_sheet->write($description_row,$cols_output++, 1155: $chosen_output->{'shortdesc'}, 1156: $format->{'b'}); 1157: # 1158: $cols_output = 0; 1159: $excel_sheet->write($notes_row,$cols_output++, 1160: $chosen_output->{'non_html_notes'}, 1161: $format->{'i'}); 1162: 1163: ############################################## 1164: # Output headings for the raw data 1165: ############################################## 1166: # 1167: # Add the student headers 1168: $cols_output = 0; 1169: foreach my $field (&get_student_fields_to_show()) { 1170: $excel_sheet->write($resource_name_row,$cols_output++,$field, 1171: $format->{'bold'}); 1172: } 1173: # 1174: # Add the remaining column headers 1175: my $total_formula_string = '=0'; 1176: my $maximum_formula_string = '=0'; 1177: foreach my $seq (@sequences) { 1178: my $symb = $seq->symb; 1179: $excel_sheet->write($sequence_name_row,, 1180: $cols_output,$seq->compTitle,$format->{'bold'}); 1181: # Determine starting cell 1182: $formula_data{$symb}->{'Excel:startcell'}= 1183: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1184: ($first_data_row,$cols_output); 1185: $formula_data{$symb}->{'Excel:startcol'}=$cols_output; 1186: my $count = 0; 1187: if ($chosen_output->{'every_problem'}) { 1188: # Put the names of the problems and parts into the sheet 1189: foreach my $res (&get_resources($navmap,$seq)) { 1190: if (scalar(@{$res->parts}) > 1) { 1191: foreach my $part (@{$res->parts}) { 1192: $excel_sheet->write($resource_name_row, 1193: $cols_output++, 1194: $res->compTitle.' part '.$res->part_display($part), 1195: $format->{'bold'}); 1196: $count++; 1197: } 1198: } else { 1199: $excel_sheet->write($resource_name_row, 1200: $cols_output++, 1201: $res->compTitle,$format->{'bold'}); 1202: $count++; 1203: } 1204: } 1205: } 1206: # Determine ending cell 1207: if ($count <= 1) { 1208: $formula_data{$symb}->{'Excel:endcell'} = $formula_data{$symb}->{'Excel:startcell'}; 1209: $formula_data{$symb}->{'Excel:endcol'} = $formula_data{$symb}->{'Excel:startcol'}; 1210: } else { 1211: $formula_data{$symb}->{'Excel:endcell'} = 1212: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1213: ($first_data_row,$cols_output-1); 1214: $formula_data{$symb}->{'Excel:endcol'} = $cols_output-1; 1215: } 1216: # Create the formula for summing up this sequence 1217: if (! exists($formula_data{$symb}->{'Excel:endcell'}) || 1218: ! defined($formula_data{$symb}->{'Excel:endcell'})) { 1219: $formula_data{$symb}->{'Excel:endcell'} = $formula_data{$symb}->{'Excel:startcell'}; 1220: } 1221: 1222: my $start = $formula_data{$symb}->{'Excel:startcell'}; 1223: my $end = $formula_data{$symb}->{'Excel:endcell'}; 1224: $formula_data{$symb}->{'Excel:sum'}= $excel_sheet->store_formula 1225: ("=IF(COUNT($start\:$end),SUM($start\:$end),\"\")"); 1226: # Determine cell the score is held in 1227: $formula_data{$symb}->{'Excel:scorecell'} = 1228: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1229: ($first_data_row,$cols_output); 1230: $formula_data{$symb}->{'Excel:scorecol'}=$cols_output; 1231: if ($chosen_output->{'base'} eq 'parts correct total') { 1232: $excel_sheet->write($resource_name_row,$cols_output++, 1233: 'parts correct', 1234: $format->{'bold'}); 1235: } elsif ($chosen_output->{'sequence_sum'}) { 1236: if ($chosen_output->{'correct'}) { 1237: # Only reporting the number correct, so do not call it score 1238: $excel_sheet->write($resource_name_row,$cols_output++, 1239: 'sum', 1240: $format->{'bold'}); 1241: } else { 1242: $excel_sheet->write($resource_name_row,$cols_output++, 1243: 'score', 1244: $format->{'bold'}); 1245: } 1246: } 1247: # 1248: $total_formula_string.='+'. 1249: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1250: ($first_data_row,$cols_output-1); 1251: if ($chosen_output->{'sequence_max'}) { 1252: $excel_sheet->write($resource_name_row,$cols_output, 1253: 'maximum', 1254: $format->{'bold'}); 1255: $formula_data{$symb}->{'Excel:maxcell'} = 1256: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1257: ($first_data_row,$cols_output); 1258: $formula_data{$symb}->{'Excel:maxcol'}=$cols_output; 1259: $maximum_formula_string.='+'. 1260: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1261: ($first_data_row,$cols_output); 1262: $cols_output++; 1263: 1264: } 1265: } 1266: if ($chosen_output->{'grand_total'}) { 1267: $excel_sheet->write($resource_name_row,$cols_output++,'Total', 1268: $format->{'bold'}); 1269: } 1270: if ($chosen_output->{'grand_maximum'}) { 1271: $excel_sheet->write($resource_name_row,$cols_output++,'Max. Total', 1272: $format->{'bold'}); 1273: } 1274: $total_formula = $excel_sheet->store_formula($total_formula_string); 1275: $maximum_formula = $excel_sheet->store_formula($maximum_formula_string); 1276: ############################################## 1277: # Output a row for MAX, if appropriate 1278: ############################################## 1279: if ($chosen_output->{'maximum_row'}) { 1280: $cols_output = 0; 1281: foreach my $field (&get_student_fields_to_show()) { 1282: if ($field eq 'username' || $field eq 'fullname' || 1283: $field eq 'id') { 1284: $excel_sheet->write($maximum_data_row,$cols_output++,'Maximum', 1285: $format->{'bold'}); 1286: } else { 1287: $excel_sheet->write($maximum_data_row,$cols_output++,''); 1288: } 1289: } 1290: # 1291: # Add the maximums for each sequence or assessment 1292: my %total_cell_translation; 1293: my %maximum_cell_translation; 1294: foreach my $seq (@sequences) { 1295: my $symb = $seq->symb; 1296: $cols_output=$formula_data{$symb}->{'Excel:startcol'}; 1297: $total_cell_translation{$formula_data{$symb}->{'Excel:scorecell'}}= 1298: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1299: ($maximum_data_row,$formula_data{$symb}->{'Excel:scorecol'}); 1300: $maximum_cell_translation{$formula_data{$symb}->{'Excel:maxcell'}}= 1301: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1302: ($maximum_data_row,$formula_data{$symb}->{'Excel:maxcol'}); 1303: my $weight; 1304: my $max = 0; 1305: foreach my $resource (&get_resources($navmap,$seq)) { 1306: foreach my $part (@{$resource->parts}){ 1307: $weight = 1; 1308: if ($chosen_output->{'scores'}) { 1309: $weight = &Apache::lonnet::EXT 1310: ('resource.'.$part.'.weight',$resource->symb, 1311: undef,undef,undef); 1312: if (!defined($weight) || ($weight eq '')) { 1313: $weight=1; 1314: } 1315: } 1316: if ($chosen_output->{'scores'} && 1317: $chosen_output->{'every_problem'}) { 1318: $excel_sheet->write($maximum_data_row,$cols_output++, 1319: $weight); 1320: } 1321: $max += $weight; 1322: } 1323: } 1324: # 1325: if ($chosen_output->{'sequence_sum'} && 1326: $chosen_output->{'every_problem'}) { 1327: my %replaceCells= 1328: ('^'.$formula_data{$symb}->{'Excel:startcell'}.':'. 1329: $formula_data{$symb}->{'Excel:endcell'}.'$' => 1330: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:startcol'}).':'. 1331: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:endcol'})); 1332: $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, 1333: $formula_data{$symb}->{'Excel:sum'},undef, 1334: %replaceCells, %replaceCells); 1335: 1336: } elsif ($chosen_output->{'sequence_sum'}) { 1337: $excel_sheet->write($maximum_data_row,$cols_output++,$max); 1338: } 1339: if ($chosen_output->{'sequence_max'}) { 1340: $excel_sheet->write($maximum_data_row,$cols_output++,$max); 1341: } 1342: # 1343: } 1344: if ($chosen_output->{'grand_total'}) { 1345: $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, 1346: $total_formula,undef, 1347: %total_cell_translation); 1348: } 1349: if ($chosen_output->{'grand_maximum'}) { 1350: $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, 1351: $maximum_formula,undef, 1352: %maximum_cell_translation); 1353: } 1354: } # End of MAXIMUM row output if ($chosen_output->{'maximum_row'}) { 1355: $rows_output = $first_data_row; 1356: ############################################## 1357: # Output summary table, which actually is above the sequence name row. 1358: ############################################## 1359: if ($chosen_output->{'summary_table'}) { 1360: $cols_output = 0; 1361: $excel_sheet->write($summary_header_row,$cols_output++, 1362: 'Summary Table',$format->{'bold'}); 1363: if ($chosen_output->{'maximum_row'}) { 1364: $excel_sheet->write($summary_header_row,$cols_output++, 1365: 'Maximum',$format->{'bold'}); 1366: } 1367: $excel_sheet->write($summary_header_row,$cols_output++, 1368: 'Average',$format->{'bold'}); 1369: $excel_sheet->write($summary_header_row,$cols_output++, 1370: 'Median',$format->{'bold'}); 1371: $excel_sheet->write($summary_header_row,$cols_output++, 1372: 'Std Dev',$format->{'bold'}); 1373: my $row = $summary_header_row+1; 1374: foreach my $seq (@sequences) { 1375: my $symb = $seq->symb; 1376: $cols_output = 0; 1377: $excel_sheet->write($row,$cols_output++, 1378: $seq->compTitle, 1379: $format->{'bold'}); 1380: if ($chosen_output->{'maximum_row'}) { 1381: $excel_sheet->write 1382: ($row,$cols_output++, 1383: '='. 1384: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1385: ($maximum_data_row,$formula_data{$symb}->{'Excel:scorecol'}) 1386: ); 1387: } 1388: my $range = 1389: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1390: ($first_data_row,$formula_data{$symb}->{'Excel:scorecol'}). 1391: ':'. 1392: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1393: ($first_data_row+$num_students-1,$formula_data{$symb}->{'Excel:scorecol'}); 1394: $excel_sheet->write($row,$cols_output++, 1395: '=AVERAGE('.$range.')'); 1396: $excel_sheet->write($row,$cols_output++, 1397: '=MEDIAN('.$range.')'); 1398: $excel_sheet->write($row,$cols_output++, 1399: '=STDEV('.$range.')'); 1400: $row++; 1401: } 1402: } 1403: ############################################## 1404: # Take care of non-excel initialization 1405: ############################################## 1406: # 1407: # Let the user know what we are doing 1408: my $studentcount = scalar(@Apache::lonstatistics::Students); 1409: if ($env{'form.SelectedStudent'}) { 1410: $studentcount = '1'; 1411: } 1412: if ($studentcount > 1) { 1413: $r->print('<h1>'.&mt('Compiling Excel spreadsheet for [_1] students', 1414: $studentcount)."</h1>\n"); 1415: } else { 1416: $r->print('<h1>'. 1417: &mt('Compiling Excel spreadsheet for 1 student'). 1418: "</h1>\n"); 1419: } 1420: $r->rflush(); 1421: # 1422: # Initialize progress window 1423: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin 1424: ($r,'Excel File Compilation Status', 1425: 'Excel File Compilation Progress', $studentcount, 1426: 'inline',undef,'Statistics','stats_status'); 1427: # 1428: &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 1429: 'Processing first student'); 1430: return; 1431: } 1432: 1433: sub excel_outputstudent { 1434: my ($r,$student) = @_; 1435: if ($request_aborted || ! defined($navmap) || ! defined($excel_sheet)) { 1436: return; 1437: } 1438: $cols_output=0; 1439: # 1440: # Write out student data 1441: my @to_show = &get_student_fields_to_show(); 1442: foreach my $field (@to_show) { 1443: my $value = $student->{$field}; 1444: if ($field eq 'comments') { 1445: $value = &Apache::lonmsgdisplay::retrieve_instructor_comments 1446: ($student->{'username'},$student->{'domain'}); 1447: } 1448: $excel_sheet->write($rows_output,$cols_output++,$value); 1449: } 1450: # 1451: # Get student assessment data 1452: my %StudentsData; 1453: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'}, 1454: $student->{'domain'}, 1455: undef, 1456: $env{'request.course.id'}); 1457: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { 1458: %StudentsData = @tmp; 1459: } 1460: # 1461: # Write out sequence scores and totals data 1462: my %total_cell_translation; 1463: my %maximum_cell_translation; 1464: foreach my $seq (@sequences) { 1465: my $symb = $seq->symb; 1466: $cols_output = $formula_data{$symb}->{'Excel:startcol'}; 1467: # Keep track of cells to translate in total cell 1468: $total_cell_translation{$formula_data{$symb}->{'Excel:scorecell'}} = 1469: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1470: ($rows_output,$formula_data{$symb}->{'Excel:scorecol'}); 1471: # and maximum cell 1472: $maximum_cell_translation{$formula_data{$symb}->{'Excel:maxcell'}} = 1473: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell 1474: ($rows_output,$formula_data{$symb}->{'Excel:maxcol'}); 1475: # 1476: my ($performance,$performance_length,$score,$seq_max,$rawdata); 1477: if ($chosen_output->{'tries'} || $chosen_output->{'correct'}){ 1478: ($performance,$performance_length,$score,$seq_max,$rawdata) = 1479: &student_tries_on_sequence($student,\%StudentsData, 1480: $navmap,$seq,'no'); 1481: } else { 1482: ($performance,$performance_length,$score,$seq_max,$rawdata) = 1483: &student_performance_on_sequence($student,\%StudentsData, 1484: $navmap,$seq,'no', 1485: $chosen_output->{ignore_weight}); 1486: } 1487: if ($chosen_output->{'every_problem'}) { 1488: if ($chosen_output->{'correct'}) { 1489: # only indiciate if each item is correct or not 1490: foreach my $value (@$rawdata) { 1491: # positive means correct, 0 or negative means 1492: # incorrect 1493: $value = $value > 0 ? 1 : 0; 1494: $excel_sheet->write($rows_output,$cols_output++,$value); 1495: } 1496: } else { 1497: foreach my $value (@$rawdata) { 1498: if ($score eq ' ' || !defined($value)) { 1499: $cols_output++; 1500: } else { 1501: $excel_sheet->write($rows_output,$cols_output++, 1502: $value); 1503: } 1504: } 1505: } 1506: } 1507: if ($chosen_output->{'sequence_sum'} && 1508: $chosen_output->{'every_problem'}) { 1509: # Write a formula for the sum of this sequence 1510: my %replaceCells= 1511: ('^'.$formula_data{$symb}->{'Excel:startcell'}.':'.$formula_data{$symb}->{'Excel:endcell'}.'$' 1512: => 1513: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($rows_output,$formula_data{$symb}->{'Excel:startcol'}).':'. 1514: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($rows_output,$formula_data{$symb}->{'Excel:endcol'}) 1515: ); 1516: # The undef is for the format 1517: $excel_sheet->repeat_formula($rows_output,$cols_output++, 1518: $formula_data{$symb}->{'Excel:sum'},undef, 1519: %replaceCells, %replaceCells); 1520: } elsif ($chosen_output->{'sequence_sum'}) { 1521: if ($score eq ' ') { 1522: $cols_output++; 1523: } else { 1524: $excel_sheet->write($rows_output,$cols_output++,$score); 1525: } 1526: } 1527: if ($chosen_output->{'sequence_max'}) { 1528: $excel_sheet->write($rows_output,$cols_output++,$seq_max); 1529: } 1530: } 1531: # 1532: if ($chosen_output->{'grand_total'}) { 1533: $excel_sheet->repeat_formula($rows_output,$cols_output++, 1534: $total_formula,undef, 1535: %total_cell_translation); 1536: } 1537: if ($chosen_output->{'grand_maximum'}) { 1538: $excel_sheet->repeat_formula($rows_output,$cols_output++, 1539: $maximum_formula,undef, 1540: %maximum_cell_translation); 1541: } 1542: # 1543: # Bookkeeping 1544: $rows_output++; 1545: $cols_output=0; 1546: # 1547: # Update the progress window 1548: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); 1549: return; 1550: } 1551: 1552: sub excel_finish { 1553: my ($r) = @_; 1554: if ($request_aborted || ! defined($navmap) || ! defined($excel_sheet)) { 1555: &excel_cleanup(); 1556: return; 1557: } 1558: # 1559: # Write the excel file 1560: $excel_workbook->close(); 1561: # 1562: # Close the progress window 1563: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); 1564: # 1565: # Tell the user where to get their excel file 1566: $r->print('<br />'. 1567: '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n"); 1568: $r->rflush(); 1569: &excel_cleanup(); 1570: return; 1571: } 1572: 1573: } 1574: ####################################################### 1575: ####################################################### 1576: 1577: =pod 1578: 1579: =head2 CSV output routines 1580: 1581: =item &csv_initialize($r) 1582: 1583: =item &csv_outputstudent($r,$student) 1584: 1585: =item &csv_finish($r) 1586: 1587: =cut 1588: 1589: ####################################################### 1590: ####################################################### 1591: { 1592: 1593: my $outputfile; 1594: my $filename; 1595: my $request_aborted; 1596: my %prog_state; # progress window state 1597: my $navmap; 1598: my @sequences; 1599: 1600: sub csv_cleanup { 1601: undef($outputfile); 1602: undef($filename); 1603: undef($request_aborted); 1604: undef(%prog_state); 1605: # 1606: undef($navmap); 1607: undef(@sequences); 1608: } 1609: 1610: sub csv_initialize{ 1611: my ($r) = @_; 1612: 1613: &csv_cleanup(); 1614: ($navmap,@sequences) = 1615: &Apache::lonstatistics::selected_sequences_with_assessments(); 1616: if (! ref($navmap)) { 1617: # Unable to get data, so bail out 1618: $r->print("<h3>". 1619: &mt('Unable to retrieve course information.'). 1620: '</h3>'); 1621: } 1622: # 1623: # Deal with unimplemented requests 1624: $request_aborted = undef; 1625: if ($chosen_output->{'base'} =~ /final table/) { 1626: $r->print(<<END); 1627: <h2>Unable to Complete Request</h2> 1628: <p> 1629: The <b>Summary Table (Scores)</b> option is not available for non-HTML output. 1630: </p> 1631: END 1632: $request_aborted = 1; 1633: } 1634: return if ($request_aborted); 1635: # 1636: # Initialize progress window 1637: my $studentcount = scalar(@Apache::lonstatistics::Students); 1638: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin 1639: ($r,'CSV File Compilation Status', 1640: 'CSV File Compilation Progress', $studentcount, 1641: 'inline',undef,'Statistics','stats_status'); 1642: # 1643: # Open a file 1644: ($outputfile,$filename) = &Apache::loncommon::create_text_file($r,'csv'); 1645: if (! defined($outputfile)) { return ''; } 1646: # 1647: # Datestamp 1648: my $description = $env{'course.'.$env{'request.course.id'}.'.description'}; 1649: print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'. 1650: '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'. 1651: "\n"; 1652: print $outputfile '"'. 1653: &Apache::loncommon::csv_translate 1654: (&Apache::lonstatistics::section_and_enrollment_description()). 1655: '"'."\n"; 1656: foreach my $item ('shortdesc','non_html_notes') { 1657: next if (! exists($chosen_output->{$item})); 1658: print $outputfile 1659: '"'.&Apache::loncommon::csv_translate($chosen_output->{$item}).'"'. 1660: "\n"; 1661: } 1662: # 1663: # Print out the headings 1664: my $sequence_row = ''; 1665: my $resource_row = undef; 1666: foreach my $field (&get_student_fields_to_show()) { 1667: $sequence_row .='"",'; 1668: $resource_row .= '"'.&Apache::loncommon::csv_translate($field).'",'; 1669: } 1670: foreach my $seq (@sequences) { 1671: $sequence_row .= '"'. 1672: &Apache::loncommon::csv_translate($seq->compTitle).'",'; 1673: my $count = 0; 1674: if ($chosen_output->{'every_problem'}) { 1675: foreach my $res (&get_resources($navmap,$seq)) { 1676: if (scalar(@{$res->parts}) < 1) { 1677: next; 1678: } 1679: foreach my $part (@{$res->parts}) { 1680: $resource_row .= '"'. 1681: &Apache::loncommon::csv_translate 1682: ($res->compTitle.', Part '.$res->part_display($part)).'",'; 1683: $count++; 1684: } 1685: } 1686: } 1687: $sequence_row.='"",'x$count; 1688: if ($chosen_output->{'sequence_sum'}) { 1689: if($chosen_output->{'correct'}) { 1690: $resource_row .= '"sum",'; 1691: } else { 1692: $resource_row .= '"score",'; 1693: } 1694: } 1695: if ($chosen_output->{'sequence_max'}) { 1696: $sequence_row.= '"",'; 1697: $resource_row .= '"maximum possible",'; 1698: } 1699: } 1700: if ($chosen_output->{'grand_total'}) { 1701: $sequence_row.= '"",'; 1702: $resource_row.= '"Total",'; 1703: } 1704: if ($chosen_output->{'grand_maximum'}) { 1705: $sequence_row.= '"",'; 1706: $resource_row.= '"Maximum",'; 1707: } 1708: chomp($sequence_row); 1709: chomp($resource_row); 1710: print $outputfile $sequence_row."\n"; 1711: print $outputfile $resource_row."\n"; 1712: return; 1713: } 1714: 1715: sub csv_outputstudent { 1716: my ($r,$student) = @_; 1717: if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) { 1718: return; 1719: } 1720: my $Str = ''; 1721: # 1722: # Output student fields 1723: my @to_show = &get_student_fields_to_show(); 1724: foreach my $field (@to_show) { 1725: my $value = $student->{$field}; 1726: if ($field eq 'comments') { 1727: $value = &Apache::lonmsgdisplay::retrieve_instructor_comments 1728: ($student->{'username'},$student->{'domain'}); 1729: } 1730: $Str .= '"'.&Apache::loncommon::csv_translate($value).'",'; 1731: } 1732: # 1733: # Get student assessment data 1734: my %StudentsData; 1735: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'}, 1736: $student->{'domain'}, 1737: undef, 1738: $env{'request.course.id'}); 1739: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { 1740: %StudentsData = @tmp; 1741: } 1742: # 1743: # Output performance data 1744: my $total = 0; 1745: my $maximum = 0; 1746: foreach my $seq (@sequences) { 1747: my ($performance,$performance_length,$score,$seq_max,$rawdata); 1748: if ($chosen_output->{'tries'}){ 1749: ($performance,$performance_length,$score,$seq_max,$rawdata) = 1750: &student_tries_on_sequence($student,\%StudentsData, 1751: $navmap,$seq,'no'); 1752: } else { 1753: ($performance,$performance_length,$score,$seq_max,$rawdata) = 1754: &student_performance_on_sequence($student,\%StudentsData, 1755: $navmap,$seq,'no', 1756: $chosen_output->{ignore_weight}); 1757: } 1758: if ($chosen_output->{'every_problem'}) { 1759: if ($chosen_output->{'correct'}) { 1760: $score = 0; 1761: # Deal with number of parts correct data 1762: $Str .= '"'.join('","',( map { if ($_>0) { 1763: $score += 1; 1764: 1; 1765: } else { 1766: 0; 1767: } 1768: } @$rawdata)).'",'; 1769: } else { 1770: $Str .= '"'.join('","',(@$rawdata)).'",'; 1771: } 1772: } 1773: if ($chosen_output->{'sequence_sum'}) { 1774: $Str .= '"'.$score.'",'; 1775: } 1776: if ($chosen_output->{'sequence_max'}) { 1777: $Str .= '"'.$seq_max.'",'; 1778: } 1779: $total+=$score; 1780: $maximum += $seq_max; 1781: } 1782: if ($chosen_output->{'grand_total'}) { 1783: $Str .= '"'.$total.'",'; 1784: } 1785: if ($chosen_output->{'grand_maximum'}) { 1786: $Str .= '"'.$maximum.'",'; 1787: } 1788: chop($Str); 1789: $Str .= "\n"; 1790: print $outputfile $Str; 1791: # 1792: # Update the progress window 1793: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); 1794: return; 1795: } 1796: 1797: sub csv_finish { 1798: my ($r) = @_; 1799: if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) { 1800: &csv_cleanup(); 1801: return; 1802: } 1803: close($outputfile); 1804: # 1805: # Close the progress window 1806: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); 1807: # 1808: # Tell the user where to get their csv file 1809: $r->print('<br />'. 1810: '<a href="'.$filename.'">'.&mt('Your csv file.').'</a>'."\n"); 1811: $r->rflush(); 1812: &csv_cleanup(); 1813: return; 1814: 1815: } 1816: 1817: } 1818: 1819: # This function will return an HTML string including a star, with 1820: # a mouseover popup showing the "real" value. An optional second 1821: # argument lets you show something other than a star. 1822: sub show_star { 1823: my $popup = shift; 1824: my $symbol = shift || '*'; 1825: # Escape the popup for JS. 1826: $popup =~ s/([^-a-zA-Z0-9:;,._ ()|!\/?=&*])/'\\' . sprintf("%lo", ord($1))/ge; 1827: 1828: return "<span onmouseover='popup_score(this, \"$popup\");return false;' onmouseout='popdown_score();return false;' style='border: 1px solid #339933; margin: -1px;'>$symbol</span>"; 1829: } 1830: 1831: ####################################################### 1832: ####################################################### 1833: 1834: =pod 1835: 1836: =item &StudentTriesOnSequence() 1837: 1838: Inputs: 1839: 1840: =over 4 1841: 1842: =item $student 1843: 1844: =item $studentdata Hash ref to all student data 1845: 1846: =item $seq Hash ref, the sequence we are working on 1847: 1848: =item $links if defined we will output links to each resource. 1849: 1850: =back 1851: 1852: =cut 1853: 1854: ####################################################### 1855: ####################################################### 1856: sub student_tries_on_sequence { 1857: my ($student,$studentdata,$navmap,$seq,$links) = @_; 1858: $links = 'no' if (! defined($links)); 1859: my $Str = ''; 1860: my ($sum,$max) = (0,0); 1861: my $performance_length = 0; 1862: my @TriesData = (); 1863: my $tries; 1864: my $hasdata = 0; # flag - true if the student has any data on the sequence 1865: foreach my $resource (&get_resources($navmap,$seq)) { 1866: my $resource_data = $studentdata->{$resource->symb}; 1867: my $value = ''; 1868: foreach my $partnum (@{$resource->parts()}) { 1869: $tries = undef; 1870: $max++; 1871: $performance_length++; 1872: my $symbol = ' '; # default to space 1873: # 1874: my $awarded = 0; 1875: if (exists($resource_data->{'resource.'.$partnum.'.awarded'})) { 1876: $awarded = $resource_data->{'resource.'.$partnum.'.awarded'}; 1877: $awarded = 0 if (! $awarded); 1878: } 1879: # 1880: my $status = ''; 1881: if (exists($resource_data->{'resource.'.$partnum.'.solved'})) { 1882: $status = $resource_data->{'resource.'.$partnum.'.solved'}; 1883: } 1884: # 1885: my $tries = 0; 1886: if(exists($resource_data->{'resource.'.$partnum.'.tries'})) { 1887: $tries = $resource_data->{'resource.'.$partnum.'.tries'}; 1888: $hasdata =1; 1889: } 1890: # 1891: if ($awarded > 0) { 1892: # The student has gotten the problem correct to some degree 1893: if ($status eq 'excused') { 1894: $symbol = 'x'; 1895: $max--; 1896: } elsif ($status eq 'correct_by_override') { 1897: $symbol = '+'; 1898: $sum++; 1899: } elsif ($tries > 0) { 1900: if ($tries > 9) { 1901: $symbol = show_star($tries); 1902: } else { 1903: $symbol = $tries; 1904: } 1905: $sum++; 1906: } else { 1907: $symbol = '+'; 1908: $sum++; 1909: } 1910: } else { 1911: # The student has the problem incorrect or it is ungraded 1912: if ($status eq 'excused') { 1913: $symbol = 'x'; 1914: $max--; 1915: } elsif ($status eq 'incorrect_by_override') { 1916: $symbol = '-'; 1917: } elsif ($status eq 'ungraded_attempted') { 1918: $symbol = 'u'; 1919: } elsif ($status eq 'incorrect_attempted' || 1920: $tries > 0) { 1921: $symbol = '.'; 1922: } else { 1923: # Problem is wrong and has not been attempted. 1924: $symbol=' '; 1925: } 1926: } 1927: # 1928: if (! defined($tries)) { 1929: $tries = 0; 1930: } 1931: if ($status =~ /^(incorrect|ungraded)/) { 1932: # Bug 3390: show '-' for tries on incorrect problems 1933: # (csv & excel only) 1934: push(@TriesData,-$tries); 1935: } else { 1936: push (@TriesData,$tries); 1937: } 1938: # 1939: if ( ($links eq 'yes' && $symbol ne ' ') || 1940: ($links eq 'all')) { 1941: if (length($symbol) > 1) { 1942: &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1'); 1943: } 1944: my $link = '/adm/grades'. 1945: '?symb='.&Apache::lonnet::escape($resource->symb). 1946: '&student='.$student->{'username'}. 1947: '&userdom='.$student->{'domain'}. 1948: '&command=submission'; 1949: $symbol = &link($symbol, $link); 1950: } 1951: $value .= $symbol; 1952: } 1953: $Str .= $value; 1954: } 1955: if ($seq->randompick()) { 1956: $max = $seq->randompick(); 1957: } 1958: if (! $hasdata && $sum == 0) { 1959: $sum = ' '; 1960: } 1961: return ($Str,$performance_length,$sum,$max,\@TriesData); 1962: } 1963: 1964: =pod 1965: 1966: =item &link 1967: 1968: Inputs: 1969: 1970: =over 4 1971: 1972: =item $text 1973: 1974: =item $target 1975: 1976: =back 1977: 1978: Takes the text and creates a link to the $text that honors 1979: the value of 'new window' if clicked on, but uses a real 1980: 'href' so middle and right clicks still work. 1981: 1982: $target and $text are assumed to be already correctly escaped; i.e., it 1983: can be dumped out directly into the output stream as-is. 1984: 1985: =cut 1986: 1987: sub link { 1988: my ($text,$target) = @_; 1989: return 1990: "<a href='$target' onclick=\"t=this.href;if(new_window)" 1991: ."{window.open(t)}else{return void(window." 1992: ."location=t)};return false;\">$text</a>"; 1993: } 1994: 1995: ####################################################### 1996: ####################################################### 1997: 1998: =pod 1999: 2000: =item &student_performance_on_sequence 2001: 2002: Inputs: 2003: 2004: =over 4 2005: 2006: =item $student 2007: 2008: =item $studentdata Hash ref to all student data 2009: 2010: =item $seq Hash ref, the sequence we are working on 2011: 2012: =item $links if defined we will output links to each resource. 2013: 2014: =back 2015: 2016: =cut 2017: 2018: ####################################################### 2019: ####################################################### 2020: sub student_performance_on_sequence { 2021: my ($student,$studentdata,$navmap,$seq,$links,$awarded_only) = @_; 2022: $links = 'no' if (! defined($links)); 2023: my $Str = ''; # final result string 2024: my ($score,$max) = (0,0); 2025: my $performance_length = 0; 2026: my $symbol; 2027: my @ScoreData = (); 2028: my $partscore; 2029: my $hasdata = 0; # flag, 0 if there were no submissions on the sequence 2030: foreach my $resource (&get_resources($navmap,$seq)) { 2031: my $symb = $resource->symb; 2032: my $resource_data = $studentdata->{$symb}; 2033: foreach my $part (@{$resource->parts()}) { 2034: $partscore = undef; 2035: my $weight; 2036: if (!$awarded_only){ 2037: $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', 2038: $symb, 2039: $student->{'domain'}, 2040: $student->{'username'}, 2041: $student->{'section'}); 2042: } 2043: if (!defined($weight) || ($weight eq '')) { 2044: $weight=1; 2045: } 2046: # 2047: $max += $weight; # see the 'excused' branch below... 2048: $performance_length++; # one character per part 2049: $symbol = ' '; # default to space 2050: # 2051: my $awarded; 2052: if (exists($resource_data->{'resource.'.$part.'.awarded'})) { 2053: $awarded = $resource_data->{'resource.'.$part.'.awarded'}; 2054: $awarded = 0 if (! $awarded); 2055: $hasdata = 1; 2056: } 2057: # 2058: $partscore = &Apache::grades::compute_points($weight,$awarded); 2059: if (! defined($awarded)) { 2060: $partscore = undef; 2061: } 2062: $score += $partscore; 2063: $symbol = $partscore; 2064: if (abs($symbol - sprintf("%.0f",$symbol)) < 0.001) { 2065: $symbol = sprintf("%.0f",$symbol); 2066: } 2067: if (length($symbol) > 1) { 2068: $symbol = show_star($symbol); 2069: } 2070: if (exists($resource_data->{'resource.'.$part.'.solved'}) && 2071: $resource_data->{'resource.'.$part.'.solved'} ne '') { 2072: my $status = $resource_data->{'resource.'.$part.'.solved'}; 2073: if ($status eq 'excused') { 2074: $symbol = 'x'; 2075: $max -= $weight; # Do not count 'excused' problems. 2076: } elsif ($status eq 'ungraded_attempted') { 2077: $symbol = 'u'; 2078: } 2079: $hasdata = 1; 2080: } elsif ($resource_data->{'resource.'.$part.'.award'} eq 'DRAFT') { 2081: $symbol = 'd'; 2082: $hasdata = 1; 2083: } elsif (!exists($resource_data->{'resource.'.$part.'.awarded'})){ 2084: # Unsolved. Did they try? 2085: if (exists($resource_data->{'resource.'.$part.'.tries'})){ 2086: $symbol = '.'; 2087: $hasdata = 1; 2088: } else { 2089: $symbol = ' '; 2090: } 2091: } 2092: # 2093: if (! defined($partscore)) { 2094: $partscore = $symbol; 2095: } 2096: push (@ScoreData,$partscore); 2097: # 2098: if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) { 2099: my $link = '/adm/grades' . 2100: '?symb='.&Apache::lonnet::escape($symb). 2101: '&student='.$student->{'username'}. 2102: '&userdom='.$student->{'domain'}. 2103: '&command=submission'; 2104: $symbol = &link($symbol, $link); 2105: } 2106: $Str .= $symbol; 2107: } 2108: } 2109: if (! $hasdata && $score == 0) { 2110: $score = ' '; 2111: } 2112: return ($Str,$performance_length,$score,$max,\@ScoreData); 2113: } 2114: 2115: ####################################################### 2116: ####################################################### 2117: 2118: =pod 2119: 2120: =item &CreateLegend() 2121: 2122: This function returns a formatted string containing the legend for the 2123: chart. The legend describes the symbols used to represent grades for 2124: problems. 2125: 2126: =cut 2127: 2128: ####################################################### 2129: ####################################################### 2130: sub CreateLegend { 2131: my $Str = "<p><pre>". 2132: " digit score or number of tries to get correct ". 2133: " * correct by student in more than 9 tries\n". 2134: " + correct by hand grading or override\n". 2135: " - incorrect by override\n". 2136: " . incorrect attempted\n". 2137: " u ungraded attempted\n". 2138: " d draft answer saved but not submitted\n". 2139: " not attempted (blank field)\n". 2140: " x excused". 2141: "</pre><p>"; 2142: return $Str; 2143: } 2144: 2145: ####################################################### 2146: ####################################################### 2147: 2148: =pod 2149: 2150: =back 2151: 2152: =cut 2153: 2154: ####################################################### 2155: ####################################################### 2156: 2157: 1; 2158: 2159: __END__