File:  [LON-CAPA] / loncom / interface / statistics / lonproblemanalysis.pm
Revision 1.57: download - view: text, annotated - select for diffs
Mon Jan 19 16:33:16 2004 UTC (20 years, 4 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
1. Changed most if not all option response related subroutines to begin
with 'OR'.
2. Placed code to list the type of the problem before it's chosen.  This will
be removed (or reworked) soon anyways.

# The LearningOnline Network with CAPA
#
# $Id: lonproblemanalysis.pm,v 1.57 2004/01/19 16:33:16 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();
use Spreadsheet::WriteExcel();

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

my @SubmitButtons = ({ name => 'PrevProblemAnalysis',
                       text => 'Previous Problem' },
                     { name => 'ProblemAnalysis',
                       text => 'Analyze Problem Again' },
                     { name => 'NextProblemAnalysis',
                       text => 'Next Problem' },
                     { name => 'break'},
                     { name => 'ClearCache',
                       text => 'Clear Caches' },
                     { name => 'updatecaches',
                       text => 'Update Student Data' },
                     { name => 'SelectAnother',
                       text => 'Choose a different Problem' },
                     { name => 'ExcelOutput',
                       text => 'Produce Excel Output' });

sub render_resource {
    my ($resource) = @_;
    ##
    ## 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;
    return '<table bgcolor="ffffff"><tr><td>'.
        '<base href="'.$base.'" />'.
        $rendered_problem.
        '</td></tr></table>';
}

sub BuildProblemAnalysisPage {
    my ($r,$c)=@_;
    #
    my %Saveable_Parameters = ('Status' => 'scalar',
                               'Section' => 'array',
                               'NumPlots' => 'scalar',
                               'AnalyzeAs' => 'scalar',
                               'AnalyzeOver' => 'scalar',
                               );
    &Apache::loncommon::store_course_settings('problem_analysis',
                                              \%Saveable_Parameters);
    &Apache::loncommon::restore_course_settings('problem_analysis',
                                                \%Saveable_Parameters);
    #
    &Apache::lonstatistics::PrepareClasslist();
    #
    $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
    $r->print(&CreateInterface());
    #
    my @Students = @Apache::lonstatistics::Students;
    #
    if (@Students < 1) {
        $r->print('<h2>There are no students in the sections selected</h2>');
    }
    #
    &Apache::loncoursedata::clear_internal_caches();
    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'})) {
        foreach my $button (@SubmitButtons) {
            if ($button->{'name'} eq 'break') {
                $r->print("<br />\n");
            } else {
                $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
                          'value="'.&mt($button->{'text'}).'" />');
                $r->print('&nbsp;'x5);
            }
        }
        #
        $r->print('<hr />');
        $r->rflush();
        #
        # Determine which problem we are to analyze
        my $current_problem = &get_target_from_id($ENV{'form.problemchoice'});
        #
        my ($prev,$curr,$next) = &get_prev_curr_next($current_problem);
        if (exists($ENV{'form.PrevProblemAnalysis'}) && defined($prev)) {
            $current_problem = $prev;
        } elsif (exists($ENV{'form.NextProblemAnalysis'}) && defined($next)) {
            $current_problem = $next;
        } else {
            $current_problem = $curr;
        }
        #
        # Store the current problem choice and send it out in the form
        $ENV{'form.problemchoice'} = &make_target_id($current_problem);
        $r->print('<input type="hidden" name="problemchoice" value="'.
                  $ENV{'form.problemchoice'}.'" />');
        #
        if (! defined($current_problem->{'resource'})) {
            $r->print('resource is undefined');
        } else {
            my $resource = $current_problem->{'resource'};
            $r->print('<h1>'.$resource->{'title'}.'</h1>');
            $r->print('<h3>'.$resource->{'src'}.'</h3>');
            $r->print(&render_resource($resource));
            $r->rflush();
            my %Data = &get_problem_data($resource->{'src'});
            my $ProblemData = $Data{$current_problem->{'part'}.
                                    '.'.
                                    $current_problem->{'respid'}};
            if ($current_problem->{'resptype'} eq 'option') {
                &OptionResponseAnalysis($r,$current_problem,
                                        $ProblemData,
                                        \@Students);
            } elsif ($current_problem->{'resptype'} eq 'radiobutton') {
                &RadioResponseAnalysis($r,$current_problem,
                                       $ProblemData,
                                       \@Students);
            } else {
                $r->print('<h2>This analysis is not supported</h2>');
            }
        }
        $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(&ProblemSelector());
    }
}


#########################################################
#########################################################
##
##      Radio Response Routines
##
#########################################################
#########################################################
sub RadioResponseAnalysis {
    my ($r,$problem,$ProblemData,$Students) = @_;
    my ($resource,$respid) = ($problem->{'resource'},
                                    $problem->{'respid'});
    my $analysis_html;
    my $PerformanceData = 
        &Apache::loncoursedata::get_response_data
        ($Students,$resource->{'symb'},$respid);
    if (! defined($PerformanceData) || 
        ref($PerformanceData) ne 'ARRAY' ) {
        $analysis_html = '<h2>'.
            &mt('There is no submission data for this resource').
            '</h2>';
        $r->print($analysis_html);
        return;
    }
    if (exists($ENV{'form.ExcelOutput'})) {
        $analysis_html .= &RR_Excel_output($r,$problem->{'resource'},
                                           $PerformanceData,$ProblemData);
    } elsif ($ENV{'form.AnalyzeOver'} eq 'Tries') {
        $analysis_html .= &RR_Tries_Analysis($r,$problem->{'resource'},
                                             $PerformanceData,$ProblemData);
    } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
        $analysis_html .= &RR_Time_Analysis($r,$problem->{'resource'},
                                            $PerformanceData,$ProblemData);
    } else {
        $analysis_html .= '<h2>'.
           &mt('The analysis you have selected is not supported at this time').
           '</h2>';
    }
    $r->print($analysis_html);
}

sub RR_Excel_output   { 
    my ($r,$PerformanceData,$ProblemData) = @_;
    return '<h1>No!</h1>';
}

sub RR_Tries_Analysis { 
    my ($r,$resource,$PerformanceData,$ProblemData) = @_;
    my $analysis_html;
    my $mintries = 1;
    my $maxtries = $ENV{'form.NumPlots'};
    my ($table,$Foils,$Concepts) = &build_foil_index($ProblemData);
    if ((! defined($Concepts)) || ((@$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';
    }
    $analysis_html .= $table;
    my @TryData = &RR_tries_data_analysis($r,$PerformanceData);
#    if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
        $analysis_html .= &RR_Tries_Foil_Analysis($mintries,$maxtries,$Foils,
                                                 \@TryData,$ProblemData);
#    } else {
#        $analysis_html = &RR_Tries_Concept_Analysis($mintries,$maxtries,
#                                                    $Concepts,
#                                                    \@TryData,
#                                                    $ProblemData);
#    }
    return $analysis_html;
}

sub RR_tries_data_analysis {
    my ($r,$Attempt_data) = @_;
    my @TryData;
    foreach my $attempt (@$Attempt_data) {
        my %Attempt = &hashify_attempt($attempt);
        my ($answer,undef) = split('=',$Attempt{'submission'});
        $TryData[$Attempt{'tries'}]->{$answer}++;
    }
    return @TryData;
}

sub RR_Time_Analysis  { 
    my ($r,$PerformanceData,$ProblemData) = @_;
    my $html;
    return $html;
}

sub RR_Tries_Foil_Analysis {
    my ($min,$max,$Foils,$TryData,$ProblemData) = @_;
    my $html;
    #
    # Compute the data neccessary to make the plots
    for (my $try=$min;$try<=$max;$try++) {
        my @PlotData_Correct; 
        my @PlotData_Incorrect;
        next if ($try > scalar(@{$TryData}));
        next if (! defined($TryData->[$try]));
        my %DataSet = %{$TryData->[$try]};
        my $total = 0;
        foreach my $foilid (@$Foils) {
            $total += $DataSet{$foilid};
        }
        foreach my $foilid (@$Foils) {
            if ($total == 0) {
                push (@PlotData_Correct,0);
                push (@PlotData_Incorrect,0);
            } else {
                if ($ProblemData->{'_Foils'}->{$foilid}->{'value'} eq 'true') {
                    push (@PlotData_Correct,
                          int(100*$DataSet{$foilid}/$total));
                    push (@PlotData_Incorrect,0);
                } else {
                    push (@PlotData_Correct,0);
                    push (@PlotData_Incorrect,
                          int(100*$DataSet{$foilid}/$total));
                }
            }
        }
        my $title='Attempt '.$try;
        my $xlabel = $total.' Submissions';
        $html.=  &Apache::loncommon::DrawBarGraph($title,
                                                  $xlabel,
                                                  'Percent Choosing',
                                                  100,
                                                  ['#33ff00','#ff3300'],
                                                  \@PlotData_Correct,
                                                  \@PlotData_Incorrect);
    }
    return $html;
}

sub RR_Tries_Concept_Analysis {
    my ($min,$max,$Concepts,$ResponseData,$ProblemData) = @_;
    my $html;
    return $html;
}

sub RR_Time_Foil_Analysis {
    my ($min,$max,$Foils,$ResponseData,$ProblemData) = @_;
    my $html;
    return $html;
}

sub RR_Time_Concept_Analysis {
    my ($min,$max,$Concepts,$ResponseData,$ProblemData) = @_;
    my $html;
    return $html;
}


sub get_Radio_problem_data {
    my ($url) = @_;
    my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
    (my $garbage,$Answ)=split('_HASH_REF__',$Answ,2);
    my %Answer = &Apache::lonnet::str2hash($Answ);
    my %Partdata;
    foreach my $part (@{$Answer{'parts'}}) {
        while (my($key,$value) = each(%Answer)) {
#            if (ref($value) eq 'ARRAY') {
#                &Apache::lonnet::logthis('is ref part:'.$part.' '.$key.'='.join(',',@$value));
#            } else {
#                &Apache::lonnet::logthis('notref part:'.$part.' '.$key.'='.$value);
#            }                
            next if ($key !~ /^$part/);
            $key =~ s/^$part\.//;
            if ($key eq 'foils') {
                $Partdata{$part}->{'_Foils'}=$value;
            } elsif ($key eq 'options') {
                $Partdata{$part}->{'_Options'}=$value;
            } elsif ($key eq 'shown') {
                $Partdata{$part}->{'_Shown'}=$value;
            } elsif ($key =~ /^foil.value.(.*)$/) {
                $Partdata{$part}->{$1}->{'value'}=$value;
            } elsif ($key =~ /^foil.text.(.*)$/) {
                $Partdata{$part}->{$1}->{'text'}=$value;
            }
        }
    }
    return %Partdata;
}

#########################################################
#########################################################
##
##      Option Response Routines
##
#########################################################
#########################################################
sub OptionResponseAnalysis {
    my ($r,$problem,$ProblemData,$Students) = @_;
    my ($resource,$respid) = ($problem->{'resource'},
                              $problem->{'respid'});
    # Note: part data is not needed.
    my $PerformanceData = 
        &Apache::loncoursedata::get_response_data
        ($Students,$resource->{'symb'},$respid);
    if (! defined($PerformanceData) || 
        ref($PerformanceData) ne 'ARRAY' ) {
        $r->print('<h2>'.
                  &mt('There is no student data for this problem.').
                  '</h2>');
    }  else {
        $r->rflush();
        if (exists($ENV{'form.ExcelOutput'})) {
            my $result = &OR_excel_sheet($r,$resource,
                                         $PerformanceData,
                                         $ProblemData);
            $r->print($result);
            $r->rflush();
        } else {
            if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
                my $analysis_html = &OR_tries_analysis($r,
                                                    $PerformanceData,
                                                    $ProblemData);
                $r->print($analysis_html);
                $r->rflush();
            } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
                my $analysis_html = &OR_time_analysis($PerformanceData,
                                                   $ProblemData);
                $r->print($analysis_html);
                $r->rflush();
            } else {
                $r->print('<h2>'.
                          &mt('The analysis you have selected is '.
                              'not supported at this time').
                          '</h2>');
            }
        }
    }
}

#########################################################
#
#       Option Response:  Tries Analysis
#
#########################################################
sub OR_tries_analysis {
    my ($r,$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 = &OR_analyze_by_tries($r,$PerformanceData,
                                                     $mintries,$maxtries);
    my $analysis = '';
    if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
        $analysis = &OR_Tries_Foil_Analysis($mintries,$maxtries,$Foils,
                                         \%ResponseData,$ORdata);
    } else {
        $analysis = &OR_Tries_Concept_Analysis($mintries,$maxtries,
                                            $Concepts,\%ResponseData,$ORdata);
    }
    $table .= $analysis;
    return $table;
}

sub OR_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::DrawBarGraph
            ($title,'Foil Number','Percent Correct',
             100,$plotcolors,$Datasets[0]);
        $analysis_html.= '<tr><td>'.$correctgraph.'</td>';
        ##
        ##
        next if (! defined($Datasets[0]));
        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::DrawBarGraph
            ($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 OR_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::DrawBarGraph
            ($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 OR_analyze_by_tries {
    my ($r,$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_OR_Row($row);
        next if (! %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;
}

#########################################################
#
#     Option Response: Time Analysis
#
#########################################################
sub OR_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 .= &OR_Foil_Time_Analysis($PerformanceData,$ORdata,$Foils,
                                          $interval);
        } else {
            $table .= &OR_Concept_Time_Analysis($PerformanceData,$ORdata,
                                             $Concepts,$interval);
        }
    }
    #
    return $table;
}

sub OR_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_OR_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::DrawBarGraph($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::DrawBarGraph($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 OR_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_OR_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::DrawBarGraph($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 OR_excel_sheet {
    my ($r,$resource,$PerformanceData,$ORdata) = @_;
    my $response = '';
    my (undef,$Foils,$Concepts) = &build_foil_index($ORdata);
    #
    # Create excel worksheet
    my $filename = '/prtspool/'.
        $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
        time.'_'.rand(1000000000).'.xls';
    my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
    if (! defined($workbook)) {
        $r->log_error("Error creating excel spreadsheet $filename: $!");
        $r->print('<p>'.&mt("Unable to create new Excel file.  ".
                            "This error has been logged.  ".
                            "Please alert your LON-CAPA administrator").
                  '</p>');
        return undef;
    }
    #
    $workbook->set_tempdir('/home/httpd/perl/tmp');
    #
    # Define some potentially useful formats
    my $format;
    $format->{'header'} = $workbook->add_format(bold      => 1, 
                                                bottom    => 1,
                                                align     => 'center');
    $format->{'bold'} = $workbook->add_format(bold=>1);
    $format->{'h1'}   = $workbook->add_format(bold=>1, size=>18);
    $format->{'h2'}   = $workbook->add_format(bold=>1, size=>16);
    $format->{'h3'}   = $workbook->add_format(bold=>1, size=>14);
    $format->{'date'} = $workbook->add_format(num_format=>
                                              'mmm d yyyy hh:mm AM/PM');
    #
    # Create and populate main worksheets
    my $problem_data_sheet  = $workbook->addworksheet('Problem Data');
    my $student_data_sheet  = $workbook->addworksheet('Student Data');
    my $response_data_sheet = $workbook->addworksheet('Response Data');
    foreach my $sheet ($problem_data_sheet,$student_data_sheet,
                       $response_data_sheet) {
        $sheet->write(0,0,$resource->{'title'},$format->{'h2'});
        $sheet->write(1,0,$resource->{'src'},$format->{'h3'});
    }
    #
    my $result;
    $result = &build_problem_data_worksheet($problem_data_sheet,$format,
                                            $Concepts,$ORdata);
    if ($result ne 'okay') {
        # Do something useful
    }
    $result = &build_student_data_worksheet($student_data_sheet,$format);
    if ($result ne 'okay') {
        # Do something useful
    }
    $result = &build_response_data_worksheet($response_data_sheet,$format,
                                             $PerformanceData,$Foils,
                                             $ORdata);
    if ($result ne 'okay') {
        # Do something useful
    }
    $response_data_sheet->activate();
    #
    # Close the excel file
    $workbook->close();
    #
    # Write a link to allow them to download it
    $result .= '<h2>'.&mt('Excel Raw Data Output').'</h2>'.
              '<p><a href="'.$filename.'">'.
              &mt('Your Excel spreadsheet.').
              '</a></p>'."\n";
    return $result;
}

sub OR_build_problem_data_worksheet {
    my ($worksheet,$format,$Concepts,$ORdata) = @_;
    my $rows_output = 3;
    my $cols_output = 0;
    $worksheet->write($rows_output++,0,'Problem Structure',$format->{'h3'});
    ##
    ##
    my @Headers;
    if (@$Concepts > 1) {
        @Headers = ("Concept\nNumber",'Concept',"Foil\nNumber",
                    'Foil Name','Foil Text','Correct value');
    } else {
        @Headers = ('Foil Number','FoilName','Foil Text','Correct value');
    }
    $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
    my %Foildata = %{$ORdata->{'_Foils'}};
    my $conceptindex = 1;
    my $foilindex = 1;
    foreach my $concept (@$Concepts) {
        my @FoilsInConcept = @{$concept->{'foils'}};
        my $firstfoil = shift(@FoilsInConcept);
        if (@$Concepts > 1) {
            $worksheet->write_row($rows_output++,0,
                                  [$conceptindex,
                                   $concept->{'name'},
                                   $foilindex++,
                                   $Foildata{$firstfoil}->{'name'},
                                   $Foildata{$firstfoil}->{'text'},
                                   $Foildata{$firstfoil}->{'value'},]);
        } else {
            $worksheet->write_row($rows_output++,0,
                                  [ $foilindex++,
                                    $Foildata{$firstfoil}->{'name'},
                                    $Foildata{$firstfoil}->{'text'},
                                    $Foildata{$firstfoil}->{'value'},]);
        }
        foreach my $foilid (@FoilsInConcept) {
            if (@$Concepts > 1) {
                $worksheet->write_row($rows_output++,0,
                                      ['',
                                       '',
                                       $foilindex,
                                       $Foildata{$foilid}->{'name'},
                                       $Foildata{$foilid}->{'text'},
                                       $Foildata{$foilid}->{'value'},]);
            } else {
                $worksheet->write_row($rows_output++,0,                
                                      [$foilindex,
                                       $Foildata{$foilid}->{'name'},
                                       $Foildata{$foilid}->{'text'},
                                       $Foildata{$foilid}->{'value'},]);
            }                
        } continue {
            $foilindex++;
        }
    } continue {
        $conceptindex++;
    }
    $rows_output++;
    $rows_output++;
    ##
    ## Option data output
    $worksheet->write($rows_output++,0,'Options',$format->{'header'});
    foreach my $string (@{$ORdata->{'_Options'}}) {
        $worksheet->write($rows_output++,0,$string);
    }
    return 'okay';
}

sub OR_build_student_data_worksheet {
    my ($worksheet,$format) = @_;
    my $rows_output = 3;
    my $cols_output = 0;
    $worksheet->write($rows_output++,0,'Student Data',$format->{'h3'});
    my @Headers = ('full name','username','domain','section',
                   "student\nnumber",'identifier');
    $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
    my @Students = @Apache::lonstatistics::Students;
    my $studentrows = &Apache::loncoursedata::get_student_data(\@Students);
    my %ids;
    foreach my $row (@$studentrows) {
        my ($mysqlid,$student) = @$row;
        $ids{$student}=$mysqlid;
    }
    foreach my $student (@Students) {
        my $name_domain = $student->{'username'}.':'.$student->{'domain'};
        $worksheet->write_row($rows_output++,0,
                          [$student->{'fullname'},
                           $student->{'username'},$student->{'domain'},
                           $student->{'section'},$student->{'id'},
                           $ids{$name_domain}]);
    }
    return;
}

sub OR_build_response_data_worksheet {
    my ($worksheet,$format,$PerformanceData,$Foils,$ORdata)=@_;
    my $rows_output = 3;
    my $cols_output = 0;
    $worksheet->write($rows_output++,0,'Response Data',$format->{'h3'});
    $worksheet->set_column(1,1,20);
    $worksheet->set_column(2,2,13);
    my @Headers = ('identifier','time','award detail','attempt');
    foreach my $foil (@$Foils) {
        push (@Headers,$foil.' submission');
        push (@Headers,$foil.' grading');
    }
    $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
    #
    foreach my $row (@$PerformanceData) {
        next if (! defined($row));
        my ($student,$award,$grading,$submission,$time,$tries) = @$row;
        my @Foilgrades = split('&',$grading);
        my @Foilsubs   = split('&',$submission);
        my %ResponseData;
        for (my $j=0;$j<=$#Foilgrades;$j++) {
            my ($foilid,$correct)  = split('=',$Foilgrades[$j]);
            my (undef,$submission) = split('=',$Foilsubs[$j]);
            $submission = &Apache::lonnet::unescape($submission);
            $ResponseData{$foilid.' submission'}=$submission;
            $ResponseData{$foilid.' award'}=$correct;
        }
        $worksheet->write($rows_output,$cols_output++,$student);
        $worksheet->write($rows_output,$cols_output++,
                          &calc_serial($time),$format->{'date'});
        $worksheet->write($rows_output,$cols_output++,$award);
        $worksheet->write($rows_output,$cols_output++,$tries);
        foreach my $foilid (@$Foils) {
            $worksheet->write($rows_output,$cols_output++,
                              $ResponseData{$foilid.' submission'});
            $worksheet->write($rows_output,$cols_output++,
                              $ResponseData{$foilid.' award'});
        }
        $rows_output++;
        $cols_output = 0;
    }
    return;
}


##
## The following is copied from datecalc1.pl, part of the 
## Spreadsheet::WriteExcel CPAN module.
##
##
######################################################################
#
# Demonstration of writing date/time cells to Excel spreadsheets,
# using UNIX/Perl time as source of date/time.
#
# Copyright 2000, Andrew Benham, adsb@bigfoot.com
#
######################################################################
#
# UNIX/Perl time is the time since the Epoch (00:00:00 GMT, 1 Jan 1970)
# measured in seconds.
#
# An Excel file can use exactly one of two different date/time systems.
# In these systems, a floating point number represents the number of days
# (and fractional parts of the day) since a start point. The floating point
# number is referred to as a 'serial'.
# The two systems ('1900' and '1904') use different starting points:
#  '1900'; '1.00' is 1 Jan 1900 BUT 1900 is erroneously regarded as
#          a leap year - see:
#            http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
#          for the excuse^H^H^H^H^H^Hreason.
#  '1904'; '1.00' is 2 Jan 1904.
#
# The '1904' system is the default for Apple Macs. Windows versions of
# Excel have the option to use the '1904' system.
#
# Note that Visual Basic's "DateSerial" function does NOT erroneously
# regard 1900 as a leap year, and thus its serials do not agree with
# the 1900 serials of Excel for dates before 1 Mar 1900.
#
# Note that StarOffice (at least at version 5.2) does NOT erroneously
# regard 1900 as a leap year, and thus its serials do not agree with
# the 1900 serials of Excel for dates before 1 Mar 1900.
#
######################################################################
#
# Calculation description
# =======================
#
# 1900 system
# -----------
# Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 70 years after 1 Jan 1900.
# Of those 70 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)
# were leap years with an extra day.
# Thus there were 17 + 70*365 days = 25567 days between 1 Jan 1900 and
# 1 Jan 1970.
# In the 1900 system, '1' is 1 Jan 1900, but as 1900 was not a leap year
# 1 Jan 1900 should really be '2', so 1 Jan 1970 is '25569'.
#
# 1904 system
# -----------
# Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 66 years after 1 Jan 1904.
# Of those 66 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)
# were leap years with an extra day.
# Thus there were 17 + 66*365 days = 24107 days between 1 Jan 1904 and
# 1 Jan 1970.
# In the 1904 system, 2 Jan 1904 being '1', 1 Jan 1970 is '24107'.
#
######################################################################
#
# Copyright (c) 2000, Andrew Benham.
# This program is free software. It may be used, redistributed and/or
# modified under the same terms as Perl itself.
#
# Andrew Benham, adsb@bigfoot.com
# London, United Kingdom
# 11 Nov 2000
#
######################################################################

# Use 1900 date system on all platforms other than Apple Mac (for which
# use 1904 date system).
my $DATE_SYSTEM = ($^O eq 'MacOS') ? 1 : 0;

#-----------------------------------------------------------
# calc_serial()
#
# Called with (up to) 2 parameters.
#   1.  Unix timestamp.  If omitted, uses current time.
#   2.  GMT flag. Set to '1' to return serial in GMT.
#       If omitted, returns serial in appropriate timezone.
#
# Returns date/time serial according to $DATE_SYSTEM selected
#-----------------------------------------------------------
sub calc_serial {
        my $time = (defined $_[0]) ? $_[0] : time();
        my $gmtflag = (defined $_[1]) ? $_[1] : 0;

        # Divide timestamp by number of seconds in a day.
        # This gives a date serial with '0' on 1 Jan 1970.
        my $serial = $time / 86400;

        # Adjust the date serial by the offset appropriate to the
        # currently selected system (1900/1904).
        if ($DATE_SYSTEM == 0) {        # use 1900 system
                $serial += 25569;
        } else {                        # use 1904 system
                $serial += 24107;
        }

        unless ($gmtflag) {
                # Now have a 'raw' serial with the right offset. But this
                # gives a serial in GMT, which is false unless the timezone
                # is GMT. We need to adjust the serial by the appropriate
                # timezone offset.
                # Calculate the appropriate timezone offset by seeing what
                # the differences between localtime and gmtime for the given
                # time are.

                my @gmtime = gmtime($time);
                my @ltime  = localtime($time);

                # For the first 7 elements of the two arrays, adjust the
                # date serial where the elements differ.
                for (0 .. 6) {
                        my $diff = $ltime[$_] - $gmtime[$_];
                        if ($diff) {
                                $serial += _adjustment($diff,$_);
                        }
                }
        }

        # Perpetuate the error that 1900 was a leap year by decrementing
        # the serial if we're using the 1900 system and the date is prior to
        # 1 Mar 1900. This has the effect of making serial value '60'
        # 29 Feb 1900.

        # This fix only has any effect if UNIX/Perl time on the platform
        # can represent 1900. Many can't.

        unless ($DATE_SYSTEM) {
                $serial-- if ($serial < 61);    # '61' is 1 Mar 1900
        }
        return $serial;
}

sub _adjustment {
        # Based on the difference in the localtime/gmtime array elements
        # number, return the adjustment required to the serial.

        # We only look at some elements of the localtime/gmtime arrays:
        #    seconds    unlikely to be different as all known timezones
        #               have an offset of integral multiples of 15 minutes,
        #               but it's easy to do.
        #    minutes    will be different for timezone offsets which are
        #               not an exact number of hours.
        #    hours      very likely to be different.
        #    weekday    will differ when localtime/gmtime difference
        #               straddles midnight.
        #
        # Assume that difference between localtime and gmtime is less than
        # 5 days, then don't have to do maths for day of month, month number,
        # year number, etc...

        my ($delta,$element) = @_;
        my $adjust = 0;

        if ($element == 0) {            # Seconds
                $adjust = $delta/86400;         # 60 * 60 * 24
        } elsif ($element == 1) {       # Minutes
                $adjust = $delta/1440;          # 60 * 24
        } elsif ($element == 2) {       # Hours
                $adjust = $delta/24;            # 24
        } elsif ($element == 6) {       # Day of week number
                # Catch difference straddling Sat/Sun in either direction
                $delta += 7 if ($delta < -4);
                $delta -= 7 if ($delta > 4);

                $adjust = $delta;
        }
        return $adjust;
}

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) {
        &Apache::lonnet::logthis('foilid = '.$foilid);
        &Apache::lonnet::logthis('_Concept = '.$Foildata{$foilid}->{'_Concept'});
        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 = lc($a); 
        my $b1 = lc($b);
        if (exists($Numbers{$a1})) {
            $a = $Numbers{$a1};
        }
        if (exists($Numbers{$b1})) {
            $b = $Numbers{$b1};
        }
        if (($a =~/^\d+$/) && ($b =~/^\d+$/)) {
            return $a <=> $b;
        } else {
            return $a cmp $b;
        }
    };
    my @Concepts;
    foreach my $concept (sort $sortfunction (keys(%Concepts))) {
        if (! defined($Concepts{$concept})) {
            $Concepts{$concept}=[];
            &Apache::lonnet::logthis('concept error: '.$concept.' does not have a value');
#            next;
        }
        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";
}

#########################################################
#########################################################
##
##   Generic Interface Routines
##
#########################################################
#########################################################
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 .= &Apache::loncommon::help_open_topic
                                                  ('Analysis_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>';
            $Str .= '</nobr><br />';
        }
        {
            $Str .= '<nobr>'.&mt('Analyze as ');
            $Str .= &Apache::loncommon::help_open_topic
                                                  ('Analysis_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 .= &Apache::loncommon::help_open_topic
                                                  ('Analysis_num_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 ProblemSelector {
    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};
#                &Apache::lonnet::logthis('----------------');
#                while (my ($k,$v)=each(%$partdata)) {
#                    if (ref($v) eq 'ARRAY') {
#                        &Apache::lonnet::logthis($k.' = '.join(',',@$v));
#                    } else {
#                        &Apache::lonnet::logthis($k.' = '.$v);
#                    }
#                }
                if ((! exists($partdata->{'option'}) || 
                     $partdata->{'option'} == 0      ) &&
                    (! exists($partdata->{'radiobutton'}) ||
                     $partdata->{'radiobutton'} == 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' ){
                    if ($resptype eq 'option' || $resptype eq 'radiobutton') {
                        my $value = &make_target_id({symb=>$res->{'symb'},
                                                     part=>$part,
                                                     respid=>$respid,
                                                     resptype=>$resptype});
                        my $checked = '';
                        if ($ENV{'form.problemchoice'} eq $value) {
                            $checked = 'checked ';
                        }
                        my $title = $res->{'title'};
                        if (! defined($title) || $title eq '') {
                            ($title) = ($res->{'src'} =~ m:/([^/]*)$:);
                        }
                        $seq_str .= '<tr><td>'.
  '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
  '</td><td>'.          
  $resptype.'</td><td>'.
  '<a href="'.$res->{'src'}.'">'.$title.'</a> ';
#  '<a href="'.$res->{'src'}.'">'.$resptype.' '.$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 colspan="2"><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,$respid,$resptype) = split(':',$problemstring);
    return ({ symb   => $symb,
              part   => $partid,
              respid => $respid,
              type   => $resptype } );
}

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_prev_curr_next {
    my ($target) = @_;
    #
    # Build an array with the data we need to search through
    my @Resource;
    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
        foreach my $res (@{$seq->{'contents'}}) {
            next if ($res->{'type'} ne 'assessment');
            foreach my $part (@{$res->{'parts'}}) {
                my $partdata = $res->{'partdata'}->{$part};
                for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
                    my $respid = $partdata->{'ResponseIds'}->[$i];
                    my $resptype = $partdata->{'ResponseTypes'}->[$i];
                    next if ($resptype ne 'option' && 
                             $resptype ne 'radiobutton');
                    push (@Resource,
                            { symb     => $res->{symb},
                              part     => $part,
                              respid   => $partdata->{'ResponseIds'}->[$i],
                              resource => $res,
                              resptype => $resptype
                            } );
                }
            }
        }
    }
    #
    #
    # Get the index of the current situation
    my $curr_idx;
    for ($curr_idx=0;$curr_idx<$#Resource;$curr_idx++) {
        my $curr_item = $Resource[$curr_idx];
        last if ($curr_item->{'symb'} eq $target->{'symb'} &&
                 $curr_item->{'part'} eq $target->{'part'} &&
                 $curr_item->{'respid'} eq $target->{'respid'} &&
                 $curr_item->{'resptype'} eq $target->{'resptype'});
    }
    my $curr_item = $Resource[$curr_idx];
    if ($curr_item->{'symb'}     ne $target->{'symb'} ||
        $curr_item->{'part'}     ne $target->{'part'} ||
        $curr_item->{'respid'}   ne $target->{'respid'} ||
        $curr_item->{'resptype'} ne $target->{'resptype'}){
        # bogus symb - return nothing
        return (undef,undef,undef);
    }
    #
    # Now just pick up the data we need
    my ($prev,$curr,$next);
    if ($curr_idx == 0) {
        $prev = undef;
        $curr = $Resource[$curr_idx  ];
        $next = $Resource[$curr_idx+1];
    } elsif ($curr_idx == $#Resource) {
        $prev = $Resource[$curr_idx-1];
        $curr = $Resource[$curr_idx  ];
        $next = undef;
    } else {
        $prev = $Resource[$curr_idx-1];
        $curr = $Resource[$curr_idx  ];
        $next = $Resource[$curr_idx+1];
    }
    return ($prev,$curr,$next);
}

sub make_target_id {
    my ($target) = @_;
    my $id = &Apache::lonnet::escape($target->{'symb'}).':'.
             &Apache::lonnet::escape($target->{'part'}).':'.
             &Apache::lonnet::escape($target->{'respid'}).':'.
             &Apache::lonnet::escape($target->{'resptype'});
    return $id;
}

sub get_target_from_id {
    my ($id) = @_;
    my ($symb,$part,$respid,$resptype) = split(':',$id);
    return ({ symb    =>&Apache::lonnet::unescape($symb),
             part     =>&Apache::lonnet::unescape($part),
             respid   =>&Apache::lonnet::unescape($respid),
             resptype =>&Apache::lonnet::unescape($resptype)});
}

#########################################################
#########################################################
##
##              Misc Option Response functions
##
#########################################################
#########################################################
sub get_time_from_row {
    my ($row) = @_;
    if (ref($row)) {
        return $row->[&Apache::loncoursedata::RD_timestamp()];
    } 
    return undef;
}

sub get_tries_from_row {
    my ($row) = @_;
    if (ref($row)) {
        return $row->[&Apache::loncoursedata::RD_tries()];
    }
    return undef;
}

sub hashify_attempt {
    my ($row) = @_;
    my %attempt;
    $attempt{'tries'}      = $row->[&Apache::loncoursedata::RD_tries()];
    $attempt{'submission'} = $row->[&Apache::loncoursedata::RD_submission()];
    $attempt{'award'}      = $row->[&Apache::loncoursedata::RD_awarddetail()];
    $attempt{'timestamp'}  = $row->[&Apache::loncoursedata::RD_timestamp()];
    return %attempt;
}

sub Process_OR_Row {
    my ($row) = @_;
    my %RowData;
    my $student_id = $row->[&Apache::loncoursedata::RD_student_id()];
    my $award      = $row->[&Apache::loncoursedata::RD_awarddetail()];
    my $grading    = $row->[&Apache::loncoursedata::RD_response_eval()];
    my $submission = $row->[&Apache::loncoursedata::RD_submission()];
    my $time       = $row->[&Apache::loncoursedata::RD_timestamp()];
    my $tries      = $row->[&Apache::loncoursedata::RD_tries()];
    return undef 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);
            $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;
                    $value =~ s/(\s*$|^\s*)//g;
                    $Partdata{$part}->{'_Foils'}->{$foil}->{'text'}=$value;
                } elsif ($key =~ /^foil\.value\.(.*)$/) {
                    my $foil = $1;
                    $Partdata{$part}->{'_Foils'}->{$foil}->{'value'}=$value;
                }
            }
        }
    }
    return %Partdata;
}

1;

__END__

#####
# partdata{part}->{_Foils}->{foilid}->{'name'}     = $
#                                   ->{'text'}     = $
#                                   ->{'value'}    = $
#                                   ->{'_Concept'} = $
# partdata{part}->{_Options}  = @
# partdata{part}->{_Concepts} = @

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