File:  [LON-CAPA] / loncom / interface / statistics / lonproblemanalysis.pm
Revision 1.42: download - view: text, annotated - select for diffs
Fri Oct 17 22:05:45 2003 UTC (20 years, 8 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Many changes, most to internal code structure.  Hopefully future changes
will not require such monsterous rewrites.
Foils analysis now shows two graphs, one for the correct choice on the
foils and the other for the incorrect submissions.
Time analysis no longer has editable plot titles.
Added a few helper functions get_time_from_row, get_tries_from_row, and
Process_Row.

# The LearningOnline Network with CAPA
#
# $Id: lonproblemanalysis.pm,v 1.42 2003/10/17 22:05:45 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

package Apache::lonproblemanalysis;

use strict;
use Apache::lonnet();
use Apache::loncommon();
use Apache::lonhtmlcommon();
use Apache::loncoursedata();
use Apache::lonstatistics;
use Apache::lonlocal;
use HTML::Entities();
use Time::Local();

my $plotcolors = ['#33ff00', 
                  '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
                  '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
                  ]; 

sub BuildProblemAnalysisPage {
    my ($r,$c)=@_;
    $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
    $r->print(&CreateInterface());
    #
    my @Students = @Apache::lonstatistics::Students;
    #
    if (exists($ENV{'form.ClearCache'}) || 
        exists($ENV{'form.updatecaches'}) ||
        (exists($ENV{'form.firstanalysis'}) &&
         $ENV{'form.firstanalysis'} ne 'no')) {
        &Apache::lonstatistics::Gather_Full_Student_Data($r);
    }
    if (! exists($ENV{'form.firstanalysis'})) {
        $r->print('<input type="hidden" name="firstanalysis" value="yes" />');
    } else {
        $r->print('<input type="hidden" name="firstanalysis" value="no" />');
    }
    $r->rflush();
    if (exists($ENV{'form.problemchoice'}) && 
        ! exists($ENV{'form.SelectAnother'})) {
        $r->print('<input type="submit" name="ProblemAnalysis" value="'.
                  &mt('Analyze Problem Again').'" />');
        $r->print('&nbsp;'x5);
        $r->print('<input type="submit" name="ClearCache" value="'.
                  &mt('Clear Caches').'" />');
        $r->print('&nbsp;'x5);
        $r->print('<input type="submit" name="updatecaches" value="'.
                  &mt('Update Student Data').'" />');
        $r->print('&nbsp;'x5);
        $r->print('<input type="hidden" name="problemchoice" value="'.
                  $ENV{'form.problemchoice'}.'" />');
        $r->print('<input type="submit" name="SelectAnother" value="'.
                  &mt('Choose a different resource').'" />');
        $r->print('&nbsp;'x5);
        #
        $r->print('<hr />');
        #
        my ($symb,$part,$resid) = &get_problem_symb(
                     &Apache::lonnet::unescape($ENV{'form.problemchoice'})
                                           );
        #
        my $resource = &get_resource_from_symb($symb);
        if (defined($resource)) {
            $r->print('<h3>'.$resource->{'src'}.'</h3>');
            my %Data = &get_problem_data($resource->{'src'});
            my $ORdata = $Data{$part.'.'.$resid};
            ##
            ## Render the problem
            my $base;
            ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
            $base = "http://".$ENV{'SERVER_NAME'}.$base;
            my $rendered_problem = 
                &Apache::lonnet::ssi_body($resource->{'src'});
            $rendered_problem =~ s/<\s*form\s*/<nop /g;
            $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
            $r->print('<table bgcolor="ffffff"><tr><td>'.
                      '<base href="'.$base.'" />'.
                      $rendered_problem.
                      '</td></tr></table>');
            ##
            ## Analyze the problem
            my $PerformanceData = 
                &Apache::loncoursedata::get_optionresponse_data
                                           (\@Students,$symb,$resid);
            if (defined($PerformanceData) && 
                ref($PerformanceData) eq 'ARRAY') {
                if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
                    my $analysis_html = &tries_analysis($PerformanceData,
                                                         $ORdata);
                    $r->print($analysis_html);
                } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
                    my $analysis_html = &time_analysis($PerformanceData,
                                                         $ORdata);
                $r->print($analysis_html);
                } else {
                    $r->print('<h2>'.
                              &mt('The analysis you have selected is '.
                                         'not supported at this time').
                              '</h2>');
                }
            } else {
                $r->print('<h2>'.
                          &mt('There is no student data for this problem.').
                          '</h2>');
            }
        } else {
            $r->print('resource is undefined');
        }
        $r->print('<hr />');
    } else {
        $r->print('<input type="submit" name="ProblemAnalysis" value="'.
                  &mt('Analyze Problem').'" />');
        $r->print('&nbsp;'x5);
        $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
        $r->print(&OptionResponseProblemSelector());
    }
}

#########################################################
#########################################################
##
##      Misc interface routines use by analysis code
##
#########################################################
#########################################################
sub build_foil_index {
    my ($ORdata) = @_;
    return if (! exists($ORdata->{'Foils'}));
    my %Foildata = %{$ORdata->{'Foils'}};
    my @Foils = sort(keys(%Foildata));
    my %Concepts;
    foreach my $foilid (@Foils) {
        push(@{$Concepts{$Foildata{$foilid}->{'Concept'}}},
             $foilid);
    }
    undef(@Foils);
    # Having gathered the concept information in a hash, we now translate it
    # into an array because we need to be consistent about order.
    # Also put the foils in order, too.
    my $sortfunction = sub {
        my %Numbers = (one   => 1,
                       two   => 2,
                       three => 3,
                       four  => 4,
                       five  => 5,
                       six   => 6,
                       seven => 7,
                       eight => 8,
                       nine  => 9,
                       ten   => 10,);
        my $a1 = $a; 
        my $b1 = $b;
        if (exists($Numbers{lc($a)})) {
            $a1 = $Numbers{lc($a)};
        }
        if (exists($Numbers{lc($b)})) {
            $b1 = $Numbers{lc($b)};
        }
        $a1 cmp $b1;
    };
    my @Concepts;
    foreach my $concept (sort $sortfunction (keys(%Concepts))) {
        push(@Concepts,{ name => $concept,
                        foils => [@{$Concepts{$concept}}]});
        push(@Foils,(@{$Concepts{$concept}}));
    }
    #
    # Build up the table of row labels.
    my $table = '<table border="1" >'."\n";
    if (@Concepts > 1) {
        $table .= '<tr>'.
            '<th>'.&mt('Concept Number').'</th>'.
            '<th>'.&mt('Concept').'</th>'.
            '<th>'.&mt('Foil Number').'</th>'.
            '<th>'.&mt('Foil Name').'</th>'.
            '<th>'.&mt('Foil Text').'</th>'.
            '<th>'.&mt('Correct Value').'</th>'.
            "</tr>\n";
    } else {
        $table .= '<tr>'.
            '<th>'.&mt('Foil Number').'</th>'.
            '<th>'.&mt('Foil Name').'</th>'.
            '<th>'.&mt('Foil Text').'</th>'.
            '<th>'.&mt('Correct Value').'</th>'.
            "</tr>\n";
    }        
    my $conceptindex = 1;
    my $foilindex = 1;
    foreach my $concept (@Concepts) {
        my @FoilsInConcept = @{$concept->{'foils'}};
        my $firstfoil = shift(@FoilsInConcept);
        if (@Concepts > 1) {
            $table .= '<tr>'.
                '<td>'.$conceptindex.'</td>'.
                '<td>'.$concept->{'name'}.'</td>'.
                '<td>'.$foilindex++.'</td>'.
                '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
                '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
                '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
                "</tr>\n";
        } else {
            $table .= '<tr>'.
                '<td>'.$foilindex++.'</td>'.
                '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
                '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
                '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
                "</tr>\n";
        }
        foreach my $foilid (@FoilsInConcept) {
            if (@Concepts > 1) {
                $table .= '<tr>'.
                    '<td></td>'.
                    '<td></td>'.
                    '<td>'.$foilindex.'</td>'.
                    '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
                    '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
                    '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
                    "</tr>\n";
            } else {
                $table .= '<tr>'.
                    '<td>'.$foilindex.'</td>'.
                    '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
                    '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
                    '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
                    "</tr>\n";
            }                
        } continue {
            $foilindex++;
        }
    } continue {
        $conceptindex++;
    }
    $table .= "</table>\n";
    #
    # Build option index with color stuff
    return ($table,\@Foils,\@Concepts);
}

sub build_option_index {
    my ($ORdata)= @_;
    my $table = "<table>\n";
    my $optionindex = 0;
    my @Rows;
    foreach my $option (&mt('correct option chosen'),@{$ORdata->{'Options'}}) {
        push (@Rows,
              '<tr>'.
              '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
              ('&nbsp;'x4).'</td>'.
              '<td>'.$option.'</td>'.
              "</tr>\n");
    }
    shift(@Rows); # Throw away 'correct option chosen' color
    $table .= join('',reverse(@Rows));
    $table .= "</table>\n";
}

#########################################################
#########################################################
##
##         Tries Analysis
##
#########################################################
#########################################################
sub tries_analysis {
    my ($PerformanceData,$ORdata) = @_;
    my $mintries = 1;
    my $maxtries = $ENV{'form.NumPlots'};
    my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
    if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
        $table = '<h3>'.
            &mt('Not enough data for concept analysis.  '.
                'Performing Foil Analysis').
            '</h3>'.$table;
        $ENV{'form.AnalyzeAs'} = 'Foils';
    }
    my %ResponseData = &analyze_option_data_by_tries($PerformanceData,
                                                     $mintries,$maxtries);
    my $analysis = '';
    if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
        $analysis = &Tries_Foil_Analysis($mintries,$maxtries,$Foils,
                                         \%ResponseData,$ORdata);
    } else {
        $analysis = &Tries_Concept_Analysis($mintries,$maxtries,
                                            $Concepts,\%ResponseData,$ORdata);
    }
    $table .= $analysis;
    return $table;
}

sub Tries_Foil_Analysis {
    my ($mintries,$maxtries,$Foils,$respdat,$ORdata) = @_;
    my %ResponseData = %$respdat;
    #
    # Compute the data neccessary to make the plots
    my @PlotData; 
    foreach my $foilid (@$Foils) {
        for (my $i=$mintries;$i<=$maxtries;$i++) {
            if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
                push(@{$PlotData[$i]->{'_correct'}},0);
            } else {
                push(@{$PlotData[$i]->{'_correct'}},
                     100*$ResponseData{$foilid}->[$i]->{'_correct'}/
                     $ResponseData{$foilid}->[$i]->{'_total'});
            }
            foreach my $option (@{$ORdata->{'Options'}}) {
                push(@{$PlotData[$i]->{'_total'}},
                     $ResponseData{$foilid}->[$i]->{'_total'});
                if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
                    push (@{$PlotData[$i]->{$option}},0);
                } else {
                    if ($ResponseData{$foilid}->[$i]->{'_total'} ==
                        $ResponseData{$foilid}->[$i]->{'_correct'}) {
                        push(@{$PlotData[$i]->{$option}},0);
                    } else {
                        push (@{$PlotData[$i]->{$option}},
                              100 * $ResponseData{$foilid}->[$i]->{$option} / 
                              ($ResponseData{$foilid}->[$i]->{'_total'} - 
                               $ResponseData{$foilid}->[$i]->{'_correct'}));
                    }
                }
            }
        }
    }
    # 
    # Build a table for the plots
    my $analysis_html = "<table>\n";
    my $foilkey = &build_option_index($ORdata);
    for (my $i=$mintries;$i<=$maxtries;$i++) {
        my $count = $ResponseData{'_total'}->[$i];
        if ($count == 0) {
            $count = 'no submissions';
        } elsif ($count == 1) {
            $count = '1 submission';
        } else {
            $count = $count.' submissions';
        }
        my $title = 'Attempt '.$i.', '.$count;
        my @Datasets;
        foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
            next if (! exists($PlotData[$i]->{$option}));
            push(@Datasets,$PlotData[$i]->{$option});
        }
        my $correctgraph = &Apache::loncommon::DrawGraph
            ($title,'Foil Number','Percent Correct',
             100,$plotcolors,$Datasets[0]);
        $analysis_html.= '<tr><td>'.$correctgraph.'</td>';
        ##
        ##
        for (my $i=0; $i< scalar(@{$Datasets[0]});$i++) {
            $Datasets[0]->[$i]=0;
        }
        $count = $ResponseData{'_total'}->[$i]-$ResponseData{'_correct'}->[$i];
        if ($count == 0) {
            $count = 'no submissions';
        } elsif ($count == 1) {
            $count = '1 submission';
        } else {
            $count = $count.' submissions';
        }
        $title = 'Attempt '.$i.', '.$count;
        my $incorrectgraph = &Apache::loncommon::DrawGraph
            ($title,'Foil Number','% Option Chosen Incorrectly',
             100,$plotcolors,@Datasets);
        $analysis_html.= '<td>'.$incorrectgraph.'</td>';
        $analysis_html.= '<td>'.$foilkey."<td></tr>\n";
    }
    $analysis_html .= "</table>\n";
    return $analysis_html;
}

sub Tries_Concept_Analysis {
    my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_;
    my %ResponseData = %$respdat;
    my $analysis_html = "<table>\n";
    #
    # Compute the data neccessary to make the plots
    my @PlotData;
    # Concept analysis
    #
    # Note: we do not bother with characterizing the students incorrect
    # answers at the concept level because an incorrect answer for one foil
    # may be a correct answer for another foil.
    my %ConceptData;
    foreach my $concept (@{$Concepts}) {
        for (my $i=$mintries;$i<=$maxtries;$i++) {
            #
            # Gather the per-attempt data
            my $cdata = $ConceptData{$concept}->[$i];
            foreach my $foilid (@{$concept->{'foils'}}) {
                $cdata->{'_correct'} += 
                    $ResponseData{$foilid}->[$i]->{'_correct'};
                $cdata->{'_total'}   += 
                    $ResponseData{$foilid}->[$i]->{'_total'};
            }
            push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
            if ($cdata->{'_total'} == 0) {
                push (@{$PlotData[$i]->{'_correct'}},0);
            } else {
                push (@{$PlotData[$i]->{'_correct'}},
                      100*$cdata->{'_correct'}/$cdata->{'_total'});
                }
        }
    }
    # Build a table for the plots
    for (my $i=$mintries;$i<=$maxtries;$i++) {
        my $minstu = $PlotData[$i]->{'_total'}->[0];
        my $maxstu = $PlotData[$i]->{'_total'}->[0];
        foreach my $count (@{$PlotData[$i]->{'_total'}}) {
            if ($minstu > $count) {
                $minstu = $count;
            }
            if ($maxstu < $count) {
                $maxstu = $count;
            }
        }
        $maxstu = 0 if (! defined($maxstu));
        $minstu = 0 if (! defined($minstu));
        my $title;
        my $count = $ResponseData{'_total'}->[$i];
        if ($count == 0) {
            $count = 'no submissions';
        } elsif ($count == 1) {
            $count = '1 submission';
        } else {
            $count = $count.' submissions';
        }
        $title = 'Attempt '.$i.', '.$count;
        my $graphlink = &Apache::loncommon::DrawGraph
            ($title,'Concept Number','Percent Correct',
             100,$plotcolors,$PlotData[$i]->{'_correct'});
        $analysis_html.= '<tr><td>'.$graphlink."</td></tr>\n";
    }
    $analysis_html .= "</table>\n";
    return $analysis_html;
}

sub analyze_option_data_by_tries {
    my ($PerformanceData,$mintries,$maxtries) = @_;
    my %Trydata;
    $mintries = 1         if (! defined($mintries) || $mintries < 1);
    $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
    foreach my $row (@$PerformanceData) {
        next if (! defined($row));
        my $tries = &get_tries_from_row($row);
        my %Row   = &Process_Row($row);
        while (my ($foilid,$href) = each(%Row)) {
            if (! ref($href)) { 
                $Trydata{$foilid}->[$tries] += $href;
                next;
            }
            while (my ($option,$value) = each(%$href)) {
                $Trydata{$foilid}->[$tries]->{$option}+=$value;
            }
        }
    }
    return %Trydata;
}

#########################################################
#########################################################
##
##                 Time Analysis
##
#########################################################
#########################################################
sub time_analysis {
    my ($PerformanceData,$ORdata) = @_;
    my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
    if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
        $table = '<h3>'.
            &mt('Not enough data for concept analysis.  '.
                'Performing Foil Analysis').
            '</h3>'.$table;
        $ENV{'form.AnalyzeAs'} = 'Foils';
    }
    my $num_plots = $ENV{'form.NumPlots'};
    my $num_data = scalar(@$PerformanceData)-1;
    my $percent = sprintf('%2f',100/$num_plots);
    #
    $table .= "<table>\n";
    for (my $i=0;$i<$num_plots;$i++) {
        ##
        my $starttime = &Apache::lonhtmlcommon::get_date_from_form
            ('startdate_'.$i);
        my $endtime = &Apache::lonhtmlcommon::get_date_from_form
            ('enddate_'.$i);
        if (! defined($starttime) || ! defined($endtime)) {
            my $sec_in_day = 86400;
            my $last_sub_time = &get_time_from_row($PerformanceData->[-1]);
            my ($sday,$smon,$syear);
            (undef,undef,undef,$sday,$smon,$syear) = 
                localtime($last_sub_time - $sec_in_day*$i);
            $starttime = &Time::Local::timelocal(0,0,0,$sday,$smon,$syear);
            $endtime = $starttime + $sec_in_day;
            if ($i == ($num_plots -1 )) {
                $starttime = &get_time_from_row($PerformanceData->[0]);
            }
        }
        my $startdateform = &Apache::lonhtmlcommon::date_setter
            ('Statistics','startdate_'.$i,$starttime);
        my $enddateform = &Apache::lonhtmlcommon::date_setter
            ('Statistics','enddate_'.$i,$endtime);
        #
        my $begin_index;
        my $end_index;
        my $j;
        while (++$j < scalar(@$PerformanceData)) {
            last if (&get_time_from_row($PerformanceData->[$j]) > $starttime);
        }
        $begin_index = $j;
        while (++$j < scalar(@$PerformanceData)) {
            last if (&get_time_from_row($PerformanceData->[$j]) > $endtime);
        }
        $end_index = $j;
        ##
        my $interval = {
            begin_index => $begin_index,
            end_index   => $end_index,
            startdateform => $startdateform,
            enddateform   => $enddateform,
        };
        if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
            $table .= &Foil_Time_Analysis($PerformanceData,$ORdata,$Foils,
                                          $interval);
        } else {
            $table .= &Concept_Time_Analysis($PerformanceData,$ORdata,
                                             $Concepts,$interval);
        }
    }
    #
    return $table;
}

sub Foil_Time_Analysis {
    my ($PerformanceData,$ORdata,$Foils,$interval) = @_;
    my $analysis_html;
    my $foilkey = &build_option_index($ORdata);
    my ($begin_index,$end_index) = ($interval->{'begin_index'},
                                    $interval->{'end_index'});
    my %TimeData;
    #
    # Compute the number getting the foils correct or incorrects
    for (my $j=$begin_index;$j<=$end_index;$j++) {
        my $row = $PerformanceData->[$j];
        next if (! defined($row));
        my %Row = &Process_Row($row);
        while (my ($foilid,$href) = each(%Row)) {
            if (! ref($href)) {
                $TimeData{$foilid} += $href;
                next;
            }
            while (my ($option,$value) = each(%$href)) {
                $TimeData{$foilid}->{$option}+=$value;
            }
        }
    }
    my @Plotdata;
    foreach my $foil (@$Foils) {
        my $total = $TimeData{$foil}->{'_total'};
        if ($total == 0) {
            push(@{$Plotdata[0]},0);
        } else {
            push(@{$Plotdata[0]},
                 100 * $TimeData{$foil}->{'_correct'} / $total);
        }
        my $total_incorrect = $total - $TimeData{$foil}->{'_correct'};
        my $optionidx = 1;
        foreach my $option (@{$ORdata->{'Options'}}) {
            if ($total_incorrect == 0) {
                push(@{$Plotdata[$optionidx]},0);
            } else {
                push(@{$Plotdata[$optionidx]},
                     100 * $TimeData{$foil}->{$option} / $total_incorrect);
            }
        } continue {
            $optionidx++;
        }
    }
    #
    # Create the plot
    my $count = $end_index-$begin_index;
    my $title;
    if ($count == 0) {
        $title = 'no submissions';
    } elsif ($count == 1) {
        $title = 'one submission';
    } else {
        $title = $count.' submissions';
    }
    my $correctplot = &Apache::loncommon::DrawGraph($title,
                                                    'Foil Number',
                                                    'Percent Correct',
                                                    100,
                                                    $plotcolors,
                                                    $Plotdata[0]);
    for (my $j=0; $j< scalar(@{$Plotdata[0]});$j++) {
        $Plotdata[0]->[$j]=0;
    }
    $count = $end_index-$begin_index-$TimeData{'_correct'};
    if ($count == 0) {
        $title = 'no submissions';
    } elsif ($count == 1) {
        $title = 'one submission';
    } else {
        $title = $count.' submissions';
    }
    my $incorrectplot = &Apache::loncommon::DrawGraph($title,
                                                 'Foil Number',
                                                 'Incorrect Option Choice',
                                                 100,
                                                 $plotcolors,
                                                 @Plotdata);        
    $analysis_html.='<tr>'.
        '<td>'.$correctplot.'</td>'.
        '<td>'.$incorrectplot.'</td>'.
        '<td align="left" valign="top">'.$foilkey.'</td>'."</tr>\n";
    $analysis_html.= '<tr>'.'<td colspan="3">'.
        '<b>Start Time</b>:'.
        ' &nbsp;'.$interval->{'startdateform'}.'<br />'.
        '<b>End Time</b>&nbsp;&nbsp;: '.
        '&nbsp;'.$interval->{'enddateform'}.'<br />'.
#        '<b>Plot Title</b>&nbsp;&nbsp;:'.
#        ("&nbsp;"x3).$interval->{'titleform'}.
        '</td>'.
        "</tr>\n";
    return $analysis_html;
}

sub Concept_Time_Analysis {
    my ($PerformanceData,$ORdata,$Concepts,$interval) = @_;
    my $analysis_html;
    ##
    ## Determine starttime, endtime, startindex, endindex
    my ($begin_index,$end_index) = ($interval->{'begin_index'},
                                    $interval->{'end_index'});
    my %TimeData;
    #
    # Compute the number getting the foils correct or incorrects
    for (my $j=$begin_index;$j<=$end_index;$j++) {
        my $row = $PerformanceData->[$j];
        next if (! defined($row));
        my %Row = &Process_Row($row);
        while (my ($foilid,$href) = each(%Row)) {
            if (! ref($href)) {
                $TimeData{$foilid} += $href;
                next;
            }
            while (my ($option,$value) = each(%$href)) {
                $TimeData{$foilid}->{$option}+=$value;
            }
        }
    }
    #
    # Put the data in plottable form
    my @Plotdata;
    foreach my $concept (@$Concepts) {
        my ($total,$correct);
        foreach my $foil (@{$concept->{'foils'}}) {
            $total += $TimeData{$foil}->{'_total'};
            $correct += $TimeData{$foil}->{'_correct'};
        }
        if ($total == 0) {
            push(@Plotdata,0);
        } else {
            push(@Plotdata,100 * $correct / $total);
        }
    }
    #
    # Create the plot
    my $title = ($end_index - $begin_index).' submissions';
    my $correctplot = &Apache::loncommon::DrawGraph($title,
                                                    'Concept Number',
                                                    'Percent Correct',
                                                    100,
                                                    $plotcolors,
                                                    \@Plotdata);
    $analysis_html.='<tr>'.
        '<td>'.$correctplot.'</td>'.
        '<td align="left" valign="top">'.
        '<b>Start Time</b>: &nbsp;'.$interval->{'startdateform'}.'<br />'.
        '<b>End Time</b>&nbsp;&nbsp;: '.
           '&nbsp;'.$interval->{'enddateform'}.'<br />'.
#        '<b>Plot Title</b>&nbsp;&nbsp;:'.("&nbsp;"x3).
#            $interval->{'titleform'}.
        '</td>'.
        "</tr>\n";
    return $analysis_html;
}

#########################################################
#########################################################
##
##             Excel output 
##
#########################################################
#########################################################
sub prepare_excel_sheet {
    my ($PerformanceData,$ORdata) = @_;
    my (undef,$Foils,$Concepts) = &build_foil_index($ORdata);
}

sub process_data_for_excel_sheet {
    my ($PerformanceData,$Foils,$Concepts,$ORdata)=@_;
}

#########################################################
#########################################################
##
##             Interface 
##
#########################################################
#########################################################
sub CreateInterface {
    ##
    ## Environment variable initialization
    if (! exists$ENV{'form.AnalyzeOver'}) {
        $ENV{'form.AnalyzeOver'} = 'Tries';
    }
    ##
    ## Build the menu
    my $Str = '';
    $Str .= '<table cellspacing="5">'."\n";
    $Str .= '<tr>';
    $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
    $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
#    $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
    $Str .= '<td align="center">&nbsp;</td>';
    $Str .= '</tr>'."\n";
    ##
    ## 
    $Str .= '<tr><td align="center">'."\n";
    $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
    $Str .= '</td>';
    #
    $Str .= '<td align="center">';
    $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
    $Str .= '</td>';
    #
#    $Str .= '<td align="center">';
    my $only_seq_with_assessments = sub { 
        my $s=shift;
        if ($s->{'num_assess'} < 1) { 
            return 0;
        } else { 
            return 1;
        }
    };
    &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
                                              $only_seq_with_assessments);
    ##
    ##
    $Str .= '<td>';
    { # These braces are here to organize the code, not scope it.
        {
            $Str .= '<nobr>'.&mt('Analyze Over ');
            $Str .='<select name="AnalyzeOver" >';
            $Str .= '<option value="Tries" ';
            if (! exists($ENV{'form.AnalyzeOver'}) || 
                $ENV{'form.AnalyzeOver'} eq 'Tries'){
                # Default to Tries
                $Str .= ' selected ';
            }
            $Str .= '>'.&mt('Tries').'</option>';
            $Str .= '<option value="Time" ';
            $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
            $Str .= '>'.&mt('Time').'</option>';
            $Str .= '</select></nobr><br />';
        }
        {
            $Str .= '<nobr>'.&mt('Analyze as ');
            $Str .='<select name="AnalyzeAs" >';
            $Str .= '<option value="Concepts" ';
            if (! exists($ENV{'form.AnalyzeAs'}) || 
                $ENV{'form.AnalyzeAs'} eq 'Concepts'){
                # Default to Concepts
                $Str .= ' selected ';
            }
            $Str .= '>'.&mt('Concepts').'</option>';
            $Str .= '<option value="Foils" ';
            $Str .= ' selected ' if ($ENV{'form.AnalyzeAs'} eq 'Foils');
            $Str .= '>'.&mt('Foils').'</option>';
            $Str .= '</select></nobr><br />';
        }
        {
            $Str .= '<br /><nobr>'.&mt('Number of Plots:');
            $Str .= '<select name="NumPlots">';
            if (! exists($ENV{'form.NumPlots'}) 
                || $ENV{'form.NumPlots'} < 1 
                || $ENV{'form.NumPlots'} > 20) {
                $ENV{'form.NumPlots'} = 5;
            }
            foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
                $Str .= '<option value="'.$i.'" ';
                if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
                $Str .= '>'.$i.'</option>';
            }
            $Str .= '</select></nobr>';
        }
    }
    $Str .= '</td>';
    ##
    ##
    $Str .= '</tr>'."\n";
    $Str .= '</table>'."\n";
    return $Str;
}

sub OptionResponseProblemSelector {
    my $Str;
    $Str = "\n<table>\n";
    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
        next if ($seq->{'num_assess'}<1);
        my $seq_str = '';
        foreach my $res (@{$seq->{'contents'}}) {
            next if ($res->{'type'} ne 'assessment');
            foreach my $part (@{$res->{'parts'}}) {
                my $partdata = $res->{'partdata'}->{$part};
                if (! exists($partdata->{'option'}) || 
                    $partdata->{'option'} == 0) {
                    next;
                }
                for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
                    my $respid = $partdata->{'ResponseIds'}->[$i];
                    my $resptype = $partdata->{'ResponseTypes'}->[$i];
                    if ($resptype eq 'option') {
                        my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$part.':'.$respid);
                        my $checked = '';
                        if ($ENV{'form.problemchoice'} eq $value) {
                            $checked = 'checked ';
                        }
                        $seq_str .= '<tr><td>'.
  '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
  '</td><td>'.
  '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> ';
                        if ($partdata->{'option'} > 1) {
                            $seq_str .= &mt('response').' '.$respid;
                        }
                        $seq_str .= "</td></tr>\n";
                    }
                }
            }
        }
        if ($seq_str ne '') {
            $Str .= '<tr><td>&nbsp</td><td><b>'.$seq->{'title'}.'</b></td>'.
                "</tr>\n".$seq_str;
        }
    }
    $Str .= "</table>\n";
    return $Str;
}

#########################################################
#########################################################
##
##              Misc functions
##
#########################################################
#########################################################
sub get_problem_symb {
    my $problemstring = shift();
    my ($symb,$partid,$resid) = ($problemstring=~ /^(.*):([^:]*):([^:]*)$/);
    return ($symb,$partid,$resid);
}

sub get_resource_from_symb {
    my ($symb) = @_;
    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
        foreach my $res (@{$seq->{'contents'}}) {
            if ($res->{'symb'} eq $symb) {
                return $res;
            }
        }
    }
    return undef;
}

sub get_time_from_row {
    my ($row) = @_;
    if (ref($row)) {
        return $row->[3];
    } 
    return undef;
}

sub get_tries_from_row {
    my ($row) = @_;
    if (ref($row)) {
        return $row->[4];
    }
    return undef;
}

sub Process_Row {
    my ($row) = @_;
    my %RowData;
    my ($award,$grading,$submission,$time,$tries) = @$row;
    next if ($award eq 'MISSING_ANSWER');
    if ($award =~ /(APPROX_ANS|EXACT_ANS)/) {
        $RowData{'_correct'} = 1;
    }
    $RowData{'_total'} = 1;
    my @Foilgrades = split('&',$grading);
    my @Foilsubs   = split('&',$submission);
    for (my $j=0;$j<=$#Foilgrades;$j++) {
        my ($foilid,$correct)  = split('=',$Foilgrades[$j]);
        my (undef,$submission) = split('=',$Foilsubs[$j]);
        if ($correct) {
            $RowData{$foilid}->{'_correct'}++;
        } else {
            $submission = &Apache::lonnet::unescape($submission);
            $submission =~ s/\%20/ /g;
            $RowData{$foilid}->{$submission}++;
        }
        $RowData{$foilid}->{'_total'}++;
    }
    return %RowData;
}

##
## get problem data and put it into a useful data structure.
## note: we must force each foil and option to not begin or end with
##       spaces as they are stored without such data.
##
sub get_problem_data {
    my ($url) = @_;
    my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
    (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
    my %Answer;
    %Answer=&Apache::lonnet::str2hash($Answ);
    my %Partdata;
    foreach my $part (@{$Answer{'parts'}}) {
        while (my($key,$value) = each(%Answer)) {
            next if ($key !~ /^$part/);
            $key =~ s/^$part\.//;
            if (ref($value) eq 'ARRAY') {
                if ($key eq 'options') {
                    $Partdata{$part}->{'Options'}=$value;
                } elsif ($key eq 'concepts') {
                    $Partdata{$part}->{'Concepts'}=$value;
                } elsif ($key =~ /^concept\.(.*)$/) {
                    my $concept = $1;
                    foreach my $foil (@$value) {
                        $Partdata{$part}->{'Foils'}->{$foil}->{'Concept'}=
                                                                      $concept;
                    }
                }
            } else {
                if ($key=~ /^foil\.text\.(.*)$/) {
                    my $foil = $1;
                    $Partdata{$part}->{'Foils'}->{$foil}->{'name'}=$foil;
                    $Partdata{$part}->{'Foils'}->{$foil}->{'text'}=$value;
                } elsif ($key =~ /^foil\.value\.(.*)$/) {
                    my $foil = $1;
                    $Partdata{$part}->{'Foils'}->{$foil}->{'value'}=$value;
                }
            }
        }
    }
    return %Partdata;
}

1;

__END__

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