--- loncom/interface/statistics/lonproblemstatistics.pm 2003/03/26 16:26:35 1.43 +++ loncom/interface/statistics/lonproblemstatistics.pm 2004/02/20 16:38:49 1.68 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemstatistics.pm,v 1.43 2003/03/26 16:26:35 matthew Exp $ +# $Id: lonproblemstatistics.pm,v 1.68 2004/02/20 16:38:49 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,63 +26,163 @@ # # (Navigate problems for statistical reports # -### +############################################### +############################################### + +=pod + +=head1 NAME + +lonproblemstatistics + +=head1 SYNOPSIS + +Routines to present problem statistics to instructors via tables, +Excel files, and plots. + +=over 4 + +=cut + +############################################### +############################################### package Apache::lonproblemstatistics; use strict; use Apache::lonnet(); +use Apache::loncommon(); use Apache::lonhtmlcommon; use Apache::loncoursedata; use Apache::lonstatistics; +use Apache::lonlocal; +use Spreadsheet::WriteExcel; -####################################################### -####################################################### +## +## Localization notes: +## +## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the +## header for plots created with Graph.pm, both of which more than likely do +## not support localization. +## +my @Fields = ( + { name => 'problem_num', + title => 'P#', + align => 'right', + color => '#FFFFE6' }, + { name => 'container', + title => 'Sequence or Folder', + align => 'left', + color => '#FFFFE6', + sortable => 'yes' }, + { name => 'title', + title => 'Title', + align => 'left', + color => '#FFFFE6', + special => 'link', + sortable => 'yes', }, + { name => 'part', + title => 'Part', + align => 'left', + color => '#FFFFE6', + }, + { name => 'num_students', + title => '#Stdnts', + align => 'right', + color => '#EEFFCC', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students Attempting Problem' }, + { name => 'tries', + title => 'Tries', + align => 'right', + color => '#EEFFCC', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Total Number of Tries' }, + { name => 'max_tries', + title => 'Max Tries', + align => 'right', + color => '#DDFFFF', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Maximum Number of Tries' }, + { name => 'mean_tries', + title => 'Mean Tries', + align => 'right', + color => '#DDFFFF', + format => '%5.2f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Average Number of Tries' }, + { name => 'std_tries', + title => 'S.D. tries', + align => 'right', + color => '#DDFFFF', + format => '%5.2f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Standard Deviation of Number of Tries' }, + { name => 'skew_tries', + title => 'Skew Tries', + align => 'right', + color => '#DDFFFF', + format => '%5.2f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Skew of Number of Tries' }, + { name => 'deg_of_diff', + title => 'DoDiff', + align => 'right', + color => '#DDFFFF', + format => '%5.2f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Degree of Difficulty'. + '[ 1 - ((#YES+#yes) / Tries) ]'}, + { name => 'num_solved', + title => '#YES', + align => 'right', + color => '#FFDDDD', + format => '%4.1f',# format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students able to Solve' }, + { name => 'num_override', + title => '#yes', + align => 'right', + color => '#FFDDDD', + format => '%4.1f',# format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students given Override' }, + { name => 'per_wrong', + title => '%Wrng', + align => 'right', + color => '#FFFFE6', + format => '%4.1f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Percent of students whose final answer is wrong' }, +); -sub CreateInterface { - my $Str = ''; - $Str .= ''."\n"; - $Str .= ''; - $Str .= ''; - $Str .= ''; - $Str .= ''; - $Str .= ''."\n"; - # - $Str .= ''."\n"; - $Str .= '
SectionsSequences and FoldersOutput
'."\n"; - $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); - $Str .= ''; - # - my $only_seq_with_assessments = sub { - my $s=shift; - if ($s->{'num_assess'} < 1) { - return 0; - } else { - return 1; - } - }; - $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, - $only_seq_with_assessments); - $Str .= ''."\n"; - $Str .= &CreateAndParseOutputSelector(); - $Str .= '
'."\n"; - return $Str; -} +############################################### +############################################### -####################################################### -####################################################### +=pod -=pod +=item &CreateInterface() -=item &CreateAndParseOutputSelector() +Create the main intereface for the statistics page. Allows the user to +select sections, maps, and output. =cut -####################################################### -####################################################### -my $output_mode; -my $show; - +############################################### +############################################### my @OutputOptions = ( { name => 'problem statistics grouped by sequence', @@ -104,118 +204,93 @@ my @OutputOptions = mode => 'excel', show => 'all', }, - { name => 'Degree of Difficulty Plot', - value => 'plot deg diff', - description => 'Generate a plot of the degree of difficulty of each '. - 'problem part.', - mode => 'plot', - show => 'deg of diff', - }, - { name => 'Percent Wrong Plot', - value => 'plot per wrong', - description => 'Generate a plot showing the percent of students who '. - 'were unable to complete each problem part', - mode => 'plot', - show => 'per wrong', - }, ); -sub OutputDescriptions { - my $Str = ''; - $Str .= "

Output Modes

\n"; - $Str .= "
\n"; - foreach my $outputmode (@OutputOptions) { - $Str .="
".$outputmode->{'name'}."
\n"; - $Str .="
".$outputmode->{'description'}."
\n"; - } - $Str .= "
\n"; - return $Str; -} - -sub CreateAndParseOutputSelector { +sub CreateInterface { my $Str = ''; - my $elementname = 'outputmode'; - # - # Format for output options is 'mode, restrictions'; - my $selected = 'html, with links'; - if (exists($ENV{'form.'.$elementname})) { - if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) { - $selected = $ENV{'form.'.$elementname}->[0]; - } else { - $selected = $ENV{'form.'.$elementname}; - } - } + $Str .= &Apache::lonhtmlcommon::breadcrumbs + (undef,&mt('Overall Problem Statistics'),'Statistics_Overall_Key'); + $Str .= ''."\n"; + $Str .= ''; + $Str .= ''; + $Str .= ''; + $Str .= ''; + $Str .= ''; + $Str .= ''."\n"; # - # Set package variables describing output mode - $output_mode = 'html'; - $show = 'all'; - foreach my $option (@OutputOptions) { - next if ($option->{'value'} ne $selected); - $output_mode = $option->{'mode'}; - $show = $option->{'show'}; - } + $Str .= ''."\n"; + $Str .= '
'.&mt('Sections').''.&mt('Enrollment Status').''.&mt('Sequences and Folders').''.&mt('Output').'
'."\n"; + $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); + $Str .= ''; + $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); + $Str .= ''; # - # Build the form element - $Str = qq/"; - return $Str; + my $only_seq_with_assessments = sub { + my $s=shift; + if ($s->{'num_assess'} < 1) { + return 0; + } else { + return 1; + } + }; + $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, + $only_seq_with_assessments); + $Str .= ''."\n"; + my ($html,$outputmode,$show) = + &Apache::lonstatistics::CreateAndParseOutputSelector( + 'statsoutputmode', + 'HTML problem statistics grouped', + @OutputOptions); + $Str .= $html; + $Str .= '
'."\n"; + $Str .= ''; + $Str .= ' 'x5; + $Str .= ''; + $Str .= ' 'x5; + return ($Str,$outputmode,$show); } ############################################### ############################################### -############################################### -############################################### -sub Gather_Student_Data { - my ($r) = @_; - my $c = $r->connection(); - # - my @Sequences = &Apache::lonstatistics::Sequences_with_Assess(); - # - my @Students = @Apache::lonstatistics::Students; - # - # Open the progress window - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin - ($r,'Statistics Compilation Status', - 'Statistics Compilation Progress', scalar(@Students)); - # - while (my $student = shift @Students) { - return if ($c->aborted()); - my ($status,undef) = &Apache::loncoursedata::ensure_current_data - ($student->{'username'},$student->{'domain'}, - $ENV{'request.course.id'}); - &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - 'last student'); - } - &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); - $r->rflush(); -} +=pod -############################################### -############################################### +=item &BuildProblemStatisticsPage() + +Main interface to problem statistics. + +=cut ############################################### ############################################### sub BuildProblemStatisticsPage { my ($r,$c)=@_; # - $output_mode = 'html'; - $show = 'grouped'; + my %Saveable_Parameters = ('Status' => 'scalar', + 'statsoutputmode' => 'scalar', + 'Section' => 'array', + 'StudentData' => 'array', + 'Maps' => 'array'); + &Apache::loncommon::store_course_settings('statistics', + \%Saveable_Parameters); + &Apache::loncommon::restore_course_settings('statistics', + \%Saveable_Parameters); # - $r->print(&CreateInterface()); + &Apache::lonstatistics::PrepareClasslist(); + # + my ($interface,$output_mode,$show) = &CreateInterface(); + $r->print($interface); $r->print(''); $r->print(''); + $r->print(''); if (! exists($ENV{'form.statsfirstcall'})) { return; } # - &Gather_Student_Data($r); + &Apache::lonstatistics::Gather_Student_Data($r); # # if ($output_mode eq 'html') { @@ -229,46 +304,50 @@ sub BuildProblemStatisticsPage { } elsif ($show eq 'ungrouped') { &output_html_ungrouped($r); } + } elsif ($output_mode eq 'excel') { + $r->print('

'.&mt('Preparing Excel Spreadsheet').'

'); + &output_excel($r); } else { - $r->print("

Not implemented

"); + $r->print('

'.&mt('Not implemented').'

'); } return; } +############################################### +############################################### + +=pod + +=item &output_html_grouped_by_sequence() + +Presents the statistics data as an html table organized by the order +the assessments appear in the course. + +=cut + +############################################### +############################################### sub output_html_grouped_by_sequence { my ($r) = @_; + my $problem_num = 0; #$r->print(&ProblemStatisticsLegend()); - my @Header = ("Title","Part","#Stdnts","Tries","Mod", - "Mean","#YES","#yes","%Wrng","DoDiff", - "S.D.","Skew.");#,"D.F.1st","D.F.2nd"); - # #FFFFE6 #EEFFCC #DDFFFF FFDDDD #DDFFDD #FFDDFF foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) { - my $show_part = 0; next if ($sequence->{'num_assess'}<1); $r->print("

".$sequence->{'title'}."

"); $r->print('
'."\n"); $r->print(''."\n"); - $r->print('\n"); + $r->print(''); + my $Str = &statistics_table_header('no container no plots'); + $r->print(''.$Str."\n"); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { - my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, - $SKEW) = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); - # - $show_part = 1 if ($part ne '0'); - $part = ' ' if ($part == 0); - # - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } - $r->print(''.&statistics_html_table_data - ($resource,$part,$num,$tries,$mod,$mean,$Solved, - $solved,$wrongpercent,$DegOfDiff,$STD,$SKEW, - $show_part). + $problem_num++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + my $option = ''; + $r->print(''.&statistics_html_table_data($data, + 'no container'). "\n"); } } @@ -280,41 +359,46 @@ sub output_html_grouped_by_sequence { return; } - ############################################### ############################################### +=pod + +=item &output_html_ungrouped() + +Presents the statistics data in a single html table which can be sorted by +different columns. + +=cut + ############################################### ############################################### sub output_html_ungrouped { - my ($r) = @_; + my ($r,$option) = @_; # + if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '') { + &plot_statistics($r,$ENV{'form.plot'}); + } + # + my $problem_num = 0; my $show_container = 0; my $show_part = 0; #$r->print(&ProblemStatisticsLegend()); - my @Header = ("Title","Part","#Stdnts","Tries","Mod", - "Mean","#YES","#yes","%Wrng","DoDiff", - "S.D.","Skew");#,"D.F.1st","D.F.2nd"); - # my $sortby = undef; - foreach (@Header) { - if ($ENV{'form.sortby'} eq $_) { - $sortby = $_; + foreach my $field (@Fields) { + if ($ENV{'form.sortby'} eq $field->{'name'}) { + $sortby = $field->{'name'}; } } - if (! defined($sortby) || $sortby eq '') { - $sortby = 'Container'; + if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') { + $sortby = 'container'; } - # #FFFFE6 #EEFFCC #DDFFFF FFDDDD #DDFFDD #FFDDFF + # If there is more than one sequence, list their titles my @Sequences = &Apache::lonstatistics::Sequences_with_Assess(); - if (@Sequences > 1) { - unshift(@Header,"Container"); - $show_container = 1; + if (@Sequences < 1) { + $option .= ' no container'; } # - $r->print('
'. - join("",@Header)."
'; - $row .= '' if ($show_part); - foreach ($num,$tries) { - $row .= ''; - } - foreach ($mod,$mean) { - $row .= ''; - } - foreach ($Solved,$solved) { - $row .= ''; - } - foreach ($wrongpercent) { - $row .= ''; - } - foreach ($DegOfDiff,$STD,$SKEW) { - $row .= ''; - } - return $row; -} +=pod + +=item &output_excel() +Presents the statistical data in an Excel 95 compatable spreadsheet file. + +=cut ############################################### ############################################### - -sub BuildGraphicChart { - my ($graph,$cacheDB,$courseDescription,$students,$courseID,$r,$c)=@_; - my %cache; - my $max; - my $title = ''; - if($graph eq 'DoDiffGraph') { - $title = 'Degree-of-Difficulty'; +sub output_excel { + my ($r) = @_; + my $filename = '/prtspool/'. + $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. + time.'_'.rand(1000000000).'.xls'; + # + my $excel_workbook = undef; + my $excel_sheet = undef; + # + my $rows_output = 0; + my $cols_output = 0; + # + # Create sheet + $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); + # + # Check for errors + if (! defined($excel_workbook)) { + $r->log_error("Error creating excel spreadsheet $filename: $!"); + $r->print(&mt("Problems creating new Excel file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator.")); + return ; + } + # + # The excel spreadsheet stores temporary data in files, then put them + # together. If needed we should be able to disable this (memory only). + # The temporary directory must be specified before calling 'addworksheet'. + # File::Temp is used to determine the temporary directory. + $excel_workbook->set_tempdir($Apache::lonnet::tmpdir); + # + # Add a worksheet + my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + if (length($sheetname) > 31) { + $sheetname = substr($sheetname,0,31); + } + $excel_sheet = $excel_workbook->addworksheet( + &Apache::loncommon::clean_excel_name($sheetname) + ); + # + # Put the course description in the header + $excel_sheet->write($rows_output,$cols_output++, + $ENV{'course.'.$ENV{'request.course.id'}.'.description'}); + $cols_output += 3; + # + # Put a description of the sections listed + my $sectionstring = ''; + my @Sections = @Apache::lonstatistics::SelectedSections; + if (scalar(@Sections) > 1) { + if (scalar(@Sections) > 2) { + my $last = pop(@Sections); + $sectionstring = "Sections ".join(', ',@Sections).', and '.$last; + } else { + $sectionstring = "Sections ".join(' and ',@Sections); + } } else { - $title = 'Wrong-Percentage'; - } - my $currentSequence = -1; - my $sortProblems = 'Sort Within Sequence'; - my ($result, $orderedProblems) = - &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r); - if($result ne 'OK') { - return; - } - my @values = (); - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - return 'Unable to tie database.7'; - } - foreach(@$orderedProblems) { - my ($sequence,$problem,$part)=split(':', $_); - if($cache{'StatisticsMaps'} ne 'All Maps' && - $cache{'StatisticsMaps'} ne $cache{$sequence.':title'}) { - next; - } - if( $currentSequence == -1 || - ($sortProblems eq 'Sort Within Sequence' && - $currentSequence != $sequence)) { - if($currentSequence != -1) { - &DrawGraph(\@values,$courseDescription,$title,$max,$r); - } - if($sortProblems eq 'Sort Within Sequence') { - $r->print('
'.$cache{$sequence.':title'}.''."\n"); - } - $currentSequence = $sequence; - @values = (); - $max=0; - } - my $data = 0; - if($graph eq 'DoDiffGraph') { - $data = sprintf("%.2f", $cache{$_.':degreeOfDifficulty'}), + if ($Sections[0] eq 'all') { + $sectionstring = "All sections"; } else { - $data = sprintf("%.1f", $cache{$_.':percentWrong'}), + $sectionstring = "Section ".$Sections[0]; } - if($max < $data) { - $max = $data; + } + $excel_sheet->write($rows_output,$cols_output++,$sectionstring); + $cols_output += scalar(@Sections); + # + # Put the date in there too + $excel_sheet->write($rows_output,$cols_output++, + 'Compiled on '.localtime(time)); + # + $rows_output++; + $cols_output=0; + # + # Long Headersheaders + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + if (exists($field->{'long_title'})) { + $excel_sheet->write($rows_output,$cols_output++, + $field->{'long_title'}); + } else { + $excel_sheet->write($rows_output,$cols_output++,''); } - push(@values, $data); } - untie(%cache); - &DrawGraph(\@values,$courseDescription,$title,$max,$r); - return; -} - -sub DrawGraph { - my ($values,$courseDescription,$title,$Max,$r)=@_; - my $sendValues = join(',', @$values); - my $sendCount = scalar(@$values); - $r->print("
The Maximum Value is: $Max"); - if ( $Max > 1 ) { - if ($Max % 10) { - if ( int($Max) < $Max ) { - $Max++; - $Max = int($Max); - } - } - #(10 - $Max % 10); - } else { $Max = 1; } - my @GData = ('','Problem_number',$title,$Max,$sendCount,$sendValues); -# $r->print(''."\n"); - $r->print('
'."\n"); - $r->print(''); -# $r->print(''."\n"); - $r->print('
'."\n"); -} - -#---- Problem Statistics Web Page --------------------------------------- -sub CreateProblemStatisticsTableHeading { - my ($headings,$r)=@_; - my $Str=''; - $Str .= ''."\n"; - $Str .= ''."\n"; - foreach(@$headings) { - $Str .= ''."\n"; + $rows_output++; + $cols_output=0; + # Brief headers + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + # Use english for excel as I am not sure how well excel handles + # other character sets.... + $excel_sheet->write($rows_output,$cols_output++,$field->{'title'}); } - $Str .= "\n".''."\n"; - return $Str; -} - -sub BuildStatisticsTable { - my ($cache,$displayFormat,$sortProblems,$orderedProblems,$headings, - $r,$color)=@_; - my $count = 1; - my $currentSequence = -1; - foreach(@$orderedProblems) { - my ($sequence,$problem,$part)=split(':', $_); - if($cache->{'StatisticsMaps'} ne 'All Maps' && - $cache->{'StatisticsMaps'} ne $cache->{$sequence.':title'}) { - next; - } - if($currentSequence == -1 || - ($sortProblems eq 'Sort Within Sequence' && - $currentSequence != $sequence)) { - if($displayFormat ne 'Display CSV Format') { - if($currentSequence ne -1) { - $r->print('
'."\n"); - $r->rflush(); - # # Compile the data my @Statsarray; foreach my $sequence (@Sequences) { @@ -322,58 +406,19 @@ sub output_html_ungrouped { foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { - my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, - $SKEW) = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); - # + $problem_num++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); $show_part = 1 if ($part ne '0'); - $part = ' ' if ($part == 0); # - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } - push (@Statsarray, - { 'sequence' => $sequence, - 'resource' => $resource, - 'Title' => $resource->{'title'}, - 'Part' => $part, - '#Stdnts' => $num, - 'Tries' => $tries, - 'Mod' => $mod, - 'Mean' => $mean, - '#YES' => $Solved, - '#yes' => $solved, - '%Wrng' => $wrongpercent, - 'DoDiff' => $DegOfDiff, - 'S.D.' => $STD, - 'Skew' => $SKEW, - }); + push (@Statsarray,$data); } } } # - # Table Headers - $r->print(''."\n"); - my $Str = ''; - foreach (@Header) { - next if ($_ eq 'Part' && !$show_part); - # Do not allow sorting on some fields - if ($_ eq $sortby || /^(Part)$/) { - $Str .= ''; - } else { - $Str .= ''; - } - } - $r->print(''.$Str."\n"); - # # Sort the data my @OutputOrder; - if ($sortby eq 'Container') { + if ($sortby eq 'container') { @OutputOrder = @Statsarray; } else { # $sortby is already defined, so we can charge ahead @@ -381,8 +426,8 @@ sub output_html_ungrouped { # Alpha comparison @OutputOrder = sort { lc($a->{$sortby}) cmp lc($b->{$sortby}) || - lc($a->{'Title'}) cmp lc($b->{'Title'}) || - lc($a->{'Part'}) cmp lc($b->{'Part'}); + lc($a->{'title'}) cmp lc($b->{'title'}) || + lc($a->{'part'}) cmp lc($b->{'part'}); } @Statsarray; } else { # Numerical comparison @@ -402,21 +447,35 @@ sub output_html_ungrouped { } if ($retvalue eq '0') { $retvalue = $b->{$sortby} <=> $a->{$sortby} || - lc($a->{'Title'}) <=> lc($b->{'Title'}) || - lc($a->{'Part'}) <=> lc($b->{'Part'}); + lc($a->{'title'}) <=> lc($b->{'title'}) || + lc($a->{'part'}) <=> lc($b->{'part'}); } $retvalue; } @Statsarray; } } - foreach my $row (@OutputOrder) { - $r->print(''); - if ($show_container) { - $r->print(''); + $option .= 'no part' if (! $show_part); + my $num_output = 0; + # + # output the headers + $r->print('
'.$_.''. - ''. - $_.'
' - .$row->{'sequence'}->{'title'}.'
'."\n"); + $r->print(''."\n"); + my $Str = &statistics_table_header($option.' sortable'); + $r->print(''.$Str."\n"); + # + foreach my $rowdata (@OutputOrder) { + $num_output++; + if ($num_output % 25 == 0) { + $r->print("
\n
\n"); + # + $r->print('
'."\n"); + $r->print(''."\n"); + my $Str = &statistics_table_header($option.' sortable'); + $r->print(''.$Str."\n"); + $r->rflush(); } - $r->print(&stats_row_from_hash($row,$show_part)); - $r->print("\n"); + $r->print(''.&statistics_html_table_data($rowdata,$option). + "\n"); } $r->print("
\n"); $r->print("
\n"); @@ -425,314 +484,337 @@ sub output_html_ungrouped { return; } -sub stats_row_from_hash { - my ($data,$show_part) = @_; - return &statistics_html_table_data($data->{'resource'},$data->{'Part'}, - $data->{'#Stdnts'}, $data->{'Tries'}, - $data->{'Mod'}, $data->{'Mean'}, - $data->{'#YES'}, $data->{'#yes'}, - $data->{"\%Wrng"}, $data->{'DoDiff'}, - $data->{'S.D.'}, $data->{'Skew'}, - $show_part); -} - ############################################### ############################################### -############################################### -############################################### -sub statistics_html_table_data { - my ($resource,$part,$num,$tries,$mod,$mean,$Solved,$solved,$wrongpercent, - $DegOfDiff,$STD,$SKEW,$show_part) = @_; - my $row = ''; - $row .= '
'. - ''. - $resource->{'title'}.''. - ''.$part.''.$_.''. - sprintf("%5.2f",$_).''.$_.''. - sprintf("%5.1f",$_).''. - sprintf("%5.2f",$_).'
P#'; - $Str .= ''.$_.' 
'); - $r->print('

'); - } - if($sortProblems eq 'Sort Within Sequence') { - $r->print(''.$cache->{$sequence.':title'}.''); + $rows_output++; + # + # Write the data + my $problem_num=0; + foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) { + next if ($sequence->{'num_assess'}<1); + foreach my $resource (@{$sequence->{'contents'}}) { + next if ($resource->{'type'} ne 'assessment'); + foreach my $part (@{$resource->{'parts'}}) { + $cols_output=0; + $problem_num++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + # + if (!defined($part) || $part eq '') { + $part = ' '; } - $r->print('
'."\n"); - $r->print(''."\n"); - $r->print(&CreateProblemStatisticsTableHeading($headings, $r)); - } else { - if($sortProblems eq 'Sort Within Sequence') { - $r->print('"'.$cache->{$sequence.':title'}.'"'); + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + $excel_sheet->write($rows_output,$cols_output++, + $data->{$field->{'name'}}); } - $r->print('
'); + $rows_output++; } - $currentSequence = $sequence; } - my $ref = ''.$cache->{$problem.':title'}.''; - my $title = $cache->{$problem.':title'}; - if($part != 0) { - $title .= ' Part '.$part; - } - my $source = $cache->{$problem.':source'}; - my $tableData = join('&', $ref, $title, $source, - $cache->{$_.':studentCount'}, - $cache->{$_.':totalTries'}, - $cache->{$_.':maxTries'}, - $cache->{$_.':mean'}, - $cache->{$_.':correct'}, - $cache->{$_.':correctByOverride'}, - $cache->{$_.':percentWrong'}, - $cache->{$_.':degreeOfDifficulty'}, - $cache->{$_.':standardDeviation'}, - $cache->{$_.':skewness'}, - $cache->{$_.':discriminationFactor1'}, - $cache->{$_.':discriminationFactor2'}); - &TableRow($displayFormat,$tableData,$count,$r,$color); - $count++; - } - if($displayFormat ne 'Display CSV Format') { - $r->print('
'."\n"); - $r->print('
'); - } else { - $r->print('
'); } + # + # Write the excel file + $excel_workbook->close(); + # Tell the user where to get their excel file + $r->print('
'. + ''. + &mt('Your Excel Spreadsheet').''."\n"); + $r->rflush(); return; } -sub TableRow { - my ($displayFormat,$Str,$RealIdx,$r,$color)=@_; - my($ref,$title,$source,$StdNo,$TotalTries,$MxTries,$Avg,$YES,$Override, - $Wrng,$DoD,$SD,$Sk,$_D1,$_D2)=split(/\&/,$Str); - my $Ptr; - if($displayFormat eq 'Display CSV Format') { - $Ptr='"'.$RealIdx.'",'."\n". - '"'.$title.'",'."\n". - '"'.$source.'",'."\n". - '"'.$StdNo.'",'."\n". - '"'.$TotalTries.'",'."\n". - '"'.$MxTries.'",'."\n". - '"'.$Avg.'",'."\n". - '"'.$YES.'",'."\n". - '"'.$Override.'",'."\n". - '"'.$Wrng.'",'."\n". - '"'.$DoD.'",'."\n". - '"'.$SD.'",'."\n". - '"'.$Sk.'",'."\n". - '"'.$_D1.'",'."\n". - '"'.$_D2.'"'."\n". - "
\n"; - $r->print("\n".$Ptr); - } else { - $Ptr=''."\n". - ''.$RealIdx.''."\n". - ''.$ref.''."\n". - ' '.$StdNo.''."\n". - ''.$TotalTries.''."\n". - ''.$MxTries.''."\n". - ''.$Avg.''."\n". - ' '.$YES.''."\n". - ' '.$Override.''."\n". - ' '.$Wrng.''."\n". - ' '.$DoD.''."\n". - ' '.$SD.''."\n". - ' '.$Sk.''."\n". - ' '.$_D1.''."\n". - ' '.$_D2.''."\n"; - $r->print($Ptr.''."\n"); +############################################### +############################################### + +=pod + +=item &statistics_html_table_data() + +Help function used to format the rows for HTML table output. + +=cut + +############################################### +############################################### +sub statistics_html_table_data { + my ($data,$options) = @_; + my $row = ''; + foreach my $field (@Fields) { + next if ($options =~ /no $field->{'name'}/); + $row .= '{'align'})) { + $row .= ' align="'.$field->{'align'}.'"'; + } + $row .= '>'; + if (exists($field->{'special'}) && $field->{'special'} eq 'link') { + $row .= ''; + } + if (exists($field->{'format'})) { + $row .= sprintf($field->{'format'},$data->{$field->{'name'}}); + } else { + $row .= $data->{$field->{'name'}}; + } + if (exists($field->{'special'}) && $field->{'special'} eq 'link') { + $row.= ''; + } + $row .= ''; } - return; + return $row; } -# For loading the colored table for display or un-colored for print -sub setbgcolor { - my $PrintTable=shift; - my %color; - if ($PrintTable){ - $color{"gb"}="#FFFFFF"; - $color{"red"}="#FFFFFF"; - $color{"yellow"}="#FFFFFF"; - $color{"green"}="#FFFFFF"; - $color{"purple"}="#FFFFFF"; - } else { - $color{"gb"}="#DDFFFF"; - $color{"red"}="#FFDDDD"; - $color{"yellow"}="#EEFFCC"; - $color{"green"}="#DDFFDD"; - $color{"purple"}="#FFDDFF"; +sub statistics_table_header { + my ($options) = @_; + my $header_row; + foreach my $field (@Fields) { + next if ($options =~ /no $field->{'name'}/); + $header_row .= ''; + if ($options =~ /sortable/ && + exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') { + $header_row .= '{'name'}."'". + ';document.Statistics.submit();">'; + } + $header_row .= &mt($field->{'title'}); + if ($options =~ /sortable/) { + $header_row.= ''; + } + if ($options !~ /no plots/ && + exists($field->{'graphable'}) && + $field->{'graphable'} eq 'yes') { + $header_row.=' ('; + $header_row .= ''; + $header_row .= &mt('plot').')'; + } + $header_row .= ''; } - return \%color; + return $header_row; } -sub ProblemStatisticsButtons { - my ($displayFormat, $displayLegend, $sortProblems)=@_; - my $Ptr = ''; - $Ptr .= '{'name'} && + exists($field->{'graphable'}) && $field->{'graphable'} eq 'yes') { + $sortfield = $field->{'name'}; + $title = $field->{'long_title'}; + } } - $Ptr .= ''; - $Ptr .= '{'num_assess'}<1); + foreach my $resource (@{$sequence->{'contents'}}) { + next if ($resource->{'type'} ne 'assessment'); + foreach my $part (@{$resource->{'parts'}}) { + my $problem_number++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + my $value = $data->{$sortfield}; + $Max = $value if ($Max < $value); + push (@Data,$value); + } + } } - $Ptr .= ''; - $Ptr .= ' 0.5) { + $Max = 1; + } elsif ($Max > 0.2) { + $Max = 0.5; + } elsif ($Max > 0.1) { + $Max = 0.2; + } + } elsif ($sortfield eq 'per_wrong') { + if ($Max > 50) { + $Max = 100; + } elsif ($Max > 25) { + $Max = 50; + } elsif ($Max > 20) { + $Max = 25; + } elsif ($Max > 10) { + $Max = 20; + } elsif ($Max > 5) { + $Max = 10; + } else { + $Max = 5; + } } - $Ptr .= ''; - return $Ptr; + + $r->print("

".&Apache::loncommon::DrawBarGraph($title, + 'Problem Number', + $yaxis, + $Max, + undef, # colors + undef, # labels + \@Data)."

\n"); + # + # Print out the data + $ENV{'form.sortby'} = 'Contents'; +# &output_html_ungrouped($r); + return; } +sub get_statistics { + my ($sequence,$resource,$part,$problem_num) = @_; + # + my $symb = $resource->{'symb'}; + my $courseid = $ENV{'request.course.id'}; + # + my $data = &Apache::loncoursedata::get_problem_statistics + (\@Apache::lonstatistics::SelectedSections, + $Apache::lonstatistics::enrollment_status, + $symb,$part,$courseid); + $data->{'part'} = $part; + $data->{'problem_num'} = $problem_num; + $data->{'container'} = $sequence->{'title'}; + $data->{'title'} = $resource->{'title'}; + $data->{'title.link'} = $resource->{'src'}.'?symb='. + &Apache::lonnet::escape($resource->{'symb'}); + # + return $data; +} + +############################################### +############################################### + +=pod + +=item &ProblemStatisticsLegend() + +HELP This needs to be localized, or at least generated automatically. + +=cut + +############################################### +############################################### sub ProblemStatisticsLegend { my $Ptr = ''; $Ptr = ''; @@ -743,7 +825,7 @@ sub ProblemStatisticsLegend { $Ptr .= 'Tries'; $Ptr .= ''; + $Ptr .= 'Max Tries'; $Ptr .= '';
Total number of tries for solving the problem.'; $Ptr .= '
'; - $Ptr .= 'ModLargest number of tries for solving the problem by a student.'; $Ptr .= '
'; $Ptr .= 'Mean