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