File:  [LON-CAPA] / loncom / interface / statistics / lonproblemanalysis.pm
Revision 1.88: download - view: text, annotated - select for diffs
Tue Sep 28 20:41:58 2004 UTC (19 years, 9 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Interface simplification: got rid of "analyze as" nonsense.  We try to
produce three plots for each attempt or time interval: concept, foil correct,
and foil incorrect.  If there is only one concept the concept plot is omitted.
Some Style Police(tm) induced changes.  Titles of plots are more consistent
(since they appear on the same page now).

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonproblemanalysis.pm,v 1.88 2004/09/28 20:41:58 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: #
    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: package Apache::lonproblemanalysis;
   28: 
   29: use strict;
   30: use Apache::lonnet();
   31: use Apache::loncommon();
   32: use Apache::lonhtmlcommon();
   33: use Apache::loncoursedata();
   34: use Apache::lonstatistics;
   35: use Apache::lonlocal;
   36: use Apache::lonstathelpers();
   37: use Apache::lonstudentsubmissions();
   38: use HTML::Entities();
   39: use Time::Local();
   40: use Spreadsheet::WriteExcel();
   41: 
   42: my $plotcolors = ['#33ff00', 
   43:                   '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
   44:                   '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
   45:                   ]; 
   46: 
   47: my @SubmitButtons = ({ name => 'PrevProblemAnalysis',
   48:                        text => 'Previous Problem' },
   49:                      { name => 'ProblemAnalysis',
   50:                        text => 'Analyze Problem Again' },
   51:                      { name => 'NextProblemAnalysis',
   52:                        text => 'Next Problem' },
   53:                      { name => 'break'},
   54:                      { name => 'SelectAnother',
   55:                        text => 'Choose a different Problem' },
   56:                      { name => 'ExcelOutput',
   57:                        text => 'Produce Excel Output' });
   58: 
   59: 
   60: sub BuildProblemAnalysisPage {
   61:     my ($r,$c)=@_;
   62:     #
   63:     my %Saveable_Parameters = ('Status' => 'scalar',
   64:                                'Section' => 'array',
   65:                                'NumPlots' => 'scalar',
   66:                                'AnalyzeOver' => 'scalar',
   67:                                );
   68:     &Apache::loncommon::store_course_settings('problem_analysis',
   69:                                               \%Saveable_Parameters);
   70:     &Apache::loncommon::restore_course_settings('problem_analysis',
   71:                                                 \%Saveable_Parameters);
   72:     #
   73:     &Apache::lonstatistics::PrepareClasslist();
   74:     #
   75:     $r->print(&CreateInterface());
   76:     #
   77:     my @Students = @Apache::lonstatistics::Students;
   78:     #
   79:     if (@Students < 1 && exists($ENV{'form.firstrun'})) {
   80:         $r->print('<h2>There are no students in the sections selected</h2>');
   81:     }
   82:     #
   83:     my @CacheButtonHTML = 
   84:         &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
   85:     $r->rflush();
   86:     #
   87:     # Support for numerical and radio response isn't complete enough to
   88:     # include in 1.2 release.
   89:     # my $problem_types = '(option|radiobutton|numerical)';
   90:     my $problem_types = '(option)';
   91:     if (exists($ENV{'form.problemchoice'}) && 
   92:         ! exists($ENV{'form.SelectAnother'})) {
   93:         foreach my $button (@SubmitButtons) {
   94:             if ($button->{'name'} eq 'break') {
   95:                 $r->print("<br />\n");
   96:             } else {
   97:                 $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
   98:                           'value="'.&mt($button->{'text'}).'" />');
   99:                 $r->print('&nbsp;'x5);
  100:             }
  101:         }
  102:         foreach my $html (@CacheButtonHTML) {
  103:             $r->print($html.('&nbsp;'x5));
  104:         }
  105:         #
  106:         $r->print('<hr />');
  107:         $r->rflush();
  108:         #
  109:         # Determine which problem we are to analyze
  110:         my $current_problem = &Apache::lonstathelpers::get_target_from_id
  111:             ($ENV{'form.problemchoice'});
  112:         #
  113:         my ($prev,$curr,$next) = 
  114:             &Apache::lonstathelpers::get_prev_curr_next($current_problem,
  115:                                                         $problem_types,
  116:                                                         'response',
  117:                                                         );
  118:         if (exists($ENV{'form.PrevProblemAnalysis'}) && defined($prev)) {
  119:             $current_problem = $prev;
  120:         } elsif (exists($ENV{'form.NextProblemAnalysis'}) && defined($next)) {
  121:             $current_problem = $next;
  122:         } else {
  123:             $current_problem = $curr;
  124:         }
  125:         #
  126:         # Store the current problem choice and send it out in the form
  127:         $ENV{'form.problemchoice'} = 
  128:             &Apache::lonstathelpers::make_target_id($current_problem);
  129:         $r->print('<input type="hidden" name="problemchoice" value="'.
  130:                   $ENV{'form.problemchoice'}.'" />');
  131:         #
  132:         if (! defined($current_problem->{'resource'})) {
  133:             $r->print('resource is undefined');
  134:         } else {
  135:             my $resource = $current_problem->{'resource'};
  136:             $r->print('<h1>'.$resource->{'title'}.'</h1>');
  137:             $r->print('<h3>'.$resource->{'src'}.'</h3>');
  138:             $r->print(&Apache::lonstathelpers::render_resource($resource));
  139:             $r->rflush();
  140:             my %Data = &Apache::lonstathelpers::get_problem_data
  141:                 ($resource->{'src'});
  142:             my $problem_data = $Data{$current_problem->{'part'}.
  143:                                     '.'.
  144:                                     $current_problem->{'respid'}};
  145:             if ($current_problem->{'resptype'} eq 'option') {
  146:                 &OptionResponseAnalysis($r,$current_problem,
  147:                                         $problem_data,
  148:                                         \@Students);
  149:             } elsif ($current_problem->{'resptype'} eq 'radiobutton') {
  150:                 &RadioResponseAnalysis($r,$current_problem,
  151:                                        $problem_data,
  152:                                        \@Students);
  153:             } elsif ($current_problem->{'resptype'} eq 'numerical') {
  154:                 ## 
  155:                 ## analyze all responses of a problem at once
  156:                 my $res = $current_problem->{'resource'};
  157:                 foreach my $partid (@{$res->{'parts'}}) {
  158:                     $current_problem->{'part'} = $partid;
  159:                     foreach my $respid (@{$res->{'partdata'}->{$partid}->{'ResponseIds'}}) {
  160:                         $current_problem->{'respid'}=$respid;
  161:                         &NumericalResponseAnalysis($r,$current_problem,
  162:                                                    $problem_data,\@Students);
  163:                     }
  164:                 }
  165:             } else {
  166:                 $r->print('<h2>This analysis is not supported</h2>');
  167:             }
  168:         }
  169:         $r->print('<hr />');
  170:     } else {
  171:         $r->print('<input type="submit" name="ProblemAnalysis" value="'.
  172:                   &mt('Analyze Problem').'" />');
  173:         $r->print('&nbsp;'x5);
  174:         $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
  175:         $r->print(&Apache::lonstathelpers::ProblemSelector
  176:                   ($problem_types));
  177:     }
  178: }
  179: 
  180: 
  181: #########################################################
  182: #########################################################
  183: ##
  184: ##      Numerical Response Routines
  185: ##
  186: #########################################################
  187: #########################################################
  188: sub NumericalResponseAnalysis {
  189:     my ($r,$problem,$problem_data,$Students) = @_;
  190:     my $c = $r->connection();
  191:     my ($resource,$partid,$respid) = ($problem->{'resource'},
  192:                                       $problem->{'part'},
  193:                                       $problem->{'respid'});
  194:     #
  195:     if (scalar(@{$resource->{'parts'}})>1) {
  196:         if (@{$resource->{'partdata'}->{$partid}->{'ResponseIds'}}>1) {
  197:             $r->print('<h3>'.
  198:                       &mt('Part [_1], response [_2].',$partid,$respid).
  199:                       '</h3>');
  200:         } else { 
  201:             $r->print('<h3>'.
  202:                       &mt('Part [_1]',$partid,$respid).
  203:                       '</h3>');
  204:         }
  205:     } elsif (@{$resource->{'partdata'}->{$partid}->{'ResponseIds'}}>1) {
  206:         $r->print('<h3>'.&mt('Response [_1]',$respid).'</h3>');
  207:     }
  208:     #
  209:     my $analysis_html;
  210:     my $PerformanceData = &Apache::loncoursedata::get_response_data
  211:         (\@Apache::lonstatistics::SelectedSections,
  212:          $Apache::lonstatistics::enrollment_status,
  213:          $resource->{'symb'},$respid);
  214:     if (! defined($PerformanceData) || 
  215:         ref($PerformanceData) ne 'ARRAY' ) {
  216:         $analysis_html = '<h2>'.
  217:             &mt('There is no submission data for this resource').
  218:             '</h2>';
  219:         $r->print($analysis_html);
  220:         return;
  221:     }
  222:     #
  223:     # This next call causes all the waiting around that people complain about
  224:     my ($max,$min) = 
  225:         &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,
  226:                                                    'Statistics',
  227:                                                    'stats_status');
  228:     return if ($c->aborted());
  229:     #
  230:     # Collate the data
  231:     my %Data;
  232:     foreach my $student (@$Students) {
  233:         my $answer = $student->{'answer'};
  234:         $Data{$answer}++;
  235:     }
  236:     my @Labels = sort {$a <=> $b } keys(%Data);
  237:     my @PlotData = @Data{@Labels};
  238:     #
  239:     my $width  = 500; 
  240:     my $height = 100;
  241:     my $plot = &one_dimensional_plot($r,500,100,scalar(@$Students),
  242:                                      \@Labels,\@PlotData);
  243:     $r->print($plot);
  244:     return;
  245: }
  246: 
  247: sub one_dimensional_plot {
  248:     my ($r,$width,$height,$N,$Labels,$Data)=@_;
  249:     #
  250:     # Compute data -> image scaling factors
  251:     my $min = $Labels->[0];
  252:     my $max = $Labels->[-1];
  253:     if ($max == $min) {
  254:         $max =$min+1;
  255:     }
  256:     my $h_scale = ($width-10)/($max-$min);
  257:     #
  258:     my $max_y = 0;
  259:     foreach (@$Data) {
  260:         $max_y = $_ if ($max_y < $_);
  261:     }
  262:     my $ticscale = 5;
  263:     if ($max_y * $ticscale > $height/2) {
  264:         $ticscale = int($height/2/$max_y);
  265:         $ticscale = 1 if ($ticscale < 1);
  266:     }
  267:     #
  268:     # Create the plot
  269:     my $plot = 
  270:         qq{<drawimage width="$width" height="$height" bgcolor="transparent" >};
  271:     for (my $idx=0;$idx<scalar(@$Labels);$idx++) {
  272:         my $xloc = 5+$h_scale*($Labels->[$idx] - $min);
  273:         my $top    = $height/2-$Data->[$idx]*$ticscale;
  274:         my $bottom = $height/2+$Data->[$idx]*$ticscale;
  275:         $plot .= 
  276:             &line($xloc,$top,$xloc,$bottom,'888888',1);
  277:     }
  278:     #
  279:     # Put the scale on last to ensure it is on top of the data.
  280:     if ($min < 0 && $max > 0) {
  281:         my $circle_x = 5+$h_scale*abs($min);  # '0' in data coordinates
  282:         my $r = 4;
  283:         $plot .= &line(5,$height/2,$circle_x-$r,$height/2,'000000',1);
  284:         $plot .= &circle($circle_x,$height/2,$r+1,'000000');
  285:         $plot .= &line($circle_x+$r,$height/2,$width-5,$height/2,'000000',1);
  286:     } else {
  287:         $plot .= &line(5,$height/2,$width-5,$height/2,'000000',1);
  288:     }
  289:     $plot .= '</drawimage>';
  290:     my $plotresult =  &Apache::lonxml::xmlparse($r,'web',$plot);
  291:     
  292:     my $title = 'Distribution of correct answers';
  293:     my $result = '<table>'.
  294:         '<tr><td colspan="3" align="center">'.
  295:         '<font size="+2">'.$title.' (N='.$N.')'.
  296:         '</font>'.
  297:         '</td></tr>'.
  298:         '<tr>'.
  299:         '<td valign="center">'.$min.'</td>'.
  300:         '<td>'.$plotresult.'</td>'.
  301:         '<td valign="center">'.$max.'</td>'.
  302:         '</tr>'.
  303:         '<tr><td colspan="3" align="center">'.
  304:         'Maximum Number of Coinciding Values: '.$max_y.
  305:         '</td></tr>'.
  306:         '</table>';
  307:     return $result;
  308: }
  309: 
  310: ##
  311: ## Helper subroutines for <drawimage>.  
  312: ## These should probably go somewhere more suitable soon.
  313: sub line {
  314:     my ($x1,$y1,$x2,$y2,$color,$thickness) = @_;
  315:     return qq{<line x1="$x1" y1="$y1" x2="$x2" y2="$y2" color="$color" thickness="$thickness" />$/};
  316: }
  317: 
  318: sub text {
  319:     my ($x,$y,$color,$text,$font,$direction) = @_;
  320:     if (! defined($font) || $font !~ /^(tiny|small|medium|large|giant)$/) {
  321:         $font = 'medium';
  322:     }
  323:     if (! defined($direction) || $direction ne 'vertical') {
  324:         $direction = '';
  325:     }
  326:     return qq{<text x="$x" y="$y" color="$color" font="$font" direction="$direction" >$text</text>};
  327: }
  328: 
  329: sub rectangle {
  330:     my ($x1,$y1,$x2,$y2,$color,$thickness,$filled) = @_;
  331:     return qq{<rectangle x1="$x1" y1="$y1" x2="$x2" y2="$y2" color="$color" thickness="$thickness" filled="$filled" />};
  332: }
  333: 
  334: sub arc {
  335:     my ($x,$y,$width,$height,$start,$end,$color,$thickness,$filled)=@_;
  336:     return qq{<arc x="$x" y="$y" width="$width" height="$height" start="$start" end="$end" color="$color" thickness="$thickness" filled="$filled" />};
  337: }
  338: 
  339: sub circle {
  340:     my ($x,$y,$radius,$color,$thickness,$filled)=@_;
  341:     return &arc($x,$y,$radius,$radius,0,360,$color,$thickness,$filled);
  342: }
  343: 
  344: sub build_student_data_worksheet {
  345:     my ($workbook,$format) = @_;
  346:     my $rows_output = 3;
  347:     my $cols_output = 0;
  348:     my $worksheet  = $workbook->addworksheet('Student Data');
  349:     $worksheet->write($rows_output++,0,'Student Data',$format->{'h3'});
  350:     my @Headers = ('full name','username','domain','section',
  351:                    "student\nnumber",'identifier');
  352:     $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
  353:     my @Students = @Apache::lonstatistics::Students;
  354:     my $studentrows = &Apache::loncoursedata::get_student_data(\@Students);
  355:     my %ids;
  356:     foreach my $row (@$studentrows) {
  357:         my ($mysqlid,$student) = @$row;
  358:         $ids{$student}=$mysqlid;
  359:     }
  360:     foreach my $student (@Students) {
  361:         my $name_domain = $student->{'username'}.':'.$student->{'domain'};
  362:         $worksheet->write_row($rows_output++,0,
  363:                           [$student->{'fullname'},
  364:                            $student->{'username'},$student->{'domain'},
  365:                            $student->{'section'},$student->{'id'},
  366:                            $ids{$name_domain}]);
  367:     }
  368:     return $worksheet;
  369: }
  370: 
  371: #########################################################
  372: #########################################################
  373: ##
  374: ##      Radio Response Routines
  375: ##
  376: #########################################################
  377: #########################################################
  378: sub RadioResponseAnalysis {
  379:     my ($r,$problem,$problem_data,$Students) = @_;
  380:     my ($resource,$respid) = ($problem->{'resource'},
  381:                               $problem->{'respid'});
  382:     my $analysis_html;
  383:     my $PerformanceData = &Apache::loncoursedata::get_response_data
  384:         (\@Apache::lonstatistics::SelectedSections,
  385:          $Apache::lonstatistics::enrollment_status,
  386:          $resource->{'symb'},$respid);
  387:     if (! defined($PerformanceData) || 
  388:         ref($PerformanceData) ne 'ARRAY' ) {
  389:         $analysis_html = '<h2>'.
  390:             &mt('There is no submission data for this resource').
  391:             '</h2>';
  392:         $r->print($analysis_html);
  393:         return;
  394:     }
  395:     if (exists($ENV{'form.ExcelOutput'})) {
  396:         $analysis_html .= &RR_Excel_output($r,$problem->{'resource'},
  397:                                            $PerformanceData,$problem_data);
  398:     } elsif ($ENV{'form.AnalyzeOver'} eq 'Tries') {
  399:         $analysis_html .= &RR_Tries_Analysis($r,$problem->{'resource'},
  400:                                              $PerformanceData,$problem_data);
  401:     } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
  402:         $analysis_html .= &RR_Time_Analysis($r,$problem->{'resource'},
  403:                                             $PerformanceData,$problem_data);
  404:     } else {
  405:         $analysis_html .= '<h2>'.
  406:            &mt('The analysis you have selected is not supported at this time').
  407:            '</h2>';
  408:     }
  409:     $r->print($analysis_html);
  410: }
  411: 
  412: sub RR_Excel_output   { 
  413:     my ($r,$PerformanceData,$problem_data) = @_;
  414:     return '<h1>No!</h1>';
  415: }
  416: 
  417: sub RR_Tries_Analysis { 
  418:     my ($r,$resource,$PerformanceData,$problem_data) = @_;
  419:     my $analysis_html;
  420:     my $mintries = 1;
  421:     my $maxtries = $ENV{'form.NumPlots'};
  422:     my ($table,$Foils,$Concepts) = &build_foil_index($problem_data);
  423:     if ((! defined($Concepts)) || (@$Concepts < 2)) {
  424:         $table = '<h3>'.
  425:             &mt('Not enough data for concept analysis.  '.
  426:                 'Performing Foil Analysis').
  427:             '</h3>'.$table;
  428:     }
  429:     $analysis_html .= $table;
  430:     my @TryData = &RR_tries_data_analysis($r,$PerformanceData);
  431:         $analysis_html .= &RR_Tries_Foil_Analysis($mintries,$maxtries,$Foils,
  432:                                                  \@TryData,$problem_data);
  433:     return $analysis_html;
  434: }
  435: 
  436: sub RR_tries_data_analysis {
  437:     my ($r,$Attempt_data) = @_;
  438:     my @TryData;
  439:     foreach my $attempt (@$Attempt_data) {
  440:         my %Attempt = &hashify_attempt($attempt);
  441:         my ($answer,undef) = split('=',$Attempt{'submission'});
  442:         $TryData[$Attempt{'tries'}]->{$answer}++;
  443:     }
  444:     return @TryData;
  445: }
  446: 
  447: sub RR_Time_Analysis  { 
  448:     my ($r,$PerformanceData,$problem_data) = @_;
  449:     my $html;
  450:     return $html;
  451: }
  452: 
  453: sub RR_Tries_Foil_Analysis {
  454:     my ($min,$max,$Foils,$TryData,$problem_data) = @_;
  455:     my $html;
  456:     #
  457:     # Compute the data neccessary to make the plots
  458:     for (my $try=$min;$try<=$max;$try++) {
  459:         my @PlotData_Correct; 
  460:         my @PlotData_Incorrect;
  461:         next if ($try > scalar(@{$TryData}));
  462:         next if (! defined($TryData->[$try]));
  463:         my %DataSet = %{$TryData->[$try]};
  464:         my $total = 0;
  465:         foreach my $foilid (@$Foils) {
  466:             $total += $DataSet{$foilid};
  467:         }
  468:         foreach my $foilid (@$Foils) {
  469:             if ($total == 0) {
  470:                 push (@PlotData_Correct,0);
  471:                 push (@PlotData_Incorrect,0);
  472:             } else {
  473:                 if ($problem_data->{'_Foils'}->{$foilid}->{'value'} eq 'true') {
  474:                     push (@PlotData_Correct,
  475:                           int(100*$DataSet{$foilid}/$total));
  476:                     push (@PlotData_Incorrect,0);
  477:                 } else {
  478:                     push (@PlotData_Correct,0);
  479:                     push (@PlotData_Incorrect,
  480:                           int(100*$DataSet{$foilid}/$total));
  481:                 }
  482:             }
  483:         }
  484:         my $title='Attempt '.$try;
  485:         my $xlabel = $total.' Submissions';
  486:         $html.=  &Apache::loncommon::DrawBarGraph($title,
  487:                                                   $xlabel,
  488:                                                   'Percent Choosing',
  489:                                                   100,
  490:                                                   ['#33ff00','#ff3300'],
  491:                                                   undef,
  492:                                                   \@PlotData_Correct,
  493:                                                   \@PlotData_Incorrect);
  494:     }
  495:     return $html;
  496: }
  497: 
  498: sub RR_Tries_Concept_Analysis {
  499:     my ($min,$max,$Concepts,$ResponseData,$problem_data) = @_;
  500:     my $html;
  501:     return $html;
  502: }
  503: 
  504: sub RR_Time_Foil_Analysis {
  505:     my ($min,$max,$Foils,$ResponseData,$problem_data) = @_;
  506:     my $html;
  507:     return $html;
  508: }
  509: 
  510: sub RR_Time_Concept_Analysis {
  511:     my ($min,$max,$Concepts,$ResponseData,$problem_data) = @_;
  512:     my $html;
  513:     return $html;
  514: }
  515: 
  516: 
  517: sub get_Radio_problem_data {
  518:     my ($url) = @_;
  519:     my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
  520:     (my $garbage,$Answ)=split('_HASH_REF__',$Answ,2);
  521:     my %Answer = &Apache::lonnet::str2hash($Answ);
  522:     my %Partdata;
  523:     foreach my $part (@{$Answer{'parts'}}) {
  524:         while (my($key,$value) = each(%Answer)) {
  525: #            if (ref($value) eq 'ARRAY') {
  526: #                &Apache::lonnet::logthis('is ref part:'.$part.' '.$key.'='.join(',',@$value));
  527: #            } else {
  528: #                &Apache::lonnet::logthis('notref part:'.$part.' '.$key.'='.$value);
  529: #            }                
  530:             next if ($key !~ /^$part/);
  531:             $key =~ s/^$part\.//;
  532:             if ($key eq 'foils') {
  533:                 $Partdata{$part}->{'_Foils'}=$value;
  534:             } elsif ($key eq 'options') {
  535:                 $Partdata{$part}->{'_Options'}=$value;
  536:             } elsif ($key eq 'shown') {
  537:                 $Partdata{$part}->{'_Shown'}=$value;
  538:             } elsif ($key =~ /^foil.value.(.*)$/) {
  539:                 $Partdata{$part}->{$1}->{'value'}=$value;
  540:             } elsif ($key =~ /^foil.text.(.*)$/) {
  541:                 $Partdata{$part}->{$1}->{'text'}=$value;
  542:             }
  543:         }
  544:     }
  545:     return %Partdata;
  546: }
  547: 
  548: #########################################################
  549: #########################################################
  550: ##
  551: ##      Option Response Routines
  552: ##
  553: #########################################################
  554: #########################################################
  555: sub OptionResponseAnalysis {
  556:     my ($r,$problem,$problem_data,$Students) = @_;
  557:     my ($resource,$respid) = ($problem->{'resource'},
  558:                               $problem->{'respid'});
  559:     # Note: part data is not needed.
  560:     my $PerformanceData = &Apache::loncoursedata::get_response_data
  561:         (\@Apache::lonstatistics::SelectedSections,
  562:          $Apache::lonstatistics::enrollment_status,
  563:          $resource->{'symb'},$respid);
  564:     if (! defined($PerformanceData) || 
  565:         ref($PerformanceData) ne 'ARRAY' ) {
  566:         $r->print('<h2>'.
  567:                   &mt('There is no student data for this problem.').
  568:                   '</h2>');
  569:     }  else {
  570:         $r->rflush();
  571:         if (exists($ENV{'form.ExcelOutput'})) {
  572:             my $result = &OR_excel_sheet($r,$resource,
  573:                                          $PerformanceData,
  574:                                          $problem_data);
  575:             $r->print($result);
  576:             $r->rflush();
  577:         } else {
  578:             if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
  579:                 my $analysis_html = &OR_tries_analysis($r,
  580:                                                     $PerformanceData,
  581:                                                     $problem_data);
  582:                 $r->print($analysis_html);
  583:                 $r->rflush();
  584:             } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
  585:                 my $analysis_html = &OR_time_analysis($PerformanceData,
  586:                                                    $problem_data);
  587:                 $r->print($analysis_html);
  588:                 $r->rflush();
  589:             } else {
  590:                 $r->print('<h2>'.
  591:                           &mt('The analysis you have selected is '.
  592:                               'not supported at this time').
  593:                           '</h2>');
  594:             }
  595:         }
  596:     }
  597: }
  598: 
  599: #########################################################
  600: #
  601: #       Option Response:  Tries Analysis
  602: #
  603: #########################################################
  604: sub OR_tries_analysis {
  605:     my ($r,$PerformanceData,$ORdata) = @_;
  606:     my $mintries = 1;
  607:     my $maxtries = $ENV{'form.NumPlots'};
  608:     my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
  609:     my %response_data = &OR_analyze_by_tries($r,$PerformanceData,
  610:                                                      $mintries,$maxtries);
  611:     my $analysis = '';
  612:     #
  613:     # Compute the data necessary to make the plots
  614:     my @foil_plot; 
  615:     my @concept_data;
  616:     for (my $j=0;$j<=scalar(@$Concepts);$j++) {
  617:         my $concept = $Concepts->[$j];
  618:         foreach my $foilid (@{$concept->{'foils'}}) {
  619:             for (my $try=$mintries;$try<=$maxtries;$try++) {
  620:                 # concept analysis data
  621:                 $concept_data[$j]->{'_correct'} +=
  622:                     $response_data{$foilid}->[$try]->{'_correct'};
  623:                 $concept_data[$j]->{'_total'} +=
  624:                     $response_data{$foilid}->[$try]->{'_total'};
  625:                 #
  626:                 # foil analysis data
  627:                 if ($response_data{$foilid}->[$try]->{'_total'} == 0) {
  628:                     push(@{$foil_plot[$try]->{'_correct'}},0);
  629:                 } else {
  630:                     push(@{$foil_plot[$try]->{'_correct'}},
  631:                          100*$response_data{$foilid}->[$try]->{'_correct'}/
  632:                          $response_data{$foilid}->[$try]->{'_total'});
  633:                 }
  634:                 foreach my $option (@{$ORdata->{'_Options'}}) {
  635:                     push(@{$foil_plot[$try]->{'_total'}},
  636:                          $response_data{$foilid}->[$try]->{'_total'});
  637:                     if ($response_data{$foilid}->[$try]->{'_total'} == 0) {
  638:                         push (@{$foil_plot[$try]->{$option}},0);
  639:                     } else {
  640:                         if ($response_data{$foilid}->[$try]->{'_total'} ==
  641:                             $response_data{$foilid}->[$try]->{'_correct'}) {
  642:                             push(@{$foil_plot[$try]->{$option}},0);
  643:                         } else {
  644:                             push (@{$foil_plot[$try]->{$option}},
  645:                                   100 * 
  646:                                   $response_data{$foilid}->[$try]->{$option} / 
  647:                                   ($response_data{$foilid}->[$try]->{'_total'} 
  648:                                    - 
  649:                                    $response_data{$foilid}->[$try]->{'_correct'}
  650:                                    ));
  651:                         }
  652:                     }
  653:                 } # End of foreach my $option
  654:             }
  655:         } # End of foreach my $foilid
  656:     } # End of concept loops
  657:     # 
  658:     # Build a table for the plots
  659:     my $analysis_html = "<table>\n";
  660:     my $optionkey = &build_option_index($ORdata);
  661:     my $num_concepts = 1;
  662:     if (defined($Concepts)) { $num_concepts = scalar(@$Concepts); }
  663:     #
  664:     for (my $try=$mintries;$try<=$maxtries;$try++) {
  665:         my $concept_graph='';
  666:         if ($num_concepts > 1) {
  667:             #
  668:             # Create concept plot
  669:             my @concept_plot_data;
  670:             for (my $j=0;$j<=$#concept_data;$j++) {
  671:                 my $total = $concept_data[$j]->{'_total'};
  672:                 if ($total == 0) {
  673:                     $concept_plot_data[$j] = 0;
  674:                 } else {
  675:                     $concept_plot_data[$j] = 100 * 
  676:                         sprintf('%0.3f',
  677:                                 $concept_data[$j]->{'_correct'} / $total);
  678:                 }
  679:             }
  680:             #
  681:             my $title;
  682:             my $count = $response_data{'_total'}->[$try];
  683:             $title = 'Attempt '.$try.' (N='.$count.')';
  684:             $concept_graph = &Apache::loncommon::DrawBarGraph
  685:                 ($title,'Concept Number','Percent Correct',
  686:                  100,$plotcolors,undef,\@concept_plot_data);
  687:         }
  688:         #
  689:         # Create Foil Plots
  690:         my $count = $response_data{'_total'}->[$try];
  691:         my $title = 'Attempt '.$try.' (N='.$count.')';
  692:         my @Datasets;
  693:         foreach my $option ('_correct',@{$ORdata->{'_Options'}}) {
  694:             next if (! exists($foil_plot[$try]->{$option}));
  695:             push(@Datasets,$foil_plot[$try]->{$option});
  696:         }
  697:         #
  698:         # Put a blank in the data set between concepts
  699:         for (my $set =0;$set<=$#Datasets;$set++) {
  700:             my @Data = @{$Datasets[$set]};
  701:             my $idx = 0;
  702:             foreach my $concept (@{$Concepts}) {
  703:                 foreach my $foilid (@{$concept->{'foils'}}) {
  704:                     $Datasets[$set]->[$idx++]=shift(@Data);
  705:                 }
  706:                 if ($concept->{'name'} ne $Concepts->[-1]->{'name'}) {
  707:                     $Datasets[$set]->[$idx++] = 0;
  708:                 }
  709:             }
  710:         }
  711:         #
  712:         # Set up the labels needed for the bar graph
  713:         my @Labels;
  714:         my $idx = 1;
  715:         foreach my $concept (@{$Concepts}) {
  716:             foreach my $foilid (@{$concept->{'foils'}}) {
  717:                 push(@Labels,$idx++);
  718:             }
  719:             push(@Labels,'');
  720:         }
  721:         #
  722:         my $correct_graph = &Apache::loncommon::DrawBarGraph
  723:             ($title,'Foil Number','Percent Correct',
  724:              100,$plotcolors,\@Labels,$Datasets[0]);
  725:         
  726:         #
  727:         #
  728:         next if (! defined($Datasets[0]));
  729:         for (my $i=0; $i< scalar(@{$Datasets[0]});$i++) {
  730:             $Datasets[0]->[$i]=0;
  731:         }
  732:         $count = $response_data{'_total'}->[$try] - 
  733:                                            $response_data{'_correct'}->[$try];
  734:         $title = 'Attempt '.$try.' (N='.$count.')';
  735:         my $incorrect_graph = &Apache::loncommon::DrawBarGraph
  736:             ($title,'Foil Number','% Option Chosen Incorrectly',
  737:              100,$plotcolors,\@Labels,@Datasets);
  738:         $analysis_html.= 
  739:             '<tr>'.
  740:             '<td>'.$concept_graph.'</td>'.
  741:             '<td>'.$correct_graph.'</td>'.
  742:             '<td>'.$incorrect_graph.'</td>'.
  743:             '<td>'.$optionkey.'<td>'.
  744:             '</tr>'.$/;
  745:     }
  746:     $analysis_html .= "</table>\n";
  747:     $table .= $analysis_html;
  748:     return $table;
  749: }
  750: 
  751: sub OR_analyze_by_tries {
  752:     my ($r,$PerformanceData,$mintries,$maxtries) = @_;
  753:     my %Trydata;
  754:     $mintries = 1         if (! defined($mintries) || $mintries < 1);
  755:     $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
  756:     foreach my $row (@$PerformanceData) {
  757:         next if (! defined($row));
  758:         my $tries = &get_tries_from_row($row);
  759:         my %Row   = &Process_OR_Row($row);
  760:         next if (! %Row);
  761:         while (my ($foilid,$href) = each(%Row)) {
  762:             if (! ref($href)) { 
  763:                 $Trydata{$foilid}->[$tries] += $href;
  764:                 next;
  765:             }
  766:             while (my ($option,$value) = each(%$href)) {
  767:                 $Trydata{$foilid}->[$tries]->{$option}+=$value;
  768:             }
  769:         }
  770:     }
  771:     return %Trydata;
  772: }
  773: 
  774: #########################################################
  775: #
  776: #     Option Response: Time Analysis
  777: #
  778: #########################################################
  779: sub OR_time_analysis {
  780:     my ($performance_data,$ORdata) = @_;
  781:     my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
  782:     my $foilkey = &build_option_index($ORdata);
  783:     my $num_concepts = 1;
  784:     if (defined($Concepts)) { $num_concepts = scalar(@$Concepts); }
  785:     #
  786:     if ($num_concepts < 2) {
  787:         $table = '<h3>'.
  788:             &mt('Not enough data for concept analysis.  '.
  789:                 'Performing Foil Analysis').
  790:                 '</h3>'.$table;
  791:     }
  792:     #
  793:     my $num_plots = $ENV{'form.NumPlots'};
  794:     my $num_data = scalar(@$performance_data)-1;
  795:     my $percent = sprintf('%2f',100/$num_plots);
  796:     #
  797:     $table .= "<table>\n";
  798:     for (my $i=0;$i<$num_plots;$i++) {
  799:         ##
  800:         my $starttime = &Apache::lonhtmlcommon::get_date_from_form
  801:             ('startdate_'.$i);
  802:         my $endtime = &Apache::lonhtmlcommon::get_date_from_form
  803:             ('enddate_'.$i);
  804:         if (! defined($starttime) || ! defined($endtime)) {
  805:             my $sec_in_day = 86400;
  806:             my $last_sub_time = &get_time_from_row($performance_data->[-1]);
  807:             my ($sday,$smon,$syear);
  808:             (undef,undef,undef,$sday,$smon,$syear) = 
  809:                 localtime($last_sub_time - $sec_in_day*$i);
  810:             $starttime = &Time::Local::timelocal(0,0,0,$sday,$smon,$syear);
  811:             $endtime = $starttime + $sec_in_day;
  812:             if ($i == ($num_plots -1 )) {
  813:                 $starttime = &get_time_from_row($performance_data->[0]);
  814:             }
  815:         }
  816:         $table .= '<tr><td colspan="4" align="center">'.
  817:             &mt('Data from [_1] to [_2]',
  818:                 &Apache::lonlocal::locallocaltime($starttime),
  819:                 &Apache::lonlocal::locallocaltime($endtime)).'</td></tr>'.$/;
  820:         my $startdateform = &Apache::lonhtmlcommon::date_setter
  821:             ('Statistics','startdate_'.$i,$starttime);
  822:         my $enddateform = &Apache::lonhtmlcommon::date_setter
  823:             ('Statistics','enddate_'.$i,$endtime);
  824:         #
  825:         my $begin_index;
  826:         my $end_index;
  827:         my $j;
  828:         while (++$j < scalar(@$performance_data)) {
  829:             last if (&get_time_from_row($performance_data->[$j]) 
  830:                                                               > $starttime);
  831:         }
  832:         $begin_index = $j;
  833:         while (++$j < scalar(@$performance_data)) {
  834:             last if (&get_time_from_row($performance_data->[$j]) > $endtime);
  835:         }
  836:         $end_index = $j;
  837:         ##
  838:         my $interval = {
  839:             begin_index => $begin_index,
  840:             end_index   => $end_index,
  841:             startdateform => $startdateform,
  842:             enddateform   => $enddateform,
  843:         };
  844:         my $concept_correct_plot = '';
  845:         if ($num_concepts > 1) {
  846:             $concept_correct_plot = 
  847:                 &OR_Concept_Time_Analysis($performance_data,
  848:                                           $ORdata,$Concepts,
  849:                                           $interval);
  850:         }
  851:         my ($foil_correct_plot,$foil_incorrect_plot) = 
  852:             &OR_Foil_Time_Analysis($performance_data,$ORdata,
  853:                                    $Foils,$Concepts,$interval);
  854:         $table .= '<tr>'.
  855:             '<td>'.$concept_correct_plot.'</td>'.
  856:             '<td>'.$foil_correct_plot.'</td>'.
  857:             '<td>'.$foil_incorrect_plot.'</td>'.
  858:             '<td align="left" valign="top">'.$foilkey.'</td></tr>'.$/;
  859:         $table .= '<tr><td colspan="4" align="center">'.
  860:             &mt('Start time: [_1]',
  861:                 $interval->{'startdateform'}).'<br />'.
  862:             &mt('End time: [_1]',
  863:                 $interval->{'enddateform'}).
  864:                 '</td></tr>'.$/;
  865:         $table.= '<tr><td colspan="4">&nbsp</td></tr>'.$/;
  866:     }
  867:     $table .= '</table>';
  868:     #
  869:     return $table;
  870: }
  871: 
  872: sub OR_Foil_Time_Analysis {
  873:     my ($performance_data,$ORdata,$Foils,$Concepts,$interval) = @_;
  874:     my $analysis_html;
  875:     my ($begin_index,$end_index) = ($interval->{'begin_index'},
  876:                                     $interval->{'end_index'});
  877:     my %TimeData;
  878:     #
  879:     # Compute the number getting the foils correct or incorrects
  880:     for (my $j=$begin_index;$j<=$end_index;$j++) {
  881:         my $row = $performance_data->[$j];
  882:         next if (! defined($row));
  883:         my %Row = &Process_OR_Row($row);
  884:         while (my ($foilid,$href) = each(%Row)) {
  885:             if (! ref($href)) {
  886:                 $TimeData{$foilid} += $href;
  887:                 next;
  888:             }
  889:             while (my ($option,$value) = each(%$href)) {
  890:                 $TimeData{$foilid}->{$option}+=$value;
  891:             }
  892:         }
  893:     }
  894:     my @plotdata;
  895:     my @labels;
  896:     foreach my $concept (@{$Concepts}) {
  897:         foreach my $foil (@{$concept->{'foils'}}) {
  898:             push(@labels,scalar(@labels)+1);
  899:             my $total = $TimeData{$foil}->{'_total'};
  900:             if ($total == 0) {
  901:                 push(@{$plotdata[0]},0);
  902:             } else {
  903:                 push(@{$plotdata[0]},
  904:                      100 * $TimeData{$foil}->{'_correct'} / $total);
  905:             }
  906:             my $total_incorrect = $total - $TimeData{$foil}->{'_correct'};
  907:             for (my $i=0;$i<scalar(@{$ORdata->{'_Options'}});$i++) {
  908:                 my $option = $ORdata->{'_Options'}->[$i];
  909:                 if ($total_incorrect == 0) {
  910:                     push(@{$plotdata[$i+1]},0);
  911:                 } else {
  912:                     push(@{$plotdata[$i+1]},
  913:                          100 * $TimeData{$foil}->{$option} / $total_incorrect);
  914:                 }
  915:             }
  916:         }
  917:         # Put in a blank one
  918:         push(@labels,'');
  919:         push(@{$plotdata[0]},0);
  920:         for (my $i=0;$i<scalar(@{$ORdata->{'_Options'}});$i++) {
  921:             push(@{$plotdata[$i+1]},0);
  922:         }
  923:     }
  924:     #
  925:     # Create the plot
  926:     my $count = $end_index-$begin_index;
  927:     my $title;
  928:     if ($count == 0) {
  929:         $title = 'no submissions';
  930:     } elsif ($count == 1) {
  931:         $title = 'one submission';
  932:     } else {
  933:         $title = $count.' submissions';
  934:     }
  935:     my $correct_plot = &Apache::loncommon::DrawBarGraph($title,
  936:                                                        'Foil Number',
  937:                                                        'Percent Correct',
  938:                                                        100,
  939:                                                        $plotcolors,
  940:                                                        undef,
  941:                                                        $plotdata[0]);
  942:     for (my $j=0; $j< scalar(@{$plotdata[0]});$j++) {
  943:         $plotdata[0]->[$j]=0;
  944:     }
  945:     $count = $end_index-$begin_index-$TimeData{'_correct'};
  946:     if ($count == 0) {
  947:         $title = 'no submissions';
  948:     } elsif ($count == 1) {
  949:         $title = 'one submission';
  950:     } else {
  951:         $title = $count.' submissions';
  952:     }
  953:     my $incorrect_plot = &Apache::loncommon::DrawBarGraph($title,
  954:                                                  'Foil Number',
  955:                                                  'Incorrect Option Choice',
  956:                                                  100,
  957:                                                  $plotcolors,
  958:                                                  undef,
  959:                                                  @plotdata);        
  960: 
  961:     return ($correct_plot,$incorrect_plot);
  962: }
  963: 
  964: sub OR_Concept_Time_Analysis {
  965:     my ($performance_data,$ORdata,$Concepts,$interval) = @_;
  966:     ##
  967:     ## Determine starttime, endtime, startindex, endindex
  968:     my ($begin_index,$end_index) = ($interval->{'begin_index'},
  969:                                     $interval->{'end_index'});
  970:     my %TimeData;
  971:     #
  972:     # Compute the number getting the foils correct or incorrects
  973:     for (my $j=$begin_index;$j<=$end_index;$j++) {
  974:         my $row = $performance_data->[$j];
  975:         next if (! defined($row));
  976:         my %Row = &Process_OR_Row($row);
  977:         while (my ($foilid,$href) = each(%Row)) {
  978:             if (! ref($href)) {
  979:                 $TimeData{$foilid} += $href;
  980:                 next;
  981:             }
  982:             while (my ($option,$value) = each(%$href)) {
  983:                 $TimeData{$foilid}->{$option}+=$value;
  984:             }
  985:         }
  986:     }
  987:     #
  988:     # Put the data in plottable form
  989:     my @plotdata;
  990:     foreach my $concept (@$Concepts) {
  991:         my ($total,$correct);
  992:         foreach my $foil (@{$concept->{'foils'}}) {
  993:             $total += $TimeData{$foil}->{'_total'};
  994:             $correct += $TimeData{$foil}->{'_correct'};
  995:         }
  996:         if ($total == 0) {
  997:             push(@plotdata,0);
  998:         } else {
  999:             push(@plotdata,100 * $correct / $total);
 1000:         }
 1001:     }
 1002:     #
 1003:     # Create the plot
 1004:     my $title = ($end_index - $begin_index).' submissions';
 1005:     return &Apache::loncommon::DrawBarGraph($title,
 1006:                                             'Concept Number',
 1007:                                             'Percent Correct',
 1008:                                             100,
 1009:                                             $plotcolors,
 1010:                                             undef,
 1011:                                             \@plotdata);
 1012: }
 1013: 
 1014: #########################################################
 1015: #########################################################
 1016: ##
 1017: ##             Excel output 
 1018: ##
 1019: #########################################################
 1020: #########################################################
 1021: sub OR_excel_sheet {
 1022:     my ($r,$resource,$performance_data,$ORdata) = @_;
 1023:     my $response = '';
 1024:     my (undef,$Foils,$Concepts) = &build_foil_index($ORdata);
 1025:     #
 1026:     # Create excel worksheet
 1027:     my $filename = '/prtspool/'.
 1028:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
 1029:         time.'_'.rand(1000000000).'.xls';
 1030:     my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
 1031:     if (! defined($workbook)) {
 1032:         $r->log_error("Error creating excel spreadsheet $filename: $!");
 1033:         $r->print('<p>'.&mt("Unable to create new Excel file.  ".
 1034:                             "This error has been logged.  ".
 1035:                             "Please alert your LON-CAPA administrator").
 1036:                   '</p>');
 1037:         return undef;
 1038:     }
 1039:     #
 1040:     $workbook->set_tempdir('/home/httpd/perl/tmp');
 1041:     my $format = &Apache::loncommon::define_excel_formats($workbook);
 1042:     #
 1043:     # Create and populate main worksheets
 1044:     my $problem_data_sheet  = $workbook->addworksheet('Problem Data');
 1045:     my $student_data_sheet = &build_student_data_worksheet($workbook,$format);
 1046:     my $response_data_sheet = $workbook->addworksheet('Response Data');
 1047:     foreach my $sheet ($problem_data_sheet,$student_data_sheet,
 1048:                        $response_data_sheet) {
 1049:         $sheet->write(0,0,$resource->{'title'},$format->{'h2'});
 1050:         $sheet->write(1,0,$resource->{'src'},$format->{'h3'});
 1051:     }
 1052:     #
 1053:     my $result;
 1054:     $result = &OR_build_problem_data_worksheet($problem_data_sheet,$format,
 1055:                                             $Concepts,$ORdata);
 1056:     if ($result ne 'okay') {
 1057:         # Do something useful
 1058:     }
 1059:     $result = &OR_build_response_data_worksheet($response_data_sheet,$format,
 1060:                                              $performance_data,$Foils,
 1061:                                              $ORdata);
 1062:     if ($result ne 'okay') {
 1063:         # Do something useful
 1064:     }
 1065:     $response_data_sheet->activate();
 1066:     #
 1067:     # Close the excel file
 1068:     $workbook->close();
 1069:     #
 1070:     # Write a link to allow them to download it
 1071:     $result .= '<h2>'.&mt('Excel Raw Data Output').'</h2>'.
 1072:               '<p><a href="'.$filename.'">'.
 1073:               &mt('Your Excel spreadsheet.').
 1074:               '</a></p>'."\n";
 1075:     return $result;
 1076: }
 1077: 
 1078: sub OR_build_problem_data_worksheet {
 1079:     my ($worksheet,$format,$Concepts,$ORdata) = @_;
 1080:     my $rows_output = 3;
 1081:     my $cols_output = 0;
 1082:     $worksheet->write($rows_output++,0,'Problem Structure',$format->{'h3'});
 1083:     ##
 1084:     ##
 1085:     my @Headers;
 1086:     if (@$Concepts > 1) {
 1087:         @Headers = ("Concept\nNumber",'Concept',"Foil\nNumber",
 1088:                     'Foil Name','Foil Text','Correct value');
 1089:     } else {
 1090:         @Headers = ('Foil Number','FoilName','Foil Text','Correct value');
 1091:     }
 1092:     $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
 1093:     my %Foildata = %{$ORdata->{'_Foils'}};
 1094:     my $conceptindex = 1;
 1095:     my $foilindex = 1;
 1096:     foreach my $concept (@$Concepts) {
 1097:         my @FoilsInConcept = @{$concept->{'foils'}};
 1098:         my $firstfoil = shift(@FoilsInConcept);
 1099:         if (@$Concepts > 1) {
 1100:             $worksheet->write_row($rows_output++,0,
 1101:                                   [$conceptindex,
 1102:                                    $concept->{'name'},
 1103:                                    $foilindex++,
 1104:                                    $Foildata{$firstfoil}->{'name'},
 1105:                                    $Foildata{$firstfoil}->{'text'},
 1106:                                    $Foildata{$firstfoil}->{'value'},]);
 1107:         } else {
 1108:             $worksheet->write_row($rows_output++,0,
 1109:                                   [ $foilindex++,
 1110:                                     $Foildata{$firstfoil}->{'name'},
 1111:                                     $Foildata{$firstfoil}->{'text'},
 1112:                                     $Foildata{$firstfoil}->{'value'},]);
 1113:         }
 1114:         foreach my $foilid (@FoilsInConcept) {
 1115:             if (@$Concepts > 1) {
 1116:                 $worksheet->write_row($rows_output++,0,
 1117:                                       ['',
 1118:                                        '',
 1119:                                        $foilindex,
 1120:                                        $Foildata{$foilid}->{'name'},
 1121:                                        $Foildata{$foilid}->{'text'},
 1122:                                        $Foildata{$foilid}->{'value'},]);
 1123:             } else {
 1124:                 $worksheet->write_row($rows_output++,0,                
 1125:                                       [$foilindex,
 1126:                                        $Foildata{$foilid}->{'name'},
 1127:                                        $Foildata{$foilid}->{'text'},
 1128:                                        $Foildata{$foilid}->{'value'},]);
 1129:             }                
 1130:         } continue {
 1131:             $foilindex++;
 1132:         }
 1133:     } continue {
 1134:         $conceptindex++;
 1135:     }
 1136:     $rows_output++;
 1137:     $rows_output++;
 1138:     ##
 1139:     ## Option data output
 1140:     $worksheet->write($rows_output++,0,'Options',$format->{'header'});
 1141:     foreach my $string (@{$ORdata->{'_Options'}}) {
 1142:         $worksheet->write($rows_output++,0,$string);
 1143:     }
 1144:     return 'okay';
 1145: }
 1146: 
 1147: sub OR_build_response_data_worksheet {
 1148:     my ($worksheet,$format,$performance_data,$Foils,$ORdata)=@_;
 1149:     my $rows_output = 3;
 1150:     my $cols_output = 0;
 1151:     $worksheet->write($rows_output++,0,'Response Data',$format->{'h3'});
 1152:     $worksheet->set_column(1,1,20);
 1153:     $worksheet->set_column(2,2,13);
 1154:     my @Headers = ('identifier','time','award detail','attempt');
 1155:     foreach my $foil (@$Foils) {
 1156:         push (@Headers,$foil.' submission');
 1157:         push (@Headers,$foil.' grading');
 1158:     }
 1159:     $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
 1160:     #
 1161:     foreach my $row (@$performance_data) {
 1162:         next if (! defined($row));
 1163:         my ($student,$award,$grading,$submission,$time,$tries) = @$row;
 1164:         my @Foilgrades = split('&',$grading);
 1165:         my @Foilsubs   = split('&',$submission);
 1166:         my %response_data;
 1167:         for (my $j=0;$j<=$#Foilgrades;$j++) {
 1168:             my ($foilid,$correct)  = split('=',$Foilgrades[$j]);
 1169:             my (undef,$submission) = split('=',$Foilsubs[$j]);
 1170:             $submission = &Apache::lonnet::unescape($submission);
 1171:             $response_data{$foilid.' submission'}=$submission;
 1172:             $response_data{$foilid.' award'}=$correct;
 1173:         }
 1174:         $worksheet->write($rows_output,$cols_output++,$student);
 1175:         $worksheet->write($rows_output,$cols_output++,
 1176:              &Apache::lonstathelpers::calc_serial($time),$format->{'date'});
 1177:         $worksheet->write($rows_output,$cols_output++,$award);
 1178:         $worksheet->write($rows_output,$cols_output++,$tries);
 1179:         foreach my $foilid (@$Foils) {
 1180:             $worksheet->write($rows_output,$cols_output++,
 1181:                               $response_data{$foilid.' submission'});
 1182:             $worksheet->write($rows_output,$cols_output++,
 1183:                               $response_data{$foilid.' award'});
 1184:         }
 1185:         $rows_output++;
 1186:         $cols_output = 0;
 1187:     }
 1188:     return;
 1189: }
 1190: 
 1191: sub build_foil_index {
 1192:     my ($ORdata) = @_;
 1193:     return if (! exists($ORdata->{'_Foils'}));
 1194:     my %Foildata = %{$ORdata->{'_Foils'}};
 1195:     my @Foils = sort(keys(%Foildata));
 1196:     my %Concepts;
 1197:     foreach my $foilid (@Foils) {
 1198:         push(@{$Concepts{$Foildata{$foilid}->{'_Concept'}}},
 1199:              $foilid);
 1200:     }
 1201:     undef(@Foils);
 1202:     # Having gathered the concept information in a hash, we now translate it
 1203:     # into an array because we need to be consistent about order.
 1204:     # Also put the foils in order, too.
 1205:     my $sortfunction = sub {
 1206:         my %Numbers = (one   => 1,
 1207:                        two   => 2,
 1208:                        three => 3,
 1209:                        four  => 4,
 1210:                        five  => 5,
 1211:                        six   => 6,
 1212:                        seven => 7,
 1213:                        eight => 8,
 1214:                        nine  => 9,
 1215:                        ten   => 10,);
 1216:         my $a1 = lc($a); 
 1217:         my $b1 = lc($b);
 1218:         if (exists($Numbers{$a1})) {
 1219:             $a1 = $Numbers{$a1};
 1220:         }
 1221:         if (exists($Numbers{$b1})) {
 1222:             $b1 = $Numbers{$b1};
 1223:         }
 1224:         if (($a1 =~/^\d+$/) && ($b1 =~/^\d+$/)) {
 1225:             return $a1 <=> $b1;
 1226:         } else {
 1227:             return $a1 cmp $b1;
 1228:         }
 1229:     };
 1230:     my @Concepts;
 1231:     foreach my $concept (sort $sortfunction (keys(%Concepts))) {
 1232:         if (! defined($Concepts{$concept})) {
 1233:             $Concepts{$concept}=[];
 1234: #            next;
 1235:         }
 1236:         push(@Concepts,{ name => $concept,
 1237:                         foils => [@{$Concepts{$concept}}]});
 1238:         push(@Foils,(@{$Concepts{$concept}}));
 1239:     }
 1240:     #
 1241:     # Build up the table of row labels.
 1242:     my $table = '<table border="1" >'."\n";
 1243:     if (@Concepts > 1) {
 1244:         $table .= '<tr>'.
 1245:             '<th>'.&mt('Concept Number').'</th>'.
 1246:             '<th>'.&mt('Concept').'</th>'.
 1247:             '<th>'.&mt('Foil Number').'</th>'.
 1248:             '<th>'.&mt('Foil Name').'</th>'.
 1249:             '<th>'.&mt('Foil Text').'</th>'.
 1250:             '<th>'.&mt('Correct Value').'</th>'.
 1251:             "</tr>\n";
 1252:     } else {
 1253:         $table .= '<tr>'.
 1254:             '<th>'.&mt('Foil Number').'</th>'.
 1255:             '<th>'.&mt('Foil Name').'</th>'.
 1256:             '<th>'.&mt('Foil Text').'</th>'.
 1257:             '<th>'.&mt('Correct Value').'</th>'.
 1258:             "</tr>\n";
 1259:     }        
 1260:     my $conceptindex = 1;
 1261:     my $foilindex = 1;
 1262:     foreach my $concept (@Concepts) {
 1263:         my @FoilsInConcept = @{$concept->{'foils'}};
 1264:         my $firstfoil = shift(@FoilsInConcept);
 1265:         if (@Concepts > 1) {
 1266:             $table .= '<tr>'.
 1267:                 '<td>'.$conceptindex.'</td>'.
 1268:                 '<td>'.&HTML::Entities::encode($concept->{'name'},'<>&"').'</td>'.
 1269:                 '<td>'.$foilindex++.'</td>'.
 1270:                 '<td>'.&HTML::Entities::encode($Foildata{$firstfoil}->{'name'},'<>&"').'</td>'.
 1271:                 '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
 1272:                 '<td>'.&HTML::Entities::encode($Foildata{$firstfoil}->{'value'},'<>&"').'</td>'.
 1273:                 "</tr>\n";
 1274:         } else {
 1275:             $table .= '<tr>'.
 1276:                 '<td>'.$foilindex++.'</td>'.
 1277:                 '<td>'.&HTML::Entities::encode($Foildata{$firstfoil}->{'name'},'<>&"').'</td>'.
 1278:                 '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
 1279:                 '<td>'.&HTML::Entities::encode($Foildata{$firstfoil}->{'value'},'<>&"').'</td>'.
 1280:                 "</tr>\n";
 1281:         }
 1282:         foreach my $foilid (@FoilsInConcept) {
 1283:             if (@Concepts > 1) {
 1284:                 $table .= '<tr>'.
 1285:                     '<td></td>'.
 1286:                     '<td></td>'.
 1287:                     '<td>'.$foilindex.'</td>'.
 1288:                     '<td>'.&HTML::Entities::encode($Foildata{$foilid}->{'name'},'<>&"').'</td>'.
 1289:                     '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
 1290:                     '<td>'.&HTML::Entities::encode($Foildata{$foilid}->{'value'},'<>&"').'</td>'.
 1291:                     "</tr>\n";
 1292:             } else {
 1293:                 $table .= '<tr>'.
 1294:                     '<td>'.$foilindex.'</td>'.
 1295:                     '<td>'.&HTML::Entities::encode($Foildata{$foilid}->{'name'},'<>&"').'</td>'.
 1296:                     '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
 1297:                     '<td>'.&HTML::Entities::encode($Foildata{$foilid}->{'value'},'<>&"').'</td>'.
 1298:                     "</tr>\n";
 1299:             }                
 1300:         } continue {
 1301:             $foilindex++;
 1302:         }
 1303:     } continue {
 1304:         $conceptindex++;
 1305:     }
 1306:     $table .= "</table>\n";
 1307:     #
 1308:     # Build option index with color stuff
 1309:     return ($table,\@Foils,\@Concepts);
 1310: }
 1311: 
 1312: sub build_option_index {
 1313:     my ($ORdata)= @_;
 1314:     my $table = "<table>\n";
 1315:     my $optionindex = 0;
 1316:     my @Rows;
 1317:     foreach my $option (&mt('correct option chosen'),@{$ORdata->{'_Options'}}) {
 1318:         push (@Rows,
 1319:               '<tr>'.
 1320:               '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
 1321:               ('&nbsp;'x4).'</td>'.
 1322:               '<td>'.&HTML::Entities::encode($option,'<>&"').'</td>'.
 1323:               "</tr>\n");
 1324:     }
 1325:     shift(@Rows); # Throw away 'correct option chosen' color
 1326:     $table .= join('',reverse(@Rows));
 1327:     $table .= "</table>\n";
 1328: }
 1329: 
 1330: #########################################################
 1331: #########################################################
 1332: ##
 1333: ##   Generic Interface Routines
 1334: ##
 1335: #########################################################
 1336: #########################################################
 1337: sub CreateInterface {
 1338:     ##
 1339:     ## Environment variable initialization
 1340:     if (! exists$ENV{'form.AnalyzeOver'}) {
 1341:         $ENV{'form.AnalyzeOver'} = 'Tries';
 1342:     }
 1343:     ##
 1344:     ## Build the menu
 1345:     my $Str = '';
 1346:     $Str .= &Apache::lonhtmlcommon::breadcrumbs
 1347:         (undef,'Detailed Problem Analysis');
 1348:     $Str .= '<table cellspacing="5">'."\n";
 1349:     $Str .= '<tr>';
 1350:     $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
 1351:     $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
 1352: #    $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
 1353:     $Str .= '<td align="center">&nbsp;</td>';
 1354:     $Str .= '</tr>'."\n";
 1355:     ##
 1356:     ## 
 1357:     $Str .= '<tr><td align="center">'."\n";
 1358:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
 1359:     $Str .= '</td>';
 1360:     #
 1361:     $Str .= '<td align="center">';
 1362:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
 1363:     $Str .= '</td>';
 1364:     #
 1365: #    $Str .= '<td align="center">';
 1366:     my $only_seq_with_assessments = sub { 
 1367:         my $s=shift;
 1368:         if ($s->{'num_assess'} < 1) { 
 1369:             return 0;
 1370:         } else { 
 1371:             return 1;
 1372:         }
 1373:     };
 1374:     &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
 1375:                                               $only_seq_with_assessments);
 1376:     ##
 1377:     ##
 1378:     $Str .= '<td>';
 1379:     { # These braces are here to organize the code, not scope it.
 1380:         {
 1381:             $Str .= '<nobr>'.&mt('Analyze Over ');
 1382:             $Str .= &Apache::loncommon::help_open_topic
 1383:                                                   ('Analysis_Analyze_Over');
 1384:             $Str .='<select name="AnalyzeOver" >';
 1385:             $Str .= '<option value="Tries" ';
 1386:             if (! exists($ENV{'form.AnalyzeOver'}) || 
 1387:                 $ENV{'form.AnalyzeOver'} eq 'Tries'){
 1388:                 # Default to Tries
 1389:                 $Str .= ' selected ';
 1390:             }
 1391:             $Str .= '>'.&mt('Tries').'</option>';
 1392:             $Str .= '<option value="Time" ';
 1393:             $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
 1394:             $Str .= '>'.&mt('Time').'</option>';
 1395:             $Str .= '</select>';
 1396:             $Str .= '</nobr><br />';
 1397:         }
 1398:         {
 1399:             $Str .= '<nobr>'.&mt('Number of Plots:');
 1400:             $Str .= &Apache::loncommon::help_open_topic
 1401:                                                   ('Analysis_num_plots');
 1402:             $Str .= '<select name="NumPlots">';
 1403:             if (! exists($ENV{'form.NumPlots'}) 
 1404:                 || $ENV{'form.NumPlots'} < 1 
 1405:                 || $ENV{'form.NumPlots'} > 20) {
 1406:                 $ENV{'form.NumPlots'} = 5;
 1407:             }
 1408:             foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
 1409:                 $Str .= '<option value="'.$i.'" ';
 1410:                 if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
 1411:                 $Str .= '>'.$i.'</option>';
 1412:             }
 1413:             $Str .= '</select></nobr><br />';
 1414:         }
 1415:         {
 1416:             $Str .= '<nobr>'.&mt('Status: [_1]',
 1417:                                  '<input type="text" '.
 1418:                                  'name="stats_status" size="60" value="" />'
 1419:                                  ).
 1420:                     '</nobr><br />';
 1421:         }
 1422:     }
 1423:     $Str .= '</td>';
 1424:     ##
 1425:     ##
 1426:     $Str .= '</tr>'."\n";
 1427:     $Str .= '</table>'."\n";
 1428:     return $Str;
 1429: }
 1430: 
 1431: #########################################################
 1432: #########################################################
 1433: ##
 1434: ##              Misc Option Response functions
 1435: ##
 1436: #########################################################
 1437: #########################################################
 1438: sub get_time_from_row {
 1439:     my ($row) = @_;
 1440:     if (ref($row)) {
 1441:         return $row->[&Apache::loncoursedata::RD_timestamp()];
 1442:     } 
 1443:     return undef;
 1444: }
 1445: 
 1446: sub get_tries_from_row {
 1447:     my ($row) = @_;
 1448:     if (ref($row)) {
 1449:         return $row->[&Apache::loncoursedata::RD_tries()];
 1450:     }
 1451:     return undef;
 1452: }
 1453: 
 1454: sub hashify_attempt {
 1455:     my ($row) = @_;
 1456:     my %attempt;
 1457:     $attempt{'tries'}      = $row->[&Apache::loncoursedata::RD_tries()];
 1458:     $attempt{'submission'} = $row->[&Apache::loncoursedata::RD_submission()];
 1459:     $attempt{'award'}      = $row->[&Apache::loncoursedata::RD_awarddetail()];
 1460:     $attempt{'timestamp'}  = $row->[&Apache::loncoursedata::RD_timestamp()];
 1461:     return %attempt;
 1462: }
 1463: 
 1464: sub Process_OR_Row {
 1465:     my ($row) = @_;
 1466:     my %RowData;
 1467:     my $student_id = $row->[&Apache::loncoursedata::RD_student_id()];
 1468:     my $award      = $row->[&Apache::loncoursedata::RD_awarddetail()];
 1469:     my $grading    = $row->[&Apache::loncoursedata::RD_response_eval()];
 1470:     my $submission = $row->[&Apache::loncoursedata::RD_submission()];
 1471:     my $time       = $row->[&Apache::loncoursedata::RD_timestamp()];
 1472:     my $tries      = $row->[&Apache::loncoursedata::RD_tries()];
 1473:     return undef if ($award eq 'MISSING_ANSWER');
 1474:     if ($award =~ /(APPROX_ANS|EXACT_ANS)/) {
 1475:         $RowData{'_correct'} = 1;
 1476:     }
 1477:     $RowData{'_total'} = 1;
 1478:     my @Foilgrades = split('&',$grading);
 1479:     my @Foilsubs   = split('&',$submission);
 1480:     for (my $j=0;$j<=$#Foilgrades;$j++) {
 1481:         my ($foilid,$correct)  = split('=',$Foilgrades[$j]);
 1482:         $foilid = &Apache::lonnet::unescape($foilid);
 1483:         my (undef,$submission) = split('=',$Foilsubs[$j]);
 1484:         if ($correct) {
 1485:             $RowData{$foilid}->{'_correct'}++;
 1486:         } else {
 1487:             $submission = &Apache::lonnet::unescape($submission);
 1488:             $RowData{$foilid}->{$submission}++;
 1489:         }
 1490:         $RowData{$foilid}->{'_total'}++;
 1491:     }
 1492:     return %RowData;
 1493: }
 1494: 
 1495: 1;
 1496: 
 1497: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>