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