File:  [LON-CAPA] / loncom / interface / statistics / lonsubmissiontimeanalysis.pm
Revision 1.37: download - view: text, annotated - select for diffs
Tue Nov 10 19:28:32 2020 UTC (3 years, 8 months ago) by raeburn
Branches: MAIN
CVS tags: HEAD
- Load CSS file(s) for resource when using (a) Student Submission Reports
  (only one problem selected), or and (b)Detailed Problem Analysis when
  "Show problem" is checked, or when using Submission Time Plots.

# The LearningOnline Network with CAPA
#
# $Id: lonsubmissiontimeanalysis.pm,v 1.37 2020/11/10 19:28:32 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::lonquickgrades();
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(&Apache::lonhtmlcommon::breadcrumbs('Submission Time Plots'));
    &Apache::lonquickgrades::startGradeScreen($r,'statistics');
    $r->print(&CreateInterface());
    #
    my @Students = @Apache::lonstatistics::Students;
    #
    if (@Students < 1) {
        $r->print('<div class="LC_warning">'
                 .&mt('There are no students in the sections selected.')
                 .'</div>'
        );
    }
    #
    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').'" />';
            &mt('Generate Graph').'" />';
        $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 ($navmap,$current_problem) = &get_current_problem(); # need to retrieve $navmap
                                                                # to support $resource->* calls
                                                                # for src and compTitle (below)
        #
        # 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('<div class="LC_warning">'
                     .&mt('Resource is undefined.')
                     .'</div>'
            );
        } else {
            $r->print('<h1>'.$resource->compTitle.'</h1>');
            $r->print('<h3>'.$resource->src.'</h3>');
            $r->print('<p>'.
                 &Apache::lonstatistics::section_and_enrollment_description().
                      '</p>');
            $r->rflush();
            $r->print(&Apache::lonstathelpers::render_resource($resource));
            $r->print('<br />');
            $r->rflush();
	    if (@Students) {	    
		$r->print(&analyze_times($r,$resource->symb,\@Students,
					 $current_problem->{'part'}));
	    }
        }
        $r->print('<hr />');
    }
}

sub get_current_problem {
    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;
    }
    return ($navmap,$current_problem);
}

#########################################################
#########################################################
##
##                  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.= '<div class="LC_warning">'
               .&mt('There is no submission data for this problem at all.')
               .'</div>';
        return $html;
    }
    my $NumSub = scalar(@{$SubData});
    if (! @{$SubData}) {
        $html.= '<div class="LC_warning">'
               .&mt('There is no submission data for this problem.')
               .'</div>';
        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 = '<br><h3>'.&mt('Student submission data').'</h3><p>'.
        &Apache::loncommon::start_data_table().
        &Apache::loncommon::start_data_table_header_row().
        &Apache::loncommon::start_data_table_row().
        '<th valign="bottom">'.&mt('Begin').'</th>'.
        '<th valign="bottom">'.&mt('End').'</th>'.
        '<th valign="bottom">'.&mt('Submissions (plotted)').'</th>'.
        '<th valign="bottom">'.&mt('Correct Submissions (not plotted)').'</th>'.
        '<th valign="bottom">'.&mt('Cumulative Correct of those attempting the problem (not plotted)').'</th>'.
        '<th valign="bottom">'.&mt('Cumulative Percent Correct of those attempting the problem (not plotted)').'</th>'.
        '<th valign="bottom">'.&mt('Cumulative Percent Correct of selected students (plotted)').'</th>'.
        &Apache::loncommon::end_data_table_row().
        &Apache::loncommon::end_data_table_header_row().
        '<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 .=
               &Apache::loncommon::start_data_table_row().
                '<td align="right"><span class="LC_nobreak">'.
                &Apache::lonlocal::locallocaltime($BinEnd[$i]-$binsize).
                '</span></td>'.
                '<td align="right"><span class="LC_nobreak">'.
                    &Apache::lonlocal::locallocaltime($BinEnd[$i]).'</td>'.
                '</span></td>'.
                '<td align="right">'.$Ydata[$i].('&nbsp;'x3).'</td>'.
                '<td align="right">'.$AnsData[$i].('&nbsp;'x3).'</td>'.
                '<td align="right">'.$CumulativeCorrect[$i].'</td>'.
                '<td align="right">'.$corr_as_percent_of_answering[$i].'</td>'.
                '<td align="right">'.$corr_as_percent_of_selected[$i].'</td>'.
               &Apache::loncommon::end_data_table_row().$/;
        }
    }
    $htmltable .= '</tbody>'.&Apache::loncommon::end_data_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 .= '<p>';
    $Str .= &Apache::loncommon::start_data_table();
    $Str .= &Apache::loncommon::start_data_table_header_row();
    $Str .= '<th>'.&mt('Sections').'</th>';
    $Str .= '<th>'.&mt('Groups').'</th>';
    $Str .= '<th>'.&mt('Access Status').'</th>';
    $Str .= &Apache::loncommon::end_data_table_header_row();
    ##
    ## 
    $Str .= &Apache::loncommon::start_data_table_row();
    $Str .= '<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 .= &Apache::loncommon::end_data_table_row();
    $Str .= &Apache::loncommon::end_data_table();
    #
    $Str .= '</p>';
    ##
    return $Str;
}

1;

__END__

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