File:  [LON-CAPA] / loncom / interface / statistics / lonsubmissiontimeanalysis.pm
Revision 1.27: download - view: text, annotated - select for diffs
Fri Aug 18 15:15:38 2006 UTC (17 years, 9 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_2_X, version_2_2_99_0, version_2_2_2, version_2_2_1, version_2_2_0, HEAD
Bug 4954.  Filter title changed from "Enrollment Status" to "Access Status".  Access Status selections in lonhtmlcommon set to:
Currently Has Access
Will Have Future Access
Previously Had Access
Any Access Status
See comment appended to bug 4954 for more information.
Documentation updated to include ability to selectively display students with future access.

# The LearningOnline Network with CAPA
#
# $Id: lonsubmissiontimeanalysis.pm,v 1.27 2006/08/18 15:15:38 raeburn 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::lonsubmissiontimeanalysis;

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

my $plotcolors = ['#33ff00', 
                  '#ff33cc', '#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 => 'SelectAnother',
                       text => 'Choose a different Problem' },
                     );

sub BuildSubmissionTimePage {
    my ($r,$c)=@_;
    #
    my %Saveable_Parameters = ('Status' => 'scalar',
                               'Section' => 'array');
    &Apache::loncommon::store_course_settings('submissiontime_analysis',
                                              \%Saveable_Parameters);
    &Apache::loncommon::restore_course_settings('submissiontime_analysis',
                                                \%Saveable_Parameters);
    #
    &Apache::lonstatistics::PrepareClasslist();    
    #
    $r->print(&CreateInterface());
    #
    my @Students = @Apache::lonstatistics::Students;
    #
    if (@Students < 1) {
        $r->print('<h2>There are no students in the sections selected</h2>');
    }
    #
    my @CacheButtonHTML = 
        &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
    $r->rflush();
    #
    if (! exists($env{'form.problemchoice'}) ||
        exists($env{'form.SelectAnother'})) {
        my $submit_button = '<input type="submit" name="" value="'.
            &mt('Graph Problem Submission Times').'" />';
        $r->print($submit_button.'&nbsp;'x5);
        $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
        $r->print(&Apache::lonstathelpers::problem_selector('.',
                                                            $submit_button));
    } else {
        foreach my $button (@SubmitButtons) {
            $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
                      'value="'.&mt($button->{'text'}).'" />');
            $r->print('&nbsp;'x5);
        }
        foreach my $html (@CacheButtonHTML) {
            $r->print($html.('&nbsp;'x5));
        }
        $r->rflush();
        #
        # Determine which problem we are to analyze
        my $current_problem = &Apache::lonstathelpers::get_target_from_id
            ($env{'form.problemchoice'});
        #
        my ($navmap,$prev,$curr,$next) = 
            &Apache::lonstathelpers::get_prev_curr_next($current_problem,
                                                        '.',
                                                        'part');
        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'} = 
            &Apache::lonstathelpers::make_target_id($current_problem);
        $r->print('<input type="hidden" name="problemchoice" value="'.
                  $env{'form.problemchoice'}.'" />');
        #
        $r->print('<hr />');
        $r->rflush();
        #
        my $resource = $current_problem->{'resource'};
        if (! defined($resource)) {
            $r->print('resource is undefined');
        } else {
            $r->print('<h1>'.$resource->compTitle.'</h1>');
            $r->print('<h3>'.$resource->src.'</h3>');
            $r->print('<h4>'.
                 &Apache::lonstatistics::section_and_enrollment_description().
                      '</h4>');
            $r->rflush();
            $r->print(&Apache::lonstathelpers::render_resource($resource));
            $r->print('<br />');
            $r->rflush();
            $r->print(&analyze_times($r,$resource->symb,\@Students,
                                     $current_problem->{'part'}));
        }
        $r->print('<hr />');
    }
}

#########################################################
#########################################################
##
##                  Time Analysis
##
#########################################################
#########################################################
sub get_week_start {
    my ($randomtime) = @_;
    my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = 
        localtime($randomtime);
    $randomtime -= $wday * 86400;
    ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = 
        localtime($randomtime);
    my $week_start = &Time::Local::timelocal(0,0,0,$mday,$month,$year);
    return $week_start;
}

sub analyze_times {
    my ($r,$symb,$students,$part) = @_;
    my $htmltable;
    #
    # Convenience arrays
    my @FullWeekDay = (qw/Sunday Monday Tuesday Wednesday Thursday Friday
                       Saturday/);
    my @WeekDay = (qw/SUN MON TUE WED THU FRI SAT SUN/);
    my @Month = (qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/);
    #
    my $html; # holds results of analysis
    # Get the data
    my $SubData = &Apache::loncoursedata::get_response_time_data
        ([&Apache::lonstatistics::get_selected_sections()],
         [&Apache::lonstatistics::get_selected_groups()],
         $Apache::lonstatistics::enrollment_status,
         $symb,$part);
    if (! defined($SubData) || ! ref($SubData)) {
        $html.= '<h2>There is no submission data for this problem at all</h2>';
        return $html;
    }
    my $NumSub = scalar(@{$SubData});
    if (! @{$SubData}) {
        $html.= '<h2>There is no submission data for this problem</h2>';
        return $html;
    }
    # Process the data
    #
    my (undef,undef,undef,$mday,$month,$year,$wday,$yday,$isdst) = 
        localtime(&get_time_from_row($SubData->[0]));
    my $day_start = &Time::Local::timelocal(0,0,0,$mday,$month,$year);
    #
    # Configure the bins used to store the data.
    my $binsize = 3600; # seconds
    my $bins_per_day = 86400/$binsize;
    my $bincount = 0;
    my $endtime = $day_start;
    #
    # Initialize loop variables
    my $max;            # The sum of @Ydata
    my @Ydata=(0);      # number of submissions
    my @AnsData=(0);    # number of correct submissions
    my @Xlabel=($WeekDay[$wday]); # Labels of itmes
    my @BinEnd;                   # The end time of each bin
    my $cumulative_answers = 0;   # The sum of @AnsData
    my %students;       # which students have attempted the problem?
    #
    foreach my $row (@$SubData) {
        my $subtime = &get_time_from_row($row);
        while ($subtime > $endtime && $endtime < time) {
            # Create a new bin
            $bincount++;
            $Ydata[$bincount]   = 0;
            $AnsData[$bincount] = 0;
            $endtime += $binsize;
            push(@BinEnd,$endtime);
            if ($bincount % (86400/$binsize) == 0) {
                $wday++;
                $wday %= 7;
                $Xlabel[$bincount] = $WeekDay[$wday];
            } else {
                $Xlabel[$bincount] = '';
            }
        }
        $Ydata[$bincount]++;
        $max = $Ydata[$bincount] if ($max < $Ydata[$bincount]);
        $AnsData[$bincount] += &successful_submission($row);
        $cumulative_answers += &successful_submission($row);
        $students{$row->[&Apache::loncoursedata::RT_student_id()]}++;
    }
    #
    # Pad the data to a full day
    while ($bincount % $bins_per_day != 0) {
        $bincount++;
        $Ydata[$bincount]=0;
        $AnsData[$bincount]=0;
        $endtime += $binsize;
        push(@BinEnd,$endtime);
        if ($bincount % (86400/$binsize) == 0) {
            $wday ++;
            $wday %= 7;
            $Xlabel[$bincount] = $WeekDay[$wday];
        } else {
            $Xlabel[$bincount] = '';
        }
    }
    my $numstudents = scalar(keys(%students));
    #
    # Determine a nice maximum value to use
    foreach my $maximum (10,15,20,25,30,40,50,60,70,80,90,100,
                          120,150,200,250,300,350,400,450,500,
                          600,700,800,900,1000,1100,1200,1500,2000,
                          2500,3000,4000,5000) {
        if ($max < $maximum) {
            $max = $maximum;
            last;
        }
    }
    #
    # Build the data table
    $htmltable = '<p>'.
        '<table rules="groups" frame="border" '.
        'summary="Student submission data">'.
        '<thead>'.
        '<tr>'.
        '<th valign="bottom">'.&mt('Begin').'</th>'.
        '<th>'.('&nbsp;'x3).'</th>'.
        '<th valign="bottom">'.&mt('End').'</th>'.
        '<th valign="bottom">'.&mt('Submissions (plotted)').'</th>'.
        '<th>'.('&nbsp;'x3).'</th>'.
        '<th valign="bottom">'.&mt('Correct Submissions (not plotted)').'</th>'.
        '<th>'.('&nbsp;'x3).'</th>'.
        '<th valign="bottom">'.&mt('Cumulative Correct of those attempting the problem (not plotted)').'</th>'.
        '<th>'.('&nbsp;'x3).'</th>'.
        '<th valign="bottom">'.&mt('Cumulative Percent Correct of those attempting the problem (not plotted)').'</th>'.
        '<th>'.('&nbsp;'x3).'</th>'.
        '<th valign="bottom">'.&mt('Cumulative Percent Correct of selected students (plotted)').'</th>'.
        '</tr>'.
        '</thead>'.
        '<tbody>';
    my @CumulativeCorrect=(0);
    my @corr_as_percent_of_selected;
    my @corr_as_percent_of_answering;
    for (my $i=0;$i<=$#Ydata;$i++) {
        $CumulativeCorrect[$i]=$CumulativeCorrect[-1]+$AnsData[$i];
        $corr_as_percent_of_answering[$i] = 
            sprintf('%3.1f',100*$CumulativeCorrect[$i]/$numstudents);
        $corr_as_percent_of_selected[$i] = 
            sprintf('%3.1f',100*$CumulativeCorrect[$i]/scalar(@$students));
        if ($Ydata[$i] != 0) {
            next if (! defined($BinEnd[$i]) || $BinEnd[$i] == 0);
            $htmltable .= 
                '<tr>'.
                '<td align="right"><nobr>'.
                &Apache::lonlocal::locallocaltime($BinEnd[$i]-$binsize).
                '</nobr></td>'.
                '<td>&nbsp;</td>'.
                '<td align="right"><nobr>'.
                    &Apache::lonlocal::locallocaltime($BinEnd[$i]).'</td>'.
                '</nobr></td>'.
                '<td align="right">'.$Ydata[$i].('&nbsp;'x3).'</td>'.
                '<td>&nbsp;</td>'.
                '<td align="right">'.$AnsData[$i].('&nbsp;'x3).'</td>'.
                '<td>&nbsp;</td>'.
                '<td align="right">'.$CumulativeCorrect[$i].'</td>'.
                '<td>&nbsp;</td>'.
                '<td align="right">'.$corr_as_percent_of_answering[$i].'</td>'.
                '<td>&nbsp;</td>'.
                '<td align="right">'.$corr_as_percent_of_selected[$i].'</td>'.
                '</tr>'.$/;
        }
    }
    $htmltable .= '</tbody></table></p>';
    #
    # Build the plot
    my $title = '';#'Number of Submissions and Number Correct';
    my $xlabel;
    (undef,undef,undef,$mday,$month,$year,$wday) = localtime($day_start);
    $xlabel .= $FullWeekDay[$wday].' '.
        join(' ',($Month[$month],$mday,1900+$year)).' - ';
    (undef,undef,undef,$mday,$month,$year,$wday) = localtime($endtime);
    $xlabel .= $FullWeekDay[$wday].' '.
        join(' ',($Month[$month],$mday,1900+$year));
    my $width = 50+2*$bincount;
    if ($width < 250) {
        $width = 250;
    }
    #
    $html .= &Apache::loncommon::DrawXYYGraph($title,
                                              $xlabel,
                                              'Submissions vs Time',
                                              $plotcolors,
                                              \@Xlabel,
                                              \@Ydata,0,$max,
                                              \@corr_as_percent_of_selected,0,100,
                                              (xskip => $bins_per_day,
                                               x_ticks => $bins_per_day,
                                               x_tick_offset => $bins_per_day,
                                               width => $width,
                      y1_label=>'Number of Submissions per hour',
                      y2_label=>'Percent of Students answering Correctly',
                     'data.1.label'=>'Submissions per hour',
                     'data.2.label'=>'Percent correct',
                                               )
                                              );
    $html .= '<br />'.$htmltable;
    return $html;
}

sub successful_submission {
    my ($row) = @_;
    if (ref($row) eq 'ARRAY') {
        return $row->[&Apache::loncoursedata::RT_awarded()];
    }
    return undef;
}

sub get_time_from_row {
    my ($row) = @_;
    if (ref($row) eq 'ARRAY') {
        return $row->[&Apache::loncoursedata::RT_timestamp()];
    } 
    return undef;
}

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

sub Process_Row {
    my ($row) = @_;
    my %RowData;
    my ($student_id,$award,$tries,$time) = @$row;
    return %RowData;
}

#########################################################
#########################################################
##
##   Generic Interface Routines
##
#########################################################
#########################################################
sub CreateInterface {
    ##
    ## Environment variable initialization
    if (! exists$env{'form.AnalyzeOver'}) {
        $env{'form.AnalyzeOver'} = 'Tries';
    }
    ##
    ## Build the menu
    my $Str = '';
    $Str .= &Apache::lonhtmlcommon::breadcrumbs('Submission Time Plots');
    $Str .= '<p>';
    $Str .= '<table cellspacing="5">'."\n";
    $Str .= '<tr>';
    $Str .= '<th align="center">'.&mt('Sections').'</th>';
    $Str .= '<th align="center">'.&mt('Groups').'</th>';
    $Str .= '<th align="center">'.&mt('Access Status').'</th>';
    $Str .= '</tr>'."\n";
    ##
    ## 
    $Str .= '<tr><td align="center">'."\n";
    $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',4);
    $Str .= '</td>';
    #
    $Str .= '<td align="center">'."\n";
    $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',4);
    $Str .= '</td>';
    #
    $Str .= '<td align="center">';
    $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,4);
    $Str .= '</td>';
    #
    $Str .= '</tr>'."\n";
    $Str .= '</table>'."\n";
    #
    $Str .= '<nobr>'.&mt('Status: [_1]',
                         '<input type="text" '.
                         'name="stats_status" size="60" value="" />').
            '</nobr>'.'</p>';
    ##
    return $Str;
}

1;

__END__

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