--- loncom/interface/statistics/lonproblemstatistics.pm 2003/03/26 15:19:16 1.42 +++ loncom/interface/statistics/lonproblemstatistics.pm 2003/06/13 20:27:17 1.53 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemstatistics.pm,v 1.42 2003/03/26 15:19:16 matthew Exp $ +# $Id: lonproblemstatistics.pm,v 1.53 2003/06/13 20:27:17 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,7 +26,26 @@ # # (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; @@ -35,15 +54,130 @@ use Apache::lonnet(); use Apache::lonhtmlcommon; use Apache::loncoursedata; use Apache::lonstatistics; +use Spreadsheet::WriteExcel; -####################################################### -####################################################### +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' }, + { name => 'num_solved', + title => '#YES', + align => 'right', + color => '#FFDDDD', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students able to Solve' }, + { name => 'num_override', + title => '#yes', + align => 'right', + color => '#FFDDDD', + 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 Wrong' }, +); + +############################################### +############################################### + +=pod +=item &CreateInterface() + +Create the main intereface for the statistics page. Allows the user to +select sections, maps, and output. + +=cut + +############################################### +############################################### sub CreateInterface { my $Str = ''; $Str .= ''."\n"; $Str .= ''; $Str .= ''; + $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''."\n"; @@ -51,6 +185,8 @@ sub CreateInterface { $Str .= ''."\n"; $Str .= '
SectionsEnrollment StatusSequences and FoldersOutput
'."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= ''; + $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); + $Str .= ''; # my $only_seq_with_assessments = sub { my $s=shift; @@ -66,6 +202,7 @@ sub CreateInterface { $Str .= &CreateAndParseOutputSelector(); $Str .= '
'."\n"; + $Str .= ''; return $Str; } @@ -76,6 +213,13 @@ sub CreateInterface { =item &CreateAndParseOutputSelector() +Construct a selection list of options for output and parse output selections. +The current output selected is indicated by the values of the two package +variables $output_mode and $show. @OutputOptions holds the descriptions of +the output options and the values for $output_mode and $show. + +Based on code from lonstudentassessment.pm. + =cut ####################################################### @@ -104,20 +248,6 @@ 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 { @@ -134,10 +264,10 @@ sub OutputDescriptions { sub CreateAndParseOutputSelector { my $Str = ''; - my $elementname = 'outputmode'; + my $elementname = 'statsoutputmode'; # # Format for output options is 'mode, restrictions'; - my $selected = 'html, with links'; + my $selected = 'HTML problem statistics grouped'; if (exists($ENV{'form.'.$elementname})) { if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) { $selected = $ENV{'form.'.$elementname}->[0]; @@ -158,6 +288,10 @@ sub CreateAndParseOutputSelector { # Build the form element $Str = qq/'); $r->print(''); + $r->print(''); if (! exists($ENV{'form.statsfirstcall'})) { return; } @@ -229,43 +380,50 @@ sub BuildProblemStatisticsPage { } elsif ($show eq 'ungrouped') { &output_html_ungrouped($r); } + } elsif ($output_mode eq 'excel') { + $r->print("

Preparing Excel Spreadsheet

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

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()) { 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'}}) { - if ($part == 0) { - $part = ' '; - } - my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, - $SKEW) = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); - 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). + $problem_num++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + my $option = ''; + $r->print(''.&statistics_html_table_data($data, + 'no container'). "\n"); } } @@ -277,119 +435,75 @@ 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; - } - # - $r->print('
'. - join("",@Header)."
'; - $row .= '' if (defined($part)); - foreach ($num,$tries) { - $row .= ''; - } - foreach ($mod,$mean) { - $row .= ''; - } - foreach ($Solved,$solved) { - $row .= ''; - } - foreach ($wrongpercent) { - $row .= ''; - } - foreach ($DegOfDiff,$STD,$SKEW) { - $row .= ''; + foreach my $field (@Fields) { + next if ($options =~ /no $field->{'name'}/); + $row .= ''; } return $row; } +sub statistics_table_header { + my ($options) = @_; + my $header_row; + foreach my $field (@Fields) { + next if ($options =~ /no $field->{'name'}/); + $header_row .= ''; + } + return $header_row; +} ############################################### ############################################### -sub BuildGraphicChart { - my ($graph,$cacheDB,$courseDescription,$students,$courseID,$r,$c)=@_; - my %cache; - my $max; - my $title = ''; - if($graph eq 'DoDiffGraph') { - $title = 'Degree-of-Difficulty'; - } 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; +=pod + +=item &plot_statistics() + +=cut + +############################################### +############################################### +sub plot_statistics { + my ($r,$datafield) = @_; + my @Data; + # + # + my $sortfield = undef; + my $title = undef; + foreach my $field (@Fields) { + if ($datafield eq $field->{'name'} && + exists($field->{'graphable'}) && $field->{'graphable'} eq 'yes') { + $sortfield = $field->{'name'}; + $title = $field->{'long_title'}; + } } - 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"); + return if (! defined($sortfield) || $sortfield eq ''); + # + my $Max = 0; + 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'}}) { + my $problem_number++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + my $value = $data->{$sortfield}; + $Max = $value if ($Max < $value); + push (@Data,$value); } - $currentSequence = $sequence; - @values = (); - $max=0; - } - my $data = 0; - if($graph eq 'DoDiffGraph') { - $data = sprintf("%.2f", $cache{$_.':degreeOfDifficulty'}), - } else { - $data = sprintf("%.1f", $cache{$_.':percentWrong'}), } - if($max < $data) { - $max = $data; + } + # + # Print out plot request + my $yaxis = ''; + if ($sortfield eq 'per_wrong') { + $yaxis = 'Percent'; + } + # + # Determine appropriate value for $Max + if ($sortfield eq 'deg_of_diff') { + if ($Max > 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; } - push(@values, $data); } - untie(%cache); - &DrawGraph(\@values,$courseDescription,$title,$max,$r); + + $r->print("

".&DrawGraph(\@Data,$title,'Problem Number',$yaxis, + $Max)."

\n"); + # + # Print out the data + $ENV{'form.sortby'} = 'Contents'; +# &output_html_ungrouped($r); return; } +############################################### +############################################### + +=pod + +=item &DrawGraph() + +=cut + +############################################### +############################################### sub DrawGraph { - my ($values,$courseDescription,$title,$Max,$r)=@_; + my ($values,$title,$xaxis,$yaxis,$Max)=@_; + $title = '' if (! defined($title)); + $xaxis = '' if (! defined($xaxis)); + $yaxis = '' if (! defined($yaxis)); + # 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"; + $Max =1 if ($Max < 1); + if ( int($Max) < $Max ) { + $Max++; + $Max = int($Max); } - $Str .= "\n".''."\n"; - return $Str; + my @GData = ($title,$xaxis,$yaxis,$Max,$sendCount,$sendValues); + return ''; } -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->print(''."\n"); - my $Str = ''; - foreach (@Header) { - if (/^(Part)$/) { # Do not allow sorting on this field - $Str .= ''; - } else { - $Str .= ''; - } + if (@Sequences < 1) { + $option .= ' no container'; } - $r->print(''.$Str."\n"); - $r->rflush(); # # Compile the data - my %Statshash; my @Statsarray; foreach my $sequence (@Sequences) { next if ($sequence->{'num_assess'}<1); 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'}); - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } - my $key = $resource->{'symb'}.':'.$part; - $Statshash{$key} = - { - '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,$Statshash{$key}); + $problem_num++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + $show_part = 1 if ($part ne '0'); + # + push (@Statsarray,$data); } } } # # Sort the data - if ($sortby eq 'Container') { - foreach my $sequence (@Sequences) { - next if ($sequence->{'num_assess'}<1); - foreach my $resource (@{$sequence->{'contents'}}) { - next if ($resource->{'type'} ne 'assessment'); - foreach my $part (@{$resource->{'parts'}}) { - my $key = $resource->{'symb'}.':'.$part; - $r->print(''); - if ($show_container) { - $r->print(''); - } - $r->print(&stats_row_from_hash($Statshash{$key})); - $r->print("\n"); - } - } - } + my @OutputOrder; + if ($sortby eq 'container') { + @OutputOrder = @Statsarray; } else { # $sortby is already defined, so we can charge ahead - my @OutputOrder; if ($sortby =~ /^(title|part)$/i) { # Alpha comparison @OutputOrder = sort { - $b->{$sortby} cmp $a->{$sortby} || - $b->{'Title'} cmp $a->{'Title'} || - $b->{'Part'} cmp $a->{'Part'}; + lc($a->{$sortby}) cmp lc($b->{$sortby}) || + lc($a->{'title'}) cmp lc($b->{'title'}) || + lc($a->{'part'}) cmp lc($b->{'part'}); } @Statsarray; } else { # Numerical comparison @@ -409,21 +523,35 @@ sub output_html_ungrouped { } if ($retvalue eq '0') { $retvalue = $b->{$sortby} <=> $a->{$sortby} || - $b->{'Title'} <=> $a->{'Title'} || - $b->{'Part'} <=> $a->{'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(''); - } - $r->print(&stats_row_from_hash($row)); - $r->print("\n"); + } + $option .= 'no part' if (! $show_part); + my $num_output = 0; + # + # output the headers + $r->print('
'.$_.''. - ''. - $_.'
' - .$sequence->{'title'}.'
' - .$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(''.&statistics_html_table_data($rowdata,$option). + "\n"); } $r->print("
\n"); $r->print("
\n"); @@ -432,317 +560,344 @@ sub output_html_ungrouped { return; } -sub stats_row_from_hash { - my ($data) = @_; - if (ref($data) ne 'HASH') { - my %Tmp = @_; - $data = \%Tmp; - } - 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'}); +############################################### +############################################### + +=pod + +=item &output_excel() + +Presents the statistical data in an Excel 95 compatable spreadsheet file. + +=cut + +############################################### +############################################### +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("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($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 { + if ($Sections[0] eq 'all') { + $sectionstring = "All sections"; + } else { + $sectionstring = "Section ".$Sections[0]; + } + } + $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; + # + # Add the headers + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + $excel_sheet->write($rows_output,$cols_output++,$field->{'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 = ' '; + } + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + $excel_sheet->write($rows_output,$cols_output++, + $data->{$field->{'name'}}); + } + $rows_output++; + } + } + } + # + # Write the excel file + $excel_workbook->close(); + # Tell the user where to get their excel file + $r->print('
'. + 'Your Excel spreadsheet.'."\n"); + $r->rflush(); + return; } ############################################### ############################################### +=pod + +=item &statistics_html_table_data() + +Help function used to format the rows for HTML table output. + +=cut + ############################################### ############################################### sub statistics_html_table_data { - my ($resource,$part,$num,$tries,$mod,$mean,$Solved,$solved,$wrongpercent, - $DegOfDiff,$STD,$SKEW) = @_; + my ($data,$options) = @_; my $row = ''; - $row .= '
'. - ''. - $resource->{'title'}.''. - ''.$part.''.$_.''. - sprintf("%5.2f",$_).''.$_.''. - sprintf("%5.1f",$_).''. - sprintf("%5.2f",$_).'{'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 .= ''; + if ($options =~ /sortable/ && + exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') { + $header_row .= '{'name'}."'". + ';document.Statistics.submit();">'; + } + $header_row .= $field->{'title'}; + if ($options =~ /sortable/) { + $header_row.= ''; + } + if ($options !~ /no plots/ && + exists($field->{'graphable'}) && + $field->{'graphable'} eq 'yes') { + $header_row.=' ('; + $header_row .= ''; + $header_row .= 'plot)'; + } + $header_row .= '
P#'; - $Str .= ''.$_.' 
'); - $r->print('

'); - } - if($sortProblems eq 'Sort Within Sequence') { - $r->print(''.$cache->{$sequence.':title'}.''); - } - $r->print('
'."\n"); - $r->print(''."\n"); - $r->print(&CreateProblemStatisticsTableHeading($headings, $r)); - } else { - if($sortProblems eq 'Sort Within Sequence') { - $r->print('"'.$cache->{$sequence.':title'}.'"'); - } - $r->print('
'); - } - $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('
'); +sub get_statistics { + my ($sequence,$resource,$part,$problem_num) = @_; + # + my $symb = $resource->{'symb'}; + my $courseid = $ENV{'request.course.id'}; + # + my $students = \@Apache::lonstatistics::Students; + if ($Apache::lonstatistics::SelectedSections[0] eq 'all') { + $students = undef; } - return; + my $data = &Apache::loncoursedata::get_problem_statistics + ($students,$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; } -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"); - } - return; -} +############################################### +############################################### -# 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"; - } - return \%color; -} - -sub ProblemStatisticsButtons { - my ($displayFormat, $displayLegend, $sortProblems)=@_; - my $Ptr = ''; - $Ptr .= '