File:  [LON-CAPA] / loncom / interface / statistics / lonproblemanalysis.pm
Revision 1.39: download - view: text, annotated - select for diffs
Wed Oct 15 21:30:51 2003 UTC (20 years, 8 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Monstrous rewrite to, on foil analysis, output graphs showing how the
students got the foil incorrect.  As a side effect there will no longer
be any big green bars on the top of the bar chart in concept analysis
mode.  The case of one concept is handled more cleanly.
Only one column of graphs in foil analysis mode as the key needs to be
repeated.
Skip 'MISSING_ANSWER' graded submissions because they will really screw it
all up.
The major functionality to include is implemented, with the exception of
Excel output of the basic data.
Assumes no options = '_total' or '_incorrect'.
Known issues:
The 'submission' and 'submissiongrading' parameter thingys do not appear to
allow leading/trailing spaces, so these are zealously removed.  May be an
erronous assumption.
A warning is thrown: "Possible attempt to put comments in qw() list".  I need
to turn off warnings around the "offending" line of code.
HTML::Entities::decode does not decode %20 into ' ' so I have to do it myself.
The foil analysis plots can look pretty vomitous, especially for the
Atwood problem in the PHY183 Spring 2003 course.
The colors selected may not be useful to color blind people.

    1: # The LearningOnline Network with CAPA
    2: #
    3: 
    4: # $Id: lonproblemanalysis.pm,v 1.39 2003/10/15 21:30:51 matthew Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: 
   29: package Apache::lonproblemanalysis;
   30: 
   31: use strict;
   32: use Apache::lonnet();
   33: use Apache::loncommon();
   34: use Apache::lonhtmlcommon();
   35: use Apache::loncoursedata();
   36: use Apache::lonstatistics;
   37: use Apache::lonlocal;
   38: use HTML::Entities();
   39: 
   40: my $plotcolors = [qw/
   41:                   #33ff00 
   42:                   #0033cc #990000 #aaaa66 #663399 #ff9933
   43:                   #66ccff #ff9999 #cccc33 #660000 #33cc66
   44:                   /]; 
   45:                #[qw/lgreen dgreen dred/];
   46: 
   47: sub BuildProblemAnalysisPage {
   48:     my ($r,$c)=@_;
   49:     $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
   50:     $r->print(&CreateInterface());
   51:     #
   52:     my @Students = @Apache::lonstatistics::Students;
   53:     #
   54:     if (exists($ENV{'form.updatecaches'}) ||
   55:         (exists($ENV{'form.firstanalysis'}) &&
   56:          $ENV{'form.firstanalysis'} ne 'no')) {
   57:         &Apache::lonstatistics::Gather_Full_Student_Data($r);
   58:     }
   59:     if (! exists($ENV{'form.firstanalysis'})) {
   60:         $r->print('<input type="hidden" name="firstanalysis" value="yes" />');
   61:     } else {
   62:         $r->print('<input type="hidden" name="firstanalysis" value="no" />');
   63:     }
   64:     $r->rflush();
   65:     if (exists($ENV{'form.problemchoice'}) && 
   66:         ! exists($ENV{'form.SelectAnother'})) {
   67:         $r->print('<input type="submit" name="ProblemAnalysis" value="'.
   68:                   &mt('Analyze Problem Again').'" />');
   69:         $r->print('&nbsp;'x5);
   70:         $r->print('<input type="submit" name="ClearCache" value="'.
   71:                   &mt('Clear Caches').'" />');
   72:         $r->print('&nbsp;'x5);
   73:         $r->print('<input type="submit" name="updatecaches" value="'.
   74:                   &mt('Update Student Data').'" />');
   75:         $r->print('&nbsp;'x5);
   76:         $r->print('<input type="hidden" name="problemchoice" value="'.
   77:                   $ENV{'form.problemchoice'}.'" />');
   78:         $r->print('<input type="submit" name="SelectAnother" value="'.
   79:                   &mt('Choose a different resource').'" />');
   80:         $r->print('&nbsp;'x5);
   81:         #
   82:         $r->print('<hr />');
   83:         #
   84:         my ($symb,$part,$resid) = &get_problem_symb(
   85:                      &Apache::lonnet::unescape($ENV{'form.problemchoice'})
   86:                                            );
   87:         #
   88:         my $resource = &get_resource_from_symb($symb);
   89:         if (defined($resource)) {
   90:             my %Data = &get_problem_data($resource->{'src'});
   91:             my $ORdata = $Data{$part.'.'.$resid};
   92:             ##
   93:             ## Render the problem
   94:             my $base;
   95:             ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
   96:             $base = "http://".$ENV{'SERVER_NAME'}.$base;
   97:             my $rendered_problem = 
   98:                 &Apache::lonnet::ssi_body($resource->{'src'});
   99:             $rendered_problem =~ s/<\s*form\s*/<nop /g;
  100:             $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
  101:             $r->print('<table bgcolor="ffffff"><tr><td>'.
  102:                       '<base href="'.$base.'" />'.
  103:                       $rendered_problem.
  104:                       '</td></tr></table>');
  105:             ##
  106:             ## Analyze the problem
  107:             my $PerformanceData = 
  108:                 &Apache::loncoursedata::get_optionresponse_data
  109:                                            (\@Students,$symb,$resid);
  110:             if (defined($PerformanceData) && 
  111:                 ref($PerformanceData) eq 'ARRAY') {
  112:                 if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
  113:                     my $analysis_html = &tries_analysis($PerformanceData,
  114:                                                          $ORdata);
  115:                     $r->print($analysis_html);
  116:                 } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
  117:                     my $analysis_html = &time_analysis($PerformanceData,
  118:                                                          $ORdata);
  119:                 $r->print($analysis_html);
  120:                 } else {
  121:                     $r->print('<h2>'.
  122:                               &mt('The analysis you have selected is '.
  123:                                          'not supported at this time').
  124:                               '</h2>');
  125:                 }
  126:             } else {
  127:                 $r->print('<h2>'.
  128:                           &mt('There is no student data for this problem.').
  129:                           '</h2>');
  130:             }
  131:         } else {
  132:             $r->print('resource is undefined');
  133:         }
  134:         $r->print('<hr />');
  135:     } else {
  136:         $r->print('<input type="submit" name="ProblemAnalysis" value="'.
  137:                   &mt('Analyze Problem').'" />');
  138:         $r->print('&nbsp;'x5);
  139:         $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
  140:         $r->print(&OptionResponseProblemSelector());
  141:     }
  142: }
  143: 
  144: 
  145: #########################################################
  146: #########################################################
  147: ##
  148: ##      Misc interface routines use by analysis code
  149: ##
  150: #########################################################
  151: #########################################################
  152: sub build_foil_index {
  153:     my ($ORdata) = @_;
  154:     my %Foildata = %{$ORdata->{'Foils'}};
  155:     my @Foils = sort(keys(%Foildata));
  156:     my %Concepts;
  157:     foreach my $foilid (@Foils) {
  158:         push(@{$Concepts{$Foildata{$foilid}->{'Concept'}}},
  159:              $foilid);
  160:     }
  161:     undef(@Foils);
  162:     # Having gathered the concept information in a hash, we now translate it
  163:     # into an array because we need to be consistent about order.
  164:     # Also put the foils in order, too.
  165:     my $sortfunction = sub {
  166:         my %Numbers = (one   => 1,
  167:                        two   => 2,
  168:                        three => 3,
  169:                        four  => 4,
  170:                        five  => 5,
  171:                        six   => 6,
  172:                        seven => 7,
  173:                        eight => 8,
  174:                        nine  => 9,
  175:                        ten   => 10,);
  176:         my $a1 = $a; 
  177:         my $b1 = $b;
  178:         if (exists($Numbers{lc($a)})) {
  179:             $a1 = $Numbers{lc($a)};
  180:         }
  181:         if (exists($Numbers{lc($b)})) {
  182:             $b1 = $Numbers{lc($b)};
  183:         }
  184:         $a1 cmp $b1;
  185:     };
  186:     my @Concepts;
  187:     foreach my $concept (sort $sortfunction (keys(%Concepts))) {
  188:         push(@Concepts,{ name => $concept,
  189:                         foils => [@{$Concepts{$concept}}]});
  190:         push(@Foils,(@{$Concepts{$concept}}));
  191:     }
  192:     #
  193:     # Build up the table of row labels.
  194:     my $table = '<table border="1" >'."\n";
  195:     if (@Concepts > 1) {
  196:         $table .= '<tr>'.
  197:             '<th>'.&mt('Concept Number').'</th>'.
  198:             '<th>'.&mt('Concept').'</th>'.
  199:             '<th>'.&mt('Foil Number').'</th>'.
  200:             '<th>'.&mt('Foil Name').'</th>'.
  201:             '<th>'.&mt('Foil Text').'</th>'.
  202:             '<th>'.&mt('Correct Value').'</th>'.
  203:             "</tr>\n";
  204:     } else {
  205:         $table .= '<tr>'.
  206:             '<th>'.&mt('Foil Number').'</th>'.
  207:             '<th>'.&mt('Foil Name').'</th>'.
  208:             '<th>'.&mt('Foil Text').'</th>'.
  209:             '<th>'.&mt('Correct Value').'</th>'.
  210:             "</tr>\n";
  211:     }        
  212:     my $conceptindex = 1;
  213:     my $foilindex = 1;
  214:     foreach my $concept (@Concepts) {
  215:         my @FoilsInConcept = @{$concept->{'foils'}};
  216:         my $firstfoil = shift(@FoilsInConcept);
  217:         if (@Concepts > 1) {
  218:             $table .= '<tr>'.
  219:                 '<td>'.$conceptindex.'</td>'.
  220:                 '<td>'.$concept->{'name'}.'</td>'.
  221:                 '<td>'.$foilindex++.'</td>'.
  222:                 '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
  223:                 '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
  224:                 '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
  225:                 "</tr>\n";
  226:         } else {
  227:             $table .= '<tr>'.
  228:                 '<td>'.$foilindex++.'</td>'.
  229:                 '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
  230:                 '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
  231:                 '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
  232:                 "</tr>\n";
  233:         }
  234:         foreach my $foilid (@FoilsInConcept) {
  235:             if (@Concepts > 1) {
  236:                 $table .= '<tr>'.
  237:                     '<td></td>'.
  238:                     '<td></td>'.
  239:                     '<td>'.$foilindex.'</td>'.
  240:                     '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
  241:                     '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
  242:                     '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
  243:                     "</tr>\n";
  244:             } else {
  245:                 $table .= '<tr>'.
  246:                     '<td>'.$foilindex.'</td>'.
  247:                     '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
  248:                     '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
  249:                     '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
  250:                     "</tr>\n";
  251:             }                
  252:         } continue {
  253:             $foilindex++;
  254:         }
  255:     } continue {
  256:         $conceptindex++;
  257:     }
  258:     $table .= "</table>\n";
  259:     #
  260:     # Build option index with color stuff
  261:     return ($table,\@Foils,\@Concepts);
  262: }
  263: 
  264: sub build_option_index {
  265:     my ($ORdata)= @_;
  266:     my $table = "<table>\n";
  267:     my $optionindex = 0;
  268:     my @Rows;
  269:     foreach my $option ('correct',@{$ORdata->{'Options'}}) {
  270:         push (@Rows,
  271:               '<tr>'.
  272:               '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
  273:               ('&nbsp;'x4).'</td>'.
  274:               '<td>'.$option.'</td>'.
  275:               "</tr>\n");
  276:     }
  277:     $table .= join('',reverse(@Rows));
  278:     $table .= "</table>\n";
  279: }
  280: 
  281: #########################################################
  282: #########################################################
  283: ##
  284: ##         Tries Analysis
  285: ##
  286: #########################################################
  287: #########################################################
  288: sub tries_analysis {
  289:     my ($PerformanceData,$ORdata) = @_;
  290:     my $mintries = 1;
  291:     my $maxtries = $ENV{'form.NumPlots'};
  292:     my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
  293:     if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
  294:         $table = '<h3>'.
  295:             &mt('Not enough data for concept analysis.  '.
  296:                 'Performing Foil Analysis').
  297:             '</h3>'.$table;
  298:         $ENV{'form.AnalyzeAs'} = 'Foils';
  299:     }
  300:     my %ResponseData = &analyze_option_data_by_tries($PerformanceData,
  301:                                                      $mintries,$maxtries);
  302:     #
  303:     # Compute the data neccessary to make the plots
  304:     my @PlotData;   # Array which holds the data for each plot
  305:                     # @{$PlotData[$try]->{'datasetname'}} holds the data for
  306:                     # try $try with respect to 'datasetname'.  The array is
  307:                     # filled either with per-foil or per-concept data.
  308:     my ($extrakey,$xlabel,$ylabel);
  309:     if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
  310:         $extrakey = &build_option_index($ORdata);
  311:         $xlabel = 'Foil Number';
  312:         $ylabel = 'Option Chosen';
  313:         foreach my $foilid (@$Foils) {
  314:             for (my $i=$mintries;$i<=$maxtries;$i++) {
  315:                 foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
  316:                     push(@{$PlotData[$i]->{'_total'}},
  317:                          $ResponseData{$foilid}->[$i]->{'_total'});
  318:                     if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
  319:                         push (@{$PlotData[$i]->{$option}},0);
  320:                     } else {
  321:                         push (@{$PlotData[$i]->{$option}},
  322:                               100 * $ResponseData{$foilid}->[$i]->{$option} / 
  323:                                     $ResponseData{$foilid}->[$i]->{'_total'});
  324:                     }
  325:                 }
  326:             }
  327:         }
  328:     } else {
  329:         # Concept analysis
  330:         #
  331:         # Note: we do not bother with characterizing the students incorrect
  332:         # answers at the concept level because an incorrect answer for one foil
  333:         # may be a correct answer for another foil.
  334:         $extrakey = '';
  335:         $xlabel = 'Concept Number';
  336:         $ylabel = 'Percent Correct';
  337:         my %ConceptData;
  338:         foreach my $concept (@{$Concepts}) {
  339:             for (my $i=$mintries;$i<=$maxtries;$i++) {
  340:                 #
  341:                 # Gather the per-attempt data
  342:                 my $cdata = $ConceptData{$concept}->[$i];
  343:                 foreach my $foilid (@{$concept->{'foils'}}) {
  344:                     $cdata->{'_correct'} += 
  345:                         $ResponseData{$foilid}->[$i]->{'_correct'};
  346:                     $cdata->{'_total'}   += 
  347:                         $ResponseData{$foilid}->[$i]->{'_total'};
  348:                 }
  349:                 push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
  350:                 if ($cdata->{'_total'} == 0) {
  351:                     push (@{$PlotData[$i]->{'_correct'}},0);
  352:                 } else {
  353:                     push (@{$PlotData[$i]->{'_correct'}},
  354:                           100*$cdata->{'_correct'}/$cdata->{'_total'});
  355:                 }
  356:             }
  357:         }
  358:     } # End of work to fill @PlotData
  359:     # 
  360:     # Build a table for the plots
  361:     $table .= "<table>\n";
  362:     my @Plots;
  363:     for (my $i=$mintries;$i<=$maxtries;$i++) {
  364:         my $minstu = $PlotData[$i]->{'_total'}->[0];
  365:         my $maxstu = $PlotData[$i]->{'_total'}->[0];
  366:         foreach my $count (@{$PlotData[$i]->{'_total'}}) {
  367:             if ($minstu > $count) {
  368:                 $minstu = $count;
  369:             }
  370:             if ($maxstu < $count) {
  371:                 $maxstu = $count;
  372:             }
  373:         }
  374:         $maxstu = 0 if (! defined($maxstu));
  375:         $minstu = 0 if (! defined($minstu));
  376:         my $title;
  377:         if ($maxstu == $minstu) {
  378:             $title = 'Attempt '.$i.', '.$maxstu.' students';
  379:         } else {
  380:             $title = 'Attempt '.$i.', '.$minstu.'-'.$maxstu.' students';
  381:         }
  382:         my @Datasets;
  383:         foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
  384:             next if (! exists($PlotData[$i]->{$option}));
  385:             push(@Datasets,$PlotData[$i]->{$option});
  386:         }
  387:         my $graphlink = &Apache::loncommon::DrawGraph($title,
  388:                                                       $xlabel,
  389:                                                       $ylabel,
  390:                                                       100,
  391:                                                       $plotcolors,
  392:                                                       @Datasets);
  393:         push(@Plots,$graphlink);
  394:     }
  395:     #
  396:     # Should this be something the user can set?  Too many dialogs!
  397:     while (my $plotlink = shift(@Plots)) {
  398:         $table .= '<tr><td>'.$plotlink.'</td><td>'.$extrakey."</td></tr>\n";
  399:     }
  400:     $table .= "</table>\n";
  401:     return ($table);
  402: }
  403: 
  404: sub analyze_option_data_by_tries {
  405:     my ($PerformanceData,$mintries,$maxtries) = @_;
  406:     my %Trydata;
  407:     $mintries = 1         if (! defined($mintries) || $mintries < 1);
  408:     $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
  409:     foreach my $row (@$PerformanceData) {
  410:         next if (! defined($row));
  411:         my ($grading,$submission,$time,$tries) = @$row;
  412:         next if ($grading eq 'MISSING_ANSWER');
  413:         my @Foilgrades = split('&',$grading);
  414:         my @Foilsubs   = split('&',$submission);
  415:         for (my $numtries = 1; $numtries <= $maxtries; $numtries++) {
  416:             if ($tries == $numtries) {
  417:                 for (my $i=0;$i<=$#Foilgrades;$i++) {
  418:                     my ($foilid,$correct)  = split('=',$Foilgrades[$i]);
  419:                     my (undef,$submission) = split('=',$Foilsubs[$i]);
  420:                     $submission = &HTML::Entities::decode($submission);
  421:                     $submission =~ s/\%20/ /g;
  422:                     if ($correct) {
  423:                         $Trydata{$foilid}->[$numtries]->{'_correct'}++;
  424:                     } else {
  425:                         $Trydata{$foilid}->[$numtries]->{$submission}++;
  426:                     }                        
  427:                     $Trydata{$foilid}->[$numtries]->{'_total'}++;
  428:                 }
  429:             }
  430:         }
  431:     }
  432:     return %Trydata;
  433: }
  434: 
  435: #########################################################
  436: #########################################################
  437: ##
  438: ##                 Time Analysis
  439: ##
  440: #########################################################
  441: #########################################################
  442: sub time_analysis {
  443:     my ($PerformanceData,$ORdata) = @_;
  444:     my $num_plots = $ENV{'form.NumPlots'};
  445:     my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
  446:     my $num_data = scalar(@$PerformanceData)-1;
  447:     my $percent = sprintf('%2f',100/$num_plots);
  448:     my $extratable = '';
  449:     if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
  450:         $extratable = &build_option_index($ORdata);
  451:     }
  452:     $table .= "<table>\n";
  453:     for (my $i=0;$i<$num_plots;$i++) {
  454:         my $starttime = &Apache::lonhtmlcommon::get_date_from_form
  455:             ('startdate_'.$i);
  456:         my $endtime = &Apache::lonhtmlcommon::get_date_from_form
  457:             ('enddate_'.$i);
  458:         my ($begin_index,$end_index,$plottitle,$plothtml,$data);
  459:         if (! defined($starttime) || ! defined($endtime)) {
  460:             $begin_index = $i*int($num_data/$num_plots);
  461:             $end_index = ($i+1)*int($num_data/$num_plots);
  462:             my $lownum  = sprintf('%2.1f',$i*$percent);
  463:             $lownum =~ s/(\.0)$//;
  464:             my $highnum = sprintf('%2.1f',($i+1)*$percent);
  465:             $highnum =~ s/(\.0)$//;
  466:             $plottitle = $lownum.'% to '.$highnum.'% of submissions';
  467:         } else {
  468:             my $j;
  469:             while (++$j < scalar(@$PerformanceData)) {
  470:                 last if ($PerformanceData->[$j]->[2] > $starttime);
  471:             }
  472:             $begin_index = $j;
  473:             while (++$j < scalar(@$PerformanceData)) {
  474:                 last if ($PerformanceData->[$j]->[2] > $endtime);
  475:             }
  476:             $end_index = $j;
  477:             $plottitle = $ENV{'form.plottitle_'.$i};
  478:         }
  479:         ($plothtml,$starttime,$endtime,$data) = 
  480:             &analyze_option_data_by_time($PerformanceData,
  481:                                          $begin_index,$end_index,
  482:                                          $plottitle,$Foils,
  483:                                          $Concepts,$ORdata);
  484:         my $startdateform = &Apache::lonhtmlcommon::date_setter
  485:             ('Statistics','startdate_'.$i,$starttime);
  486:         my $enddateform = &Apache::lonhtmlcommon::date_setter
  487:             ('Statistics','enddate_'.$i,$endtime);
  488:         $table.="<tr><td>".$plothtml.'</td><td align="left" valign="top">'.
  489:             "<b>Start Time</b>: &nbsp;".$startdateform."<br />".
  490:             "<b>End Time</b>&nbsp;&nbsp;: "."&nbsp;".$enddateform."<br />".
  491:             '<b>Plot Title</b>&nbsp;&nbsp;:'.("&nbsp;"x3).
  492:             '<input type="text" size="30" name="plottitle_'.$i.'" value="'.
  493:                   &HTML::Entities::encode($plottitle).'" /><br />'.$extratable.
  494:             "</td></tr>\n";
  495:     }
  496:     $table .="</table>\n";
  497:     return $table;
  498: }
  499: 
  500: sub analyze_option_data_by_time {
  501:     my ($PerformanceData,$begin_index,
  502:         $end_index,$description,$Foils,$Concepts,$ORdata) = @_;
  503:     my %TimeData;
  504:     #
  505:     # Get the start and end times for this segment of the plot
  506:     my $starttime = $PerformanceData->[$begin_index]->[2];
  507:     my $endtime   = $PerformanceData->[$end_index  ]->[2];
  508:     #
  509:     # Compute the number getting the foils correct or incorrects
  510:     for (my $i=$begin_index;$i<=$end_index;$i++) {
  511:         my $row = $PerformanceData->[$i];
  512:         next if (! defined($row));
  513:         my ($grading,$submission,$time,$tries) = @$row;
  514:         next if ($grading eq 'MISSING_ANSWER');
  515:         my @Foilgrades = split('&',$grading);
  516:         my @Foilsubs   = split('&',$submission);
  517:         for (my $j=0;$j<=$#Foilgrades;$j++) {
  518:             my ($foilid,$correct)  = split('=',$Foilgrades[$j]);
  519:             my (undef,$submission) = split('=',$Foilsubs[$j]);
  520:             if ($correct) {
  521:                 $TimeData{$foilid}->{'_correct'}++;
  522:             } else {
  523:                 $submission = &HTML::Entities::decode($submission);
  524:                 $submission =~ s/\%20/ /g;
  525:                 $TimeData{$foilid}->{$submission}++;
  526:             }
  527:             $TimeData{$foilid}->{'_total'}++;
  528:         }
  529:     }
  530:     #
  531:     # Compute the total and percent correct
  532:     my @Plotdata;
  533:     my ($xlabel,$ylabel);
  534:     if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
  535:         $xlabel = 'Foil Number';
  536:         $ylabel = 'Option Chosen';
  537:         foreach my $foil (@$Foils) {
  538:             my $total = $TimeData{$foil}->{'_total'};
  539:             my $optionidx = 0;
  540:             foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
  541:                 if ($total > 0) {
  542:                     push(@{$Plotdata[$optionidx]},
  543:                          100 * $TimeData{$foil}->{$option} / $total);
  544:                 } else {
  545:                     push(@{$Plotdata[$optionidx]},0);
  546:                 }
  547:             } continue {
  548:                 $optionidx++;
  549:             }
  550:         }
  551:     } else {
  552:         $xlabel = 'Concept Number';
  553:         $ylabel = 'Percent Correct';
  554:         foreach my $concept (@$Concepts) {
  555:             my $correct;
  556:             my $total;
  557:             foreach my $foil (@{$concept->{'foils'}}) {
  558:                 $correct+=$TimeData{$foil}->{'_correct'};
  559:                 $total  +=$TimeData{$foil}->{'_total'};
  560:             }
  561:             if ($total > 0) {
  562:                 push(@{$Plotdata[0]},100 * $correct / $total);
  563:             } else {
  564:                 push(@{$Plotdata[0]},0);
  565:             }
  566:         }
  567:     }
  568:     #
  569:     # Create the plot
  570:     my $graphlink = &Apache::loncommon::DrawGraph
  571:         ($description,#'Time Interval Analysis',
  572:          $xlabel,
  573:          $ylabel,
  574:          100,
  575:          $plotcolors,
  576:          @Plotdata);
  577:     #
  578:     return ($graphlink,$starttime,$endtime,\%TimeData);
  579: }
  580: 
  581: #########################################################
  582: #########################################################
  583: ##
  584: ##             Interface 
  585: ##
  586: #########################################################
  587: #########################################################
  588: sub CreateInterface {
  589:     ##
  590:     ## Environment variable initialization
  591:     if (! exists$ENV{'form.AnalyzeOver'}) {
  592:         $ENV{'form.AnalyzeOver'} = 'Tries';
  593:     }
  594:     ##
  595:     ## Build the menu
  596:     my $Str = '';
  597:     $Str .= '<table cellspacing="5">'."\n";
  598:     $Str .= '<tr>';
  599:     $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
  600:     $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
  601: #    $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
  602:     $Str .= '<td align="center">&nbsp;</td>';
  603:     $Str .= '</tr>'."\n";
  604:     ##
  605:     ## 
  606:     $Str .= '<tr><td align="center">'."\n";
  607:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
  608:     $Str .= '</td>';
  609:     #
  610:     $Str .= '<td align="center">';
  611:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
  612:     $Str .= '</td>';
  613:     #
  614: #    $Str .= '<td align="center">';
  615:     my $only_seq_with_assessments = sub { 
  616:         my $s=shift;
  617:         if ($s->{'num_assess'} < 1) { 
  618:             return 0;
  619:         } else { 
  620:             return 1;
  621:         }
  622:     };
  623:     &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
  624:                                               $only_seq_with_assessments);
  625:     ##
  626:     ##
  627:     $Str .= '<td>';
  628:     { # These braces are here to organize the code, not scope it.
  629:         {
  630:             $Str .= '<nobr>'.&mt('Analyze Over ');
  631:             $Str .='<select name="AnalyzeOver" >';
  632:             $Str .= '<option value="Tries" ';
  633:             if (! exists($ENV{'form.AnalyzeOver'}) || 
  634:                 $ENV{'form.AnalyzeOver'} eq 'Tries'){
  635:                 # Default to Tries
  636:                 $Str .= ' selected ';
  637:             }
  638:             $Str .= '>'.&mt('Tries').'</option>';
  639:             $Str .= '<option value="Time" ';
  640:             $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
  641:             $Str .= '>'.&mt('Time').'</option>';
  642:             $Str .= '</select></nobr><br />';
  643:         }
  644:         {
  645:             $Str .= '<nobr>'.&mt('Analyze as ');
  646:             $Str .='<select name="AnalyzeAs" >';
  647:             $Str .= '<option value="Concepts" ';
  648:             if (! exists($ENV{'form.AnalyzeAs'}) || 
  649:                 $ENV{'form.AnalyzeAs'} eq 'Concepts'){
  650:                 # Default to Concepts
  651:                 $Str .= ' selected ';
  652:             }
  653:             $Str .= '>'.&mt('Concepts').'</option>';
  654:             $Str .= '<option value="Foils" ';
  655:             $Str .= ' selected ' if ($ENV{'form.AnalyzeAs'} eq 'Foils');
  656:             $Str .= '>'.&mt('Foils').'</option>';
  657:             $Str .= '</select></nobr><br />';
  658:         }
  659:         {
  660:             $Str .= '<br /><nobr>'.&mt('Number of Plots:');
  661:             $Str .= '<select name="NumPlots">';
  662:             if (! exists($ENV{'form.NumPlots'}) 
  663:                 || $ENV{'form.NumPlots'} < 1 
  664:                 || $ENV{'form.NumPlots'} > 20) {
  665:                 $ENV{'form.NumPlots'} = 5;
  666:             }
  667:             foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
  668:                 $Str .= '<option value="'.$i.'" ';
  669:                 if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
  670:                 $Str .= '>'.$i.'</option>';
  671:             }
  672:             $Str .= '</select></nobr>';
  673:         }
  674:     }
  675:     $Str .= '</td>';
  676:     ##
  677:     ##
  678:     $Str .= '</tr>'."\n";
  679:     $Str .= '</table>'."\n";
  680:     return ($Str);
  681: }
  682: 
  683: sub OptionResponseProblemSelector {
  684:     my $Str;
  685:     $Str = "\n<table>\n";
  686:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
  687:         next if ($seq->{'num_assess'}<1);
  688:         my $seq_str = '';
  689:         foreach my $res (@{$seq->{'contents'}}) {
  690:             next if ($res->{'type'} ne 'assessment');
  691:             foreach my $part (@{$res->{'parts'}}) {
  692:                 my $partdata = $res->{'partdata'}->{$part};
  693:                 if (! exists($partdata->{'option'}) || 
  694:                     $partdata->{'option'} == 0) {
  695:                     next;
  696:                 }
  697:                 for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
  698:                     my $respid = $partdata->{'ResponseIds'}->[$i];
  699:                     my $resptype = $partdata->{'ResponseTypes'}->[$i];
  700:                     if ($resptype eq 'option') {
  701:                         my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$part.':'.$respid);
  702:                         my $checked = '';
  703:                         if ($ENV{'form.problemchoice'} eq $value) {
  704:                             $checked = 'checked ';
  705:                         }
  706:                         $seq_str .= '<tr><td>'.
  707:   '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
  708:   '</td><td>'.
  709:   '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> ';
  710:                         if ($partdata->{'option'} > 1) {
  711:                             $seq_str .= &mt('response').' '.$respid;
  712:                         }
  713:                         $seq_str .= "</td></tr>\n";
  714:                     }
  715:                 }
  716:             }
  717:         }
  718:         if ($seq_str ne '') {
  719:             $Str .= '<tr><td>&nbsp</td><td><b>'.$seq->{'title'}.'</b></td>'.
  720:                 "</tr>\n".$seq_str;
  721:         }
  722:     }
  723:     $Str .= "</table>\n";
  724:     return $Str;
  725: }
  726: 
  727: #########################################################
  728: #########################################################
  729: ##
  730: ##              Misc functions
  731: ##
  732: #########################################################
  733: #########################################################
  734: sub get_problem_symb {
  735:     my $problemstring = shift();
  736:     my ($symb,$partid,$resid) = ($problemstring=~ /^(.*):([^:]*):([^:]*)$/);
  737:     return ($symb,$partid,$resid);
  738: }
  739: 
  740: sub get_resource_from_symb {
  741:     my ($symb) = @_;
  742:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
  743:         foreach my $res (@{$seq->{'contents'}}) {
  744:             if ($res->{'symb'} eq $symb) {
  745:                 return $res;
  746:             }
  747:         }
  748:     }
  749:     return undef;
  750: }
  751: 
  752: ##
  753: ## get problem data and put it into a useful data structure.
  754: ## note: we must force each foil and option to not begin or end with
  755: ##       spaces as they are stored without such data.
  756: ##
  757: sub get_problem_data {
  758:     my ($url) = @_;
  759:     my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
  760:     (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
  761:     my %Answer;
  762:     %Answer=&Apache::lonnet::str2hash($Answ);
  763:     my %Partdata;
  764:     foreach my $part (@{$Answer{'parts'}}) {
  765:         while (my($key,$value) = each(%Answer)) {
  766:             next if ($key !~ /^$part/);
  767:             $key =~ s/^$part\.//;
  768:             if (ref($value) eq 'ARRAY') {
  769:                 if ($key eq 'options') {
  770:                     for(my $i=0;$i<scalar(@$value);$i++) {
  771:                         $value->[$i]=~ s/(\s*$|^\s*)//g;
  772:                     }
  773:                     $Partdata{$part}->{'Options'}=$value;
  774:                 } elsif ($key eq 'concepts') {
  775:                     $Partdata{$part}->{'Concepts'}=$value;
  776:                 } elsif ($key =~ /^concept\.(.*)$/) {
  777:                     my $concept = $1;
  778:                     foreach my $foil (@$value) {
  779:                         $foil =~ s/(\s*$|^\s*)//g;
  780:                         $Partdata{$part}->{'Foils'}->{$foil}->{'Concept'}=
  781:                                                                       $concept;
  782:                     }
  783:                 }
  784:             } else {
  785:                 $value =~ s/(\s*$|^\s*)//g;
  786:                 if ($key=~ /^foil\.text\.(.*)$/) {
  787:                     my $foil = $1;
  788:                     $foil =~ s/(\s*$|^\s*)//g;
  789:                     $Partdata{$part}->{'Foils'}->{$foil}->{'name'}=$foil;
  790:                     $Partdata{$part}->{'Foils'}->{$foil}->{'text'}=$value;
  791:                 } elsif ($key =~ /^foil\.value\.(.*)$/) {
  792:                     my $foil = $1;
  793:                     $foil =~ s/(\s*$|^\s*)//g;
  794:                     $Partdata{$part}->{'Foils'}->{$foil}->{'value'}=$value;
  795:                 }
  796:             }
  797:         }
  798:     }
  799:     return %Partdata;
  800: }
  801: 
  802: 1;
  803: 
  804: __END__

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