Annotation of loncom/interface/statistics/lonproblemstatistics.pm, revision 1.77
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.77 ! matthew 3: # $Id: lonproblemstatistics.pm,v 1.76 2004/03/29 19:41:24 matthew Exp $
1.1 stredwic 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: # (Navigate problems for statistical reports
28: #
1.47 matthew 29: ###############################################
30: ###############################################
31:
32: =pod
33:
34: =head1 NAME
35:
36: lonproblemstatistics
37:
38: =head1 SYNOPSIS
39:
40: Routines to present problem statistics to instructors via tables,
41: Excel files, and plots.
42:
43: =over 4
44:
45: =cut
46:
47: ###############################################
48: ###############################################
1.1 stredwic 49:
1.36 minaeibi 50: package Apache::lonproblemstatistics;
1.1 stredwic 51:
52: use strict;
53: use Apache::lonnet();
1.62 matthew 54: use Apache::loncommon();
1.1 stredwic 55: use Apache::lonhtmlcommon;
56: use Apache::loncoursedata;
1.41 matthew 57: use Apache::lonstatistics;
1.59 matthew 58: use Apache::lonlocal;
1.44 matthew 59: use Spreadsheet::WriteExcel;
1.70 matthew 60: use Apache::lonstathelpers();
1.71 matthew 61: use Time::HiRes;
1.73 matthew 62:
63: my @StatsArray;
64:
1.59 matthew 65: ##
66: ## Localization notes:
67: ##
68: ## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the
69: ## header for plots created with Graph.pm, both of which more than likely do
70: ## not support localization.
71: ##
1.49 matthew 72: my @Fields = (
73: { name => 'problem_num',
74: title => 'P#',
75: align => 'right',
1.76 matthew 76: color => '#FFFFE6',
77: selectable => 'no',
78: selected => 'yes',
79: },
1.49 matthew 80: { name => 'container',
1.51 matthew 81: title => 'Sequence or Folder',
1.49 matthew 82: align => 'left',
83: color => '#FFFFE6',
1.76 matthew 84: sortable => 'yes',
85: selectable => 'no',
86: selected => 'yes',
87: },
1.49 matthew 88: { name => 'title',
89: title => 'Title',
90: align => 'left',
91: color => '#FFFFE6',
92: special => 'link',
1.76 matthew 93: sortable => 'yes',
94: selectable => 'no',
95: selected => 'yes',
96: },
1.49 matthew 97: { name => 'part',
98: title => 'Part',
99: align => 'left',
1.55 matthew 100: color => '#FFFFE6',
1.76 matthew 101: selectable => 'no',
102: selected => 'yes',
103: },
1.49 matthew 104: { name => 'num_students',
105: title => '#Stdnts',
106: align => 'right',
107: color => '#EEFFCC',
108: format => '%d',
109: sortable => 'yes',
110: graphable => 'yes',
1.76 matthew 111: long_title => 'Number of Students Attempting Problem',
112: selectable => 'yes',
113: selected => 'yes',
114: },
1.49 matthew 115: { name => 'tries',
116: title => 'Tries',
117: align => 'right',
118: color => '#EEFFCC',
119: format => '%d',
120: sortable => 'yes',
121: graphable => 'yes',
1.76 matthew 122: long_title => 'Total Number of Tries',
123: selectable => 'yes',
124: selected => 'yes',
125: },
1.49 matthew 126: { name => 'max_tries',
127: title => 'Max Tries',
128: align => 'right',
129: color => '#DDFFFF',
130: format => '%d',
131: sortable => 'yes',
132: graphable => 'yes',
1.76 matthew 133: long_title => 'Maximum Number of Tries',
134: selectable => 'yes',
135: selected => 'yes',
136: },
1.73 matthew 137: { name => 'min_tries',
138: title => 'Min Tries',
139: align => 'right',
140: color => '#DDFFFF',
141: format => '%d',
142: sortable => 'yes',
143: graphable => 'yes',
1.76 matthew 144: long_title => 'Minumum Number of Tries',
145: selectable => 'yes',
146: selected => 'yes',
147: },
1.49 matthew 148: { name => 'mean_tries',
149: title => 'Mean Tries',
150: align => 'right',
151: color => '#DDFFFF',
152: format => '%5.2f',
153: sortable => 'yes',
154: graphable => 'yes',
1.76 matthew 155: long_title => 'Average Number of Tries',
156: selectable => 'yes',
157: selected => 'yes',
158: },
1.49 matthew 159: { name => 'std_tries',
160: title => 'S.D. tries',
161: align => 'right',
162: color => '#DDFFFF',
163: format => '%5.2f',
164: sortable => 'yes',
165: graphable => 'yes',
1.76 matthew 166: long_title => 'Standard Deviation of Number of Tries',
167: selectable => 'yes',
168: selected => 'yes',
169: },
1.49 matthew 170: { name => 'skew_tries',
171: title => 'Skew Tries',
172: align => 'right',
173: color => '#DDFFFF',
174: format => '%5.2f',
175: sortable => 'yes',
176: graphable => 'yes',
1.76 matthew 177: long_title => 'Skew of Number of Tries',
178: selectable => 'yes',
179: selected => 'no',
180: },
1.49 matthew 181: { name => 'num_solved',
182: title => '#YES',
183: align => 'right',
184: color => '#FFDDDD',
1.63 matthew 185: format => '%4.1f',# format => '%d',
1.49 matthew 186: sortable => 'yes',
187: graphable => 'yes',
1.76 matthew 188: long_title => 'Number of Students able to Solve',
1.77 ! matthew 189: selectable => 'yes',
1.76 matthew 190: selected => 'yes',
191: },
1.49 matthew 192: { name => 'num_override',
193: title => '#yes',
194: align => 'right',
195: color => '#FFDDDD',
1.63 matthew 196: format => '%4.1f',# format => '%d',
1.49 matthew 197: sortable => 'yes',
198: graphable => 'yes',
1.76 matthew 199: long_title => 'Number of Students given Override',
200: selectable => 'yes',
201: selected => 'yes',
202: },
1.73 matthew 203: { name => 'num_wrong',
204: title => '#Wrng',
1.49 matthew 205: align => 'right',
1.73 matthew 206: color => '#FFDDDD',
1.49 matthew 207: format => '%4.1f',
208: sortable => 'yes',
209: graphable => 'yes',
1.76 matthew 210: long_title => 'Percent of students whose final answer is wrong',
211: selectable => 'yes',
212: selected => 'yes',
213: },
1.73 matthew 214: { name => 'deg_of_diff',
215: title => 'DoDiff',
216: align => 'right',
217: color => '#FFFFE6',
218: format => '%5.2f',
219: sortable => 'yes',
220: graphable => 'yes',
221: long_title => 'Degree of Difficulty'.
1.76 matthew 222: '[ 1 - ((#YES+#yes) / Tries) ]',
223: selectable => 'yes',
224: selected => 'yes',
225: },
1.71 matthew 226: { name => 'deg_of_disc',
1.73 matthew 227: title => 'DoDisc',
1.71 matthew 228: align => 'right',
229: color => '#FFFFE6',
230: format => '%4.2f',
231: sortable => 'yes',
232: graphable => 'yes',
1.76 matthew 233: long_title => 'Degree of Discrimination',
234: selectable => 'yes',
235: selected => 'no',
236: },
1.49 matthew 237: );
238:
1.76 matthew 239: my %SelectedFields;
240:
241: sub parse_field_selection {
242: #
243: # Pull out the defaults
244: if (! defined($ENV{'form.fieldselections'})) {
245: $ENV{'form.fieldselections'} = [];
246: foreach my $field (@Fields) {
247: next if ($field->{'selectable'} ne 'yes');
248: if ($field->{'selected'} eq 'yes') {
249: push(@{$ENV{'form.fieldselections'}},$field->{'name'});
250: }
251: }
252: }
253: #
254: # This should not happen, but in case it does...
255: if (ref($ENV{'form.fieldselections'}) ne 'ARRAY') {
256: $ENV{'form.fieldselections'} = [$ENV{'form.fieldselections'}];
257: }
258: #
259: # Set the field data and the selected fields (for easier checking)
260: undef(%SelectedFields);
261: foreach my $field (@Fields) {
262: next if ($field->{'selectable'} ne 'yes');
263: $field->{'selected'} = 'no';
264: foreach my $selection (@{$ENV{'form.fieldselections'}}) {
265: if ($selection eq $field->{'name'} || $selection eq 'all') {
266: $field->{'selected'} = 'yes';
267: $SelectedFields{$field->{'name'}}++;
268: }
269: }
270: }
271: return;
272: }
273:
274: sub field_selection_input {
275: my $Str = '<select name="fieldselections" multiple size="5">'."\n";
276: $Str .= '<option value="all">all</option>'."\n";
277: foreach my $field (@Fields) {
278: next if ($field->{'selectable'} ne 'yes');
279: $Str .= ' <option value="'.$field->{'name'}.'" ';
280: if ($field->{'selected'} eq 'yes') {
281: $Str .= 'selected ';
282: }
283: $Str .= '>'.$field->{'title'}.'</option>'."\n";
284: }
285: $Str .= "</select>\n";
286: }
287:
1.47 matthew 288: ###############################################
289: ###############################################
290:
291: =pod
292:
293: =item &CreateInterface()
294:
295: Create the main intereface for the statistics page. Allows the user to
296: select sections, maps, and output.
297:
298: =cut
1.1 stredwic 299:
1.47 matthew 300: ###############################################
301: ###############################################
1.41 matthew 302: sub CreateInterface {
1.76 matthew 303: &parse_field_selection();
1.41 matthew 304: my $Str = '';
1.67 matthew 305: $Str .= &Apache::lonhtmlcommon::breadcrumbs
1.69 matthew 306: (undef,'Overall Problem Statistics','Statistics_Overall_Key');
1.41 matthew 307: $Str .= '<table cellspacing="5">'."\n";
308: $Str .= '<tr>';
1.59 matthew 309: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
310: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
311: $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
1.76 matthew 312: $Str .= '<td align="center"><b>'.&mt('Statistics').'</b></td>';
1.70 matthew 313: $Str .= '<td rowspan="2">'.
314: &Apache::lonstathelpers::limit_by_time_form().'</td>';
1.41 matthew 315: $Str .= '</tr>'."\n";
316: #
317: $Str .= '<tr><td align="center">'."\n";
318: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.50 matthew 319: $Str .= '</td><td align="center">';
320: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.41 matthew 321: $Str .= '</td><td align="center">';
322: #
323: my $only_seq_with_assessments = sub {
324: my $s=shift;
325: if ($s->{'num_assess'} < 1) {
326: return 0;
327: } else {
328: return 1;
329: }
330: };
331: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
332: $only_seq_with_assessments);
1.76 matthew 333: $Str .= '</td><td>'.&field_selection_input();
1.41 matthew 334: $Str .= '</td></tr>'."\n";
335: $Str .= '</table>'."\n";
1.59 matthew 336: $Str .= '<input type="submit" name="GenerateStatistics" value="'.
337: &mt('Generate Statistics').'" />';
1.54 matthew 338: $Str .= ' 'x5;
1.73 matthew 339: $Str .= 'Plot '.&plot_dropdown().(' 'x10);
1.59 matthew 340: $Str .= '<input type="submit" name="ClearCache" value="'.
341: &mt('Clear Caches').'" />';
1.54 matthew 342: $Str .= ' 'x5;
1.73 matthew 343: $Str .= '<input type="submit" name="UpdateCache" value="'.
344: &mt('Update Student Data').'" />';
345: $Str .= ' 'x5;
346: $Str .= '<input type="submit" name="Excel" value="'.
347: &mt('Produce Excel Output').'" />';
348: $Str .= ' 'x5;
349: return $Str;
1.41 matthew 350: }
1.25 stredwic 351:
1.41 matthew 352: ###############################################
353: ###############################################
1.28 stredwic 354:
1.47 matthew 355: =pod
356:
357: =item &BuildProblemStatisticsPage()
358:
359: Main interface to problem statistics.
360:
361: =cut
362:
1.41 matthew 363: ###############################################
364: ###############################################
365: sub BuildProblemStatisticsPage {
366: my ($r,$c)=@_;
1.61 matthew 367: #
368: my %Saveable_Parameters = ('Status' => 'scalar',
369: 'statsoutputmode' => 'scalar',
370: 'Section' => 'array',
371: 'StudentData' => 'array',
1.77 ! matthew 372: 'Maps' => 'array',
! 373: 'fieldselections'=> 'array');
1.61 matthew 374: &Apache::loncommon::store_course_settings('statistics',
375: \%Saveable_Parameters);
376: &Apache::loncommon::restore_course_settings('statistics',
377: \%Saveable_Parameters);
378: #
379: &Apache::lonstatistics::PrepareClasslist();
1.41 matthew 380: #
1.73 matthew 381: # Clear the package variables
382: undef(@StatsArray);
1.71 matthew 383: #
1.73 matthew 384: # Finally let the user know we are here
385: my $interface = &CreateInterface();
1.57 matthew 386: $r->print($interface);
1.41 matthew 387: $r->print('<input type="hidden" name="sortby" value="'.$ENV{'form.sortby'}.
388: '" />');
1.73 matthew 389: #
1.41 matthew 390: if (! exists($ENV{'form.statsfirstcall'})) {
1.73 matthew 391: $r->print('<input type="hidden" name="statsfirstcall" value="yes" />');
392: $r->print('<h3>'.
393: &mt('Press "Generate Statistics" when you are ready.').
394: '</h3><p>'.
395: &mt('It may take some time to update the student data '.
396: 'for the first analysis. Future analysis this session '.
397: ' will not have this delay.').
398: '</p>');
1.41 matthew 399: return;
1.73 matthew 400: } elsif ($ENV{'form.statsfirstcall'} eq 'yes' ||
401: exists($ENV{'form.UpdateCache'}) ||
402: exists($ENV{'form.ClearCache'}) ) {
403: $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
404: &Apache::lonstatistics::Gather_Student_Data($r);
405: } else {
406: $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
1.28 stredwic 407: }
1.73 matthew 408: $r->rflush();
1.41 matthew 409: #
1.73 matthew 410: # This probably does not need to be done each time we are called, but
411: # it does not slow things down noticably.
412: &Apache::loncoursedata::populate_weight_table();
1.75 matthew 413: #
1.73 matthew 414: if (exists($ENV{'form.Excel'})) {
415: &Excel_output($r);
416: } else {
1.75 matthew 417: my $count = 0;
418: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
419: $count += $seq->{'num_assess'};
420: }
421: if ($count > 10) {
422: $r->print('<h2>'.
423: &mt('Compiling statistics for [_1] problems',$count).
424: '</h2>');
425: if ($count > 30) {
426: $r->print('<h3>'.&mt('This will take some time.').'</h3>');
427: }
428: $r->rflush();
429: }
430: #
1.73 matthew 431: my $sortby = $ENV{'form.sortby'};
432: $sortby = 'container' if (! defined($sortby) || $sortby =~ /^\s*$/);
433: my $plot = $ENV{'form.plot'};
1.75 matthew 434: if ($plot eq '' || $plot eq 'none') {
435: undef($plot);
436: }
1.73 matthew 437: if ($sortby eq 'container' && ! defined($plot)) {
438: &output_html_by_sequence($r);
439: } else {
440: if (defined($plot)) {
441: &make_plot($r,$plot);
442: }
443: &output_html_stats($r);
444: }
445: }
446: return;
447: }
448:
449: ##########################################################
450: ##########################################################
451: ##
452: ## HTML output routines
453: ##
454: ##########################################################
455: ##########################################################
456: sub output_html_by_sequence {
457: my ($r) = @_;
458: my $c = $r->connection();
459: $r->print(&html_preamble());
1.41 matthew 460: #
1.73 matthew 461: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
462: last if ($c->aborted);
463: next if ($seq->{'num_assess'} < 1);
464: $r->print("<h3>".$seq->{'title'}."</h3>".
465: '<table border="0"><tr><td bgcolor="#777777">'."\n".
466: '<table border="0" cellpadding="3">'."\n".
467: '<tr bgcolor="#FFFFE6">'.
468: &statistics_table_header('no container')."</tr>\n");
469: my @Data = &compute_statistics_on_sequence($seq);
470: foreach my $data (@Data) {
471: $r->print('<tr>'.&statistics_html_table_data($data,
472: 'no container').
473: "</tr>\n");
1.70 matthew 474: }
1.73 matthew 475: $r->print('</table>'."\n".'</table>'."\n");
1.41 matthew 476: $r->rflush();
1.28 stredwic 477: }
1.41 matthew 478: return;
479: }
1.21 stredwic 480:
1.73 matthew 481: sub output_html_stats {
482: my ($r)=@_;
483: &compute_all_statistics($r);
484: $r->print(&html_preamble());
485: &sort_data($ENV{'form.sortby'});
486: #
487: my $count=0;
488: foreach my $data (@StatsArray) {
489: if ($count++ % 50 == 0) {
490: $r->print("</table>\n</table>\n");
491: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n".
492: '<table border="0" cellpadding="3">'."\n".
493: '<tr bgcolor="#FFFFE6">'.
494: '<tr bgcolor="#FFFFE6">'.
495: &statistics_table_header().
496: "</tr>\n");
497: }
498: $r->print('<tr>'.&statistics_html_table_data($data)."</tr>\n");
499: }
500: $r->print("</table>\n</table>\n");
501: return;
502: }
1.47 matthew 503:
1.73 matthew 504: sub html_preamble {
505: my $Str='';
506: $Str .= "<h2>".
507: $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
508: "</h2>\n";
509: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
510: if (defined($starttime) || defined($endtime)) {
511: # Inform the user what the time limits on the data are.
512: $Str .= '<h3>'.&mt('Statistics on submissions from [_1] to [_2]',
513: &Apache::lonlocal::locallocaltime($starttime),
514: &Apache::lonlocal::locallocaltime($endtime)
515: ).'</h3>';
516: }
517: $Str .= "<h3>".&mt('Compiled on [_1]',
518: &Apache::lonlocal::locallocaltime(time))."</h3>";
519: return $Str;
520: }
1.47 matthew 521:
522:
1.44 matthew 523: ###############################################
524: ###############################################
1.73 matthew 525: ##
526: ## Misc HTML output routines
527: ##
528: ###############################################
529: ###############################################
530: sub statistics_html_table_data {
531: my ($data,$options) = @_;
532: my $row = '';
533: foreach my $field (@Fields) {
534: next if ($options =~ /no $field->{'name'}/);
1.76 matthew 535: next if ($field->{'selected'} ne 'yes');
1.73 matthew 536: $row .= '<td bgcolor="'.$field->{'color'}.'"';
537: if (exists($field->{'align'})) {
538: $row .= ' align="'.$field->{'align'}.'"';
1.41 matthew 539: }
1.73 matthew 540: $row .= '>';
541: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
542: $row .= '<a href="'.$data->{$field->{'name'}.'.link'}.'">';
1.41 matthew 543: }
1.73 matthew 544: if (exists($field->{'format'})) {
545: $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
546: } else {
547: $row .= $data->{$field->{'name'}};
548: }
549: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
550: $row.= '</a>';
551: }
552: $row .= '</td>';
1.21 stredwic 553: }
1.73 matthew 554: return $row;
1.41 matthew 555: }
1.25 stredwic 556:
1.73 matthew 557: sub statistics_table_header {
558: my ($options) = @_;
559: my $header_row;
560: foreach my $field (@Fields) {
561: next if ($options =~ /no $field->{'name'}/);
1.76 matthew 562: next if ($field->{'selected'} ne 'yes');
1.73 matthew 563: $header_row .= '<th>';
564: if (exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
565: $header_row .= '<a href="javascript:'.
566: 'document.Statistics.sortby.value='."'".$field->{'name'}."'".
567: ';document.Statistics.submit();">';
568: }
569: $header_row .= &mt($field->{'title'});
570: if ($options =~ /sortable/) {
571: $header_row.= '</a>';
572: }
573: if ($options !~ /no plots/ &&
574: exists($field->{'graphable'}) &&
575: $field->{'graphable'} eq 'yes') {
576: $header_row.=' (';
577: $header_row .= '<a href="javascript:'.
578: "document.Statistics.plot.value='$field->{'name'}'".
579: ';document.Statistics.submit();">';
580: $header_row .= &mt('plot').'</a>)';
581: }
582: $header_row .= '</th>';
583: }
584: return $header_row;
585: }
1.26 stredwic 586:
1.73 matthew 587: ####################################################
588: ####################################################
589: ##
590: ## Plotting Routines
591: ##
592: ####################################################
593: ####################################################
594: sub make_plot {
595: my ($r,$plot) = @_;
596: &compute_all_statistics($r);
597: &sort_data($ENV{'form.sortby'});
598: if ($plot eq 'degrees') {
599: °rees_plot($r);
1.74 matthew 600: } elsif ($plot eq 'tries statistics') {
601: &tries_data_plot($r);
1.73 matthew 602: } else {
603: &make_single_stat_plot($r,$plot);
604: }
605: return;
606: }
1.47 matthew 607:
1.73 matthew 608: sub make_single_stat_plot {
609: my ($r,$datafield) = @_;
1.41 matthew 610: #
1.73 matthew 611: my $title; my $yaxis;
612: foreach my $field (@Fields) {
613: next if ($field->{'name'} ne $datafield);
614: $title = $field->{'long_title'};
615: $yaxis = $field->{'title'};
616: last;
617: }
618: if ($title eq '' || $yaxis eq '') {
619: # datafield is something we do not know enough about to plot
620: $r->print('<h3>'.
621: &mt('Unable to plot the requested statistic.').
622: '</h3>');
623: return;
1.49 matthew 624: }
625: #
1.73 matthew 626: # Build up the data sets to plot
627: my @Labels;
628: my @Data;
629: my $max = 1;
630: foreach my $data (@StatsArray) {
631: push(@Labels,$data->{'problem_num'});
632: push(@Data,$data->{$datafield});
633: if ($data->{$datafield}>$max) {
634: $max = $data->{$datafield};
635: }
636: }
637: foreach (1,2,3,4,5,10,15,20,25,40,50,75,100,150,200,250,300,500,600,750,
638: 1000,1500,2000,2500,3000,3500,4000,5000,7500,10000,15000,20000) {
639: if ($max <= $_) {
640: $max = $_;
641: last;
1.42 matthew 642: }
643: }
1.73 matthew 644: if ($max > 20000) {
645: $max = 10000*(int($max/10000)+1);
1.42 matthew 646: }
1.73 matthew 647: #
648: $r->print("<p>".&Apache::loncommon::DrawBarGraph($title,
649: 'Problem Number',
650: $yaxis,
651: $max,
652: undef, # colors
653: \@Labels,
654: \@Data)."</p>\n");
655: return;
656: }
657:
658: sub degrees_plot {
659: my ($r)=@_;
660: my $count = scalar(@StatsArray);
661: my $width = 50 + 10*$count;
662: $width = 300 if ($width < 300);
663: my $height = 300;
664: my $plot = '';
665: my $ymax = 0;
666: my $ymin = 0;
667: my @Disc; my @Diff; my @Labels;
668: foreach my $data (@StatsArray) {
669: push(@Labels,$data->{'problem_num'});
670: my $disc = $data->{'deg_of_disc'};
671: my $diff = $data->{'deg_of_diff'};
672: push(@Disc,$disc);
673: push(@Diff,$diff);
674: #
675: $ymin = $disc if ($ymin > $disc);
676: $ymin = $diff if ($ymin > $diff);
677: $ymax = $disc if ($ymax < $disc);
678: $ymax = $diff if ($ymax < $diff);
679: }
680: #
681: # Make sure we show relevant information.
682: if ($ymin < 0) {
683: if (abs($ymin) < 0.05) {
684: $ymin = 0;
685: } else {
686: $ymin = -1;
1.42 matthew 687: }
688: }
1.73 matthew 689: if ($ymax > 0) {
690: if (abs($ymax) < 0.05) {
691: $ymax = 0;
1.42 matthew 692: } else {
1.73 matthew 693: $ymax = 1;
1.42 matthew 694: }
1.43 matthew 695: }
1.49 matthew 696: #
1.73 matthew 697: my $xmax = $Labels[-1];
698: if ($xmax > 50) {
699: if ($xmax % 10 != 0) {
700: $xmax = 10 * (int($xmax/10)+1);
701: }
702: } else {
703: if ($xmax % 5 != 0) {
704: $xmax = 5 * (int($xmax/5)+1);
1.49 matthew 705: }
1.26 stredwic 706: }
1.41 matthew 707: #
1.73 matthew 708: my $discdata .= '<data>'.join(',',@Labels).'</data>'.$/.
709: '<data>'.join(',',@Disc).'</data>'.$/;
710: #
711: my $diffdata .= '<data>'.join(',',@Labels).'</data>'.$/.
712: '<data>'.join(',',@Diff).'</data>'.$/;
713: #
714: $plot=<<"END";
715: <gnuplot
716: texfont="10"
717: fgcolor="x000000"
718: plottype="Cartesian"
719: font="large"
720: grid="on"
721: align="center"
722: border="on"
723: transparent="on"
724: alttag="Sample Plot"
725: samples="100"
726: bgcolor="xffffff"
727: height="$height"
728: width="$width">
729: <key
730: pos="top right"
731: title=""
732: box="off" />
733: <title>Degree of Discrmination and Degree of Difficulty</title>
734: <axis xmin="0" ymin="$ymin" xmax="$xmax" ymax="$ymax" color="x000000" />
735: <xlabel>Problem Number</xlabel>
736: <curve
737: linestyle="linespoints"
738: name="DoDisc"
739: pointtype="0"
740: color="x000000">
741: $discdata
742: </curve>
743: <curve
744: linestyle="linespoints"
745: name="DoDiff"
746: pointtype="0"
747: color="xFF0000">
748: $diffdata
749: </curve>
750: </gnuplot>
751: END
752: my $plotresult =
753: '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
754: $r->print($plotresult);
1.41 matthew 755: return;
1.42 matthew 756: }
757:
1.74 matthew 758: sub tries_data_plot {
759: my ($r)=@_;
760: my $count = scalar(@StatsArray);
761: my $width = 50 + 10*$count;
762: $width = 300 if ($width < 300);
763: my $height = 300;
764: my $plot = '';
765: my @STD; my @Mean; my @Max; my @Min;
766: my @Labels;
767: my $ymax = 5;
768: foreach my $data (@StatsArray) {
769: my $max = $data->{'mean_tries'} + $data->{'std_tries'};
770: $ymax = $max if ($ymax < $max);
771: $ymax = $max if ($ymax < $max);
772: push(@Labels,$data->{'problem_num'});
773: push(@STD,$data->{'std_tries'});
774: push(@Mean,$data->{'mean_tries'});
775: }
776: #
777: # Make sure we show relevant information.
778: my $xmax = $Labels[-1];
779: if ($xmax > 50) {
780: if ($xmax % 10 != 0) {
781: $xmax = 10 * (int($xmax/10)+1);
782: }
783: } else {
784: if ($xmax % 5 != 0) {
785: $xmax = 5 * (int($xmax/5)+1);
786: }
787: }
788: $ymax = int($ymax)+1+2;
789: #
790: my $std_data .= '<data>'.join(',',@Labels).'</data>'.$/.
791: '<data>'.join(',',@Mean).'</data>'.$/;
792: #
793: my $std_error_data .= '<data>'.join(',',@Labels).'</data>'.$/.
794: '<data>'.join(',',@Mean).'</data>'.$/.
795: '<data>'.join(',',@STD).'</data>'.$/;
796: #
797: $plot=<<"END";
798: <gnuplot
799: texfont="10"
800: fgcolor="x000000"
801: plottype="Cartesian"
802: font="large"
803: grid="on"
804: align="center"
805: border="on"
806: transparent="on"
807: alttag="Sample Plot"
808: samples="100"
809: bgcolor="xffffff"
810: height="$height"
811: width="$width">
812: <title>Mean and S.D. of Tries</title>
813: <axis xmin="0" ymin="0" xmax="$xmax" ymax="$ymax" color="x000000" />
814: <xlabel>Problem Number</xlabel>
815: <curve
816: linestyle="yerrorbars"
817: name="S.D. Tries"
818: pointtype="1"
819: color="x666666">
820: $std_error_data
821: </curve>
822: <curve
823: linestyle="points"
824: name="Mean Tries"
825: pointtype="1"
826: color="xCC4444">
827: $std_data
828: </curve>
829: </gnuplot>
830: END
831: my $plotresult =
832: '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
833: $r->print($plotresult);
834: return;
835: }
836:
1.73 matthew 837: sub plot_dropdown {
838: my $current = '';
839: #
840: if (defined($ENV{'form.plot'})) {
841: $current = $ENV{'form.plot'};
842: }
843: #
844: my @Additional_Plots = (
845: { graphable=>'yes',
846: name => 'degrees',
1.74 matthew 847: title => 'DoDisc and DoDiff' },
848: { graphable=>'yes',
849: name => 'tries statistics',
850: title => 'Mean and S.D. of Tries' });
1.73 matthew 851: #
852: my $Str= "\n".'<select name="plot" size="1">';
853: $Str .= '<option name="none"></option>'."\n";
854: $Str .= '<option name="none2">none</option>'."\n";
855: foreach my $field (@Fields,@Additional_Plots) {
856: if (! exists($field->{'graphable'}) ||
857: $field->{'graphable'} ne 'yes') {
858: next;
859: }
860: $Str .= '<option value="'.$field->{'name'}.'"';
861: if ($field->{'name'} eq $current) {
862: $Str .= ' selected ';
863: }
864: $Str.= '>'.&mt($field->{'title'}).'</option>'."\n";
865: }
866: $Str .= '</select>'."\n";
867: return $Str;
868: }
869:
1.41 matthew 870: ###############################################
871: ###############################################
1.73 matthew 872: ##
873: ## Excel output routines
874: ##
1.41 matthew 875: ###############################################
876: ###############################################
1.73 matthew 877: sub Excel_output {
1.44 matthew 878: my ($r) = @_;
1.73 matthew 879: $r->print('<h2>'.&mt('Preparing Excel Spreadsheet').'</h2>');
880: ##
881: ## Compute the statistics
882: &compute_all_statistics($r);
883: my $c = $r->connection;
884: return if ($c->aborted());
885: ##
886: ## Create the excel workbook
1.44 matthew 887: my $filename = '/prtspool/'.
888: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1.73 matthew 889: time.'_'.rand(1000000000).'.xls';
1.70 matthew 890: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
891: #
1.44 matthew 892: # Create sheet
1.73 matthew 893: my $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
1.44 matthew 894: #
895: # Check for errors
896: if (! defined($excel_workbook)) {
897: $r->log_error("Error creating excel spreadsheet $filename: $!");
1.59 matthew 898: $r->print(&mt("Problems creating new Excel file. ".
1.44 matthew 899: "This error has been logged. ".
1.59 matthew 900: "Please alert your LON-CAPA administrator."));
1.73 matthew 901: return 0;
1.44 matthew 902: }
903: #
904: # The excel spreadsheet stores temporary data in files, then put them
905: # together. If needed we should be able to disable this (memory only).
906: # The temporary directory must be specified before calling 'addworksheet'.
907: # File::Temp is used to determine the temporary directory.
908: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
909: #
910: # Add a worksheet
911: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
912: if (length($sheetname) > 31) {
913: $sheetname = substr($sheetname,0,31);
914: }
1.73 matthew 915: my $excel_sheet = $excel_workbook->addworksheet(
916: &Apache::loncommon::clean_excel_name($sheetname));
917: ##
918: ## Begin creating excel sheet
919: ##
920: my ($rows_output,$cols_output) = (0,0);
1.44 matthew 921: #
922: # Put the course description in the header
923: $excel_sheet->write($rows_output,$cols_output++,
924: $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
925: $cols_output += 3;
926: #
927: # Put a description of the sections listed
928: my $sectionstring = '';
929: my @Sections = @Apache::lonstatistics::SelectedSections;
930: if (scalar(@Sections) > 1) {
931: if (scalar(@Sections) > 2) {
932: my $last = pop(@Sections);
933: $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
934: } else {
935: $sectionstring = "Sections ".join(' and ',@Sections);
936: }
937: } else {
938: if ($Sections[0] eq 'all') {
939: $sectionstring = "All sections";
940: } else {
941: $sectionstring = "Section ".$Sections[0];
942: }
943: }
944: $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
945: $cols_output += scalar(@Sections);
946: #
1.70 matthew 947: # Time restrictions
948: my $time_string;
949: if (defined($starttime)) {
950: # call localtime but not lonlocal:locallocaltime because excel probably
951: # cannot handle localized text. Probably.
952: $time_string .= 'Data collected from '.localtime($time_string);
953: if (defined($endtime)) {
954: $time_string .= ' to '.localtime($endtime);
955: }
956: $time_string .= '.';
957: } elsif (defined($endtime)) {
958: # See note above about lonlocal:locallocaltime
959: $time_string .= 'Data collected before '.localtime($endtime).'.';
960: }
961: #
1.44 matthew 962: # Put the date in there too
963: $excel_sheet->write($rows_output,$cols_output++,
964: 'Compiled on '.localtime(time));
965: #
966: $rows_output++;
967: $cols_output=0;
968: #
1.73 matthew 969: # Long Headers
1.55 matthew 970: foreach my $field (@Fields) {
971: next if ($field->{'name'} eq 'problem_num');
1.76 matthew 972: next if ($field->{'selected'} ne 'yes');
1.55 matthew 973: if (exists($field->{'long_title'})) {
974: $excel_sheet->write($rows_output,$cols_output++,
975: $field->{'long_title'});
976: } else {
977: $excel_sheet->write($rows_output,$cols_output++,'');
978: }
979: }
980: $rows_output++;
981: $cols_output=0;
982: # Brief headers
1.49 matthew 983: foreach my $field (@Fields) {
1.76 matthew 984: next if ($field->{'selected'} ne 'yes');
1.49 matthew 985: next if ($field->{'name'} eq 'problem_num');
1.59 matthew 986: # Use english for excel as I am not sure how well excel handles
987: # other character sets....
1.49 matthew 988: $excel_sheet->write($rows_output,$cols_output++,$field->{'title'});
1.44 matthew 989: }
990: $rows_output++;
1.73 matthew 991: foreach my $data (@StatsArray) {
992: $cols_output=0;
993: foreach my $field (@Fields) {
1.76 matthew 994: next if ($field->{'selected'} ne 'yes');
1.73 matthew 995: next if ($field->{'name'} eq 'problem_num');
996: $excel_sheet->write($rows_output,$cols_output++,
997: $data->{$field->{'name'}});
1.44 matthew 998: }
1.73 matthew 999: $rows_output++;
1.44 matthew 1000: }
1001: #
1002: $excel_workbook->close();
1.73 matthew 1003: #
1.44 matthew 1004: # Tell the user where to get their excel file
1005: $r->print('<br />'.
1.59 matthew 1006: '<a href="'.$filename.'">'.
1007: &mt('Your Excel Spreadsheet').'</a>'."\n");
1.44 matthew 1008: $r->rflush();
1009: return;
1010: }
1011:
1.73 matthew 1012: ##################################################
1013: ##################################################
1014: ##
1015: ## Statistics Gathering and Manipulation Routines
1016: ##
1017: ##################################################
1018: ##################################################
1019: sub compute_statistics_on_sequence {
1020: my ($seq) = @_;
1021: my @Data;
1022: foreach my $res (@{$seq->{'contents'}}) {
1023: next if ($res->{'type'} ne 'assessment');
1024: foreach my $part (@{$res->{'parts'}}) {
1025: #
1026: # This is where all the work happens
1027: my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1);
1028: push (@Data,$data);
1029: push (@StatsArray,$data);
1.49 matthew 1030: }
1.26 stredwic 1031: }
1.73 matthew 1032: return @Data;
1.41 matthew 1033: }
1.26 stredwic 1034:
1.73 matthew 1035: sub compute_all_statistics {
1036: my ($r) = @_;
1037: if (@StatsArray > 0) {
1038: # Assume we have already computed the statistics
1039: return;
1040: }
1041: my $c = $r->connection;
1042: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1043: last if ($c->aborted);
1044: next if ($seq->{'num_assess'} < 1);
1045: &compute_statistics_on_sequence($seq);
1.49 matthew 1046: }
1047: }
1048:
1.73 matthew 1049: sub sort_data {
1050: my ($sortkey) = @_;
1051: return if (! @StatsArray);
1.45 matthew 1052: #
1.73 matthew 1053: # Sort the data
1054: my $sortby = undef;
1.49 matthew 1055: foreach my $field (@Fields) {
1.73 matthew 1056: if ($sortkey eq $field->{'name'}) {
1057: $sortby = $field->{'name'};
1.45 matthew 1058: }
1.26 stredwic 1059: }
1.73 matthew 1060: if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
1061: $sortby = 'container';
1062: }
1063: if ($sortby ne 'container') {
1064: # $sortby is already defined, so we can charge ahead
1065: if ($sortby =~ /^(title|part)$/i) {
1066: # Alpha comparison
1067: @StatsArray = sort {
1068: lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
1069: lc($a->{'title'}) cmp lc($b->{'title'}) ||
1070: lc($a->{'part'}) cmp lc($b->{'part'});
1071: } @StatsArray;
1.24 stredwic 1072: } else {
1.73 matthew 1073: # Numerical comparison
1074: @StatsArray = sort {
1075: my $retvalue = 0;
1076: if ($b->{$sortby} eq 'nan') {
1077: if ($a->{$sortby} ne 'nan') {
1078: $retvalue = -1;
1079: } else {
1080: $retvalue = 0;
1081: }
1082: }
1083: if ($a->{$sortby} eq 'nan') {
1084: if ($b->{$sortby} ne 'nan') {
1085: $retvalue = 1;
1086: }
1087: }
1088: if ($retvalue eq '0') {
1089: $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
1090: lc($a->{'title'}) <=> lc($b->{'title'}) ||
1091: lc($a->{'part'}) <=> lc($b->{'part'});
1092: }
1093: $retvalue;
1094: } @StatsArray;
1.24 stredwic 1095: }
1096: }
1.45 matthew 1097: #
1.73 matthew 1098: # Renumber the data set
1099: my $count;
1100: foreach my $data (@StatsArray) {
1101: $data->{'problem_num'} = ++$count;
1102: }
1.24 stredwic 1103: return;
1.48 matthew 1104: }
1105:
1.70 matthew 1106: ########################################################
1107: ########################################################
1108:
1109: =pod
1110:
1111: =item &get_statistics()
1112:
1113: Wrapper routine from the call to loncoursedata::get_problem_statistics.
1.73 matthew 1114: Calls lonstathelpers::get_time_limits() to limit the data set by time
1115: and &compute_discrimination_factor
1.70 matthew 1116:
1117: Inputs: $sequence, $resource, $part, $problem_num
1118:
1119: Returns: Hash reference with statistics data from
1120: loncoursedata::get_problem_statistics.
1121:
1122: =cut
1123:
1124: ########################################################
1125: ########################################################
1.48 matthew 1126: sub get_statistics {
1.49 matthew 1127: my ($sequence,$resource,$part,$problem_num) = @_;
1.48 matthew 1128: #
1.70 matthew 1129: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.49 matthew 1130: my $symb = $resource->{'symb'};
1.48 matthew 1131: my $courseid = $ENV{'request.course.id'};
1132: #
1.49 matthew 1133: my $data = &Apache::loncoursedata::get_problem_statistics
1.66 matthew 1134: (\@Apache::lonstatistics::SelectedSections,
1135: $Apache::lonstatistics::enrollment_status,
1.70 matthew 1136: $symb,$part,$courseid,$starttime,$endtime);
1.49 matthew 1137: $data->{'part'} = $part;
1138: $data->{'problem_num'} = $problem_num;
1139: $data->{'container'} = $sequence->{'title'};
1140: $data->{'title'} = $resource->{'title'};
1.53 matthew 1141: $data->{'title.link'} = $resource->{'src'}.'?symb='.
1142: &Apache::lonnet::escape($resource->{'symb'});
1.49 matthew 1143: #
1.76 matthew 1144: if ($SelectedFields{'deg_of_disc'}) {
1145: $data->{'deg_of_disc'} =
1146: &compute_discrimination_factor($resource,$part,$sequence);
1147: }
1.49 matthew 1148: return $data;
1.71 matthew 1149: }
1150:
1151:
1152: ###############################################
1153: ###############################################
1154:
1155: =pod
1156:
1157: =item &compute_discrimination_factor()
1158:
1159: Inputs: $Resource, $Sequence
1160:
1161: Returns: integer between -1 and 1
1162:
1163: =cut
1164:
1165: ###############################################
1166: ###############################################
1167: sub compute_discrimination_factor {
1168: my ($resource,$part,$sequence) = @_;
1169: my @Resources;
1170: foreach my $res (@{$sequence->{'contents'}}) {
1171: next if ($res->{'symb'} eq $resource->{'symb'});
1172: push (@Resources,$res->{'symb'});
1173: }
1174: #
1175: # rank
1176: my $ranking =
1177: &Apache::loncoursedata::rank_students_by_scores_on_resources
1178: (\@Resources,
1179: \@Apache::lonstatistics::SelectedSections,
1180: $Apache::lonstatistics::enrollment_status,undef);
1181: #
1182: # compute their percent scores on the problems in the sequence,
1183: my $number_to_grab = int(scalar(@{$ranking})/4);
1184: my $num_students = scalar(@{$ranking});
1185: my @BottomSet = map { $_->[&Apache::loncoursedata::RNK_student()];
1186: } @{$ranking}[0..$number_to_grab];
1187: my @TopSet =
1188: map {
1189: $_->[&Apache::loncoursedata::RNK_student()];
1190: } @{$ranking}[($num_students-$number_to_grab)..($num_students-1)];
1191: my ($bottom_sum,$bottom_max) =
1192: &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet);
1193: my ($top_sum,$top_max) =
1194: &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet);
1195: my $deg_of_disc;
1196: if ($top_max == 0 || $bottom_max==0) {
1197: $deg_of_disc = 'nan';
1198: } else {
1199: $deg_of_disc = ($top_sum/$top_max) - ($bottom_sum/$bottom_max);
1200: }
1201: #&Apache::lonnet::logthis(' '.$top_sum.'/'.$top_max.
1202: # ' - '.$bottom_sum.'/'.$bottom_max);
1203: return $deg_of_disc;
1.1 stredwic 1204: }
1.12 minaeibi 1205:
1.45 matthew 1206: ###############################################
1207: ###############################################
1.47 matthew 1208:
1209: =pod
1210:
1.73 matthew 1211: =item ProblemStatisticsLegend
1212:
1213: =over 4
1214:
1215: =item #Stdnts
1216: Total number of students attempted the problem.
1217:
1218: =item Tries
1219: Total number of tries for solving the problem.
1.59 matthew 1220:
1.73 matthew 1221: =item Max Tries
1222: Largest number of tries for solving the problem by a student.
1223:
1224: =item Mean
1225: Average number of tries. [ Tries / #Stdnts ]
1226:
1227: =item #YES
1228: Number of students solved the problem correctly.
1229:
1230: =item #yes
1231: Number of students solved the problem by override.
1232:
1233: =item %Wrong
1234: Percentage of students who tried to solve the problem
1235: but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
1236:
1237: =item DoDiff
1238: Degree of Difficulty of the problem.
1239: [ 1 - ((#YES+#yes) / Tries) ]
1240:
1241: =item S.D.
1242: Standard Deviation of the tries.
1243: [ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1)
1244: where Xi denotes every student\'s tries ]
1245:
1246: =item Skew.
1247: Skewness of the students tries.
1248: [(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
1249:
1250: =item Dis.F.
1251: Discrimination Factor: A Standard for evaluating the
1252: problem according to a Criterion<br>
1253:
1254: =item [Criterion to group students into %27 Upper Students -
1255: and %27 Lower Students]
1256: 1st Criterion for Sorting the Students:
1257: Sum of Partial Credit Awarded / Total Number of Tries
1258: 2nd Criterion for Sorting the Students:
1259: Total number of Correct Answers / Total Number of Tries
1260:
1261: =item Disc.
1262: Number of Students had at least one discussion.
1263:
1264: =back
1.47 matthew 1265:
1266: =cut
1.1 stredwic 1267:
1.73 matthew 1268:
1269: ############################################################
1270: ############################################################
1271: ##
1272: ## How this all works:
1273: ## Statistics are computed by calling &get_statistics with the sequence,
1274: ## resource, and part id to run statistics on. At various places within
1275: ## the loops which compute the statistics, as well as before and after
1276: ## the entire process, subroutines can be called. The subroutines are
1277: ## registered to the following hooks:
1278: ##
1279: ## hook subroutine inputs
1280: ## ----------------------------------------------------------
1281: ## pre $r,$count
1282: ## pre_seq $r,$count,$seq
1283: ## pre_res $r,$count,$seq,$res
1284: ## calc $r,$count,$seq,$res,$data
1285: ## post_res $r,$count,$seq,$res
1286: ## post_seq $r,$count,$seq
1287: ## post $r,$count
1288: ##
1289: ## abort $r
1290: ##
1291: ## subroutines will be called in the order in which they are registered.
1292: ##
1293: ############################################################
1294: ############################################################
1295: {
1296:
1297: my %hooks;
1298: my $aborted = 0;
1299:
1300: sub abort_computation {
1301: $aborted = 1;
1302: }
1303:
1304: sub clear_hooks {
1305: $aborted = 0;
1306: undef(%hooks);
1307: }
1308:
1309: sub register_hook {
1310: my ($hookname,$subref)=@_;
1311: if ($hookname !~ /^(pre|pre_seq|pre_res|post|post_seq|post_res|calc)$/){
1312: return;
1313: }
1314: if (ref($subref) ne 'CODE') {
1315: &Apache::lonnet::logthis('attempt to register hook to non-code: '.
1316: $hookname,' = '.$subref);
1317: } else {
1318: if (exists($hooks{$hookname})) {
1319: push(@{$hooks{$hookname}},$subref);
1320: } else {
1321: $hooks{$hookname} = [$subref];
1322: }
1323: }
1324: return;
1325: }
1326:
1327: sub run_hooks {
1328: my $context = shift();
1329: foreach my $hook (@{$hooks{$context}}) {
1330: if ($aborted && $context ne 'abort') {
1331: last;
1332: }
1333: my $retvalue = $hook->(@_);
1334: if (defined($retvalue) && $retvalue eq '0') {
1335: $aborted = 1 if (! $aborted);
1336: }
1337: }
1338: }
1339:
1340: sub run_statistics {
1341: my ($r) = @_;
1342: my $count = 0;
1343: &run_hooks('pre',$r,$count);
1344: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1345: last if ($aborted);
1346: next if ($seq->{'num_assess'}<1);
1347: &run_hooks('pre_seq',$r,$count,$seq);
1348: foreach my $res (@{$seq->{'contents'}}) {
1349: last if ($aborted);
1350: next if ($res->{'type'} ne 'assessment');
1351: &run_hooks('pre_res',$r,$count,$seq,$res);
1352: foreach my $part (@{$res->{'parts'}}) {
1353: last if ($aborted);
1354: #
1355: # This is where all the work happens
1356: my $data = &get_statistics($seq,$res,$part,++$count);
1357: &run_hooks('calc',$r,$count,$seq,$res,$part,$data);
1358: }
1359: &run_hooks('post_res',$r,$count,$seq,$res);
1360: }
1361: &run_hooks('post_seq',$r,$count,$seq);
1362: }
1363: if ($aborted) {
1364: &run_hooks('abort',$r);
1365: } else {
1366: &run_hooks('post',$r,$count);
1367: }
1368: return;
1.1 stredwic 1369: }
1.24 stredwic 1370:
1.73 matthew 1371: } # End of %hooks scope
1372:
1373: ############################################################
1374: ############################################################
1.4 minaeibi 1375:
1.1 stredwic 1376: 1;
1377: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>