Annotation of loncom/interface/statistics/lonproblemanalysis.pm, revision 1.39
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.33 matthew 3:
1.39 ! matthew 4: # $Id: lonproblemanalysis.pm,v 1.38 2003/10/14 22:02:49 matthew Exp $
1.1 stredwic 5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23: #
24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28:
1.11 minaeibi 29: package Apache::lonproblemanalysis;
1.1 stredwic 30:
31: use strict;
32: use Apache::lonnet();
1.25 matthew 33: use Apache::loncommon();
1.7 stredwic 34: use Apache::lonhtmlcommon();
1.23 matthew 35: use Apache::loncoursedata();
36: use Apache::lonstatistics;
37: use Apache::lonlocal;
1.37 matthew 38: use HTML::Entities();
1.2 stredwic 39:
1.39 ! matthew 40: my $plotcolors = [qw/
! 41: #33ff00
! 42: #0033cc #990000 #aaaa66 #663399 #ff9933
! 43: #66ccff #ff9999 #cccc33 #660000 #33cc66
! 44: /];
! 45: #[qw/lgreen dgreen dred/];
! 46:
1.1 stredwic 47: sub BuildProblemAnalysisPage {
1.23 matthew 48: my ($r,$c)=@_;
1.24 matthew 49: $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
1.25 matthew 50: $r->print(&CreateInterface());
1.28 matthew 51: #
52: my @Students = @Apache::lonstatistics::Students;
53: #
1.33 matthew 54: if (exists($ENV{'form.updatecaches'}) ||
55: (exists($ENV{'form.firstanalysis'}) &&
56: $ENV{'form.firstanalysis'} ne 'no')) {
57: &Apache::lonstatistics::Gather_Full_Student_Data($r);
58: }
59: if (! exists($ENV{'form.firstanalysis'})) {
60: $r->print('<input type="hidden" name="firstanalysis" value="yes" />');
61: } else {
62: $r->print('<input type="hidden" name="firstanalysis" value="no" />');
63: }
1.39 ! matthew 64: $r->rflush();
1.33 matthew 65: if (exists($ENV{'form.problemchoice'}) &&
66: ! exists($ENV{'form.SelectAnother'})) {
1.31 matthew 67: $r->print('<input type="submit" name="ProblemAnalysis" value="'.
68: &mt('Analyze Problem Again').'" />');
69: $r->print(' 'x5);
70: $r->print('<input type="submit" name="ClearCache" value="'.
71: &mt('Clear Caches').'" />');
72: $r->print(' 'x5);
1.33 matthew 73: $r->print('<input type="submit" name="updatecaches" value="'.
74: &mt('Update Student Data').'" />');
75: $r->print(' 'x5);
1.31 matthew 76: $r->print('<input type="hidden" name="problemchoice" value="'.
77: $ENV{'form.problemchoice'}.'" />');
78: $r->print('<input type="submit" name="SelectAnother" value="'.
79: &mt('Choose a different resource').'" />');
80: $r->print(' 'x5);
81: #
1.25 matthew 82: $r->print('<hr />');
1.23 matthew 83: #
1.25 matthew 84: my ($symb,$part,$resid) = &get_problem_symb(
1.23 matthew 85: &Apache::lonnet::unescape($ENV{'form.problemchoice'})
86: );
1.28 matthew 87: #
1.23 matthew 88: my $resource = &get_resource_from_symb($symb);
89: if (defined($resource)) {
1.25 matthew 90: my %Data = &get_problem_data($resource->{'src'});
91: my $ORdata = $Data{$part.'.'.$resid};
92: ##
1.26 matthew 93: ## Render the problem
1.25 matthew 94: my $base;
95: ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
96: $base = "http://".$ENV{'SERVER_NAME'}.$base;
1.26 matthew 97: my $rendered_problem =
98: &Apache::lonnet::ssi_body($resource->{'src'});
1.30 matthew 99: $rendered_problem =~ s/<\s*form\s*/<nop /g;
100: $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
1.26 matthew 101: $r->print('<table bgcolor="ffffff"><tr><td>'.
1.25 matthew 102: '<base href="'.$base.'" />'.
1.26 matthew 103: $rendered_problem.
104: '</td></tr></table>');
1.25 matthew 105: ##
106: ## Analyze the problem
1.26 matthew 107: my $PerformanceData =
108: &Apache::loncoursedata::get_optionresponse_data
1.28 matthew 109: (\@Students,$symb,$resid);
1.26 matthew 110: if (defined($PerformanceData) &&
111: ref($PerformanceData) eq 'ARRAY') {
1.36 matthew 112: if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
1.33 matthew 113: my $analysis_html = &tries_analysis($PerformanceData,
114: $ORdata);
115: $r->print($analysis_html);
1.36 matthew 116: } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
1.33 matthew 117: my $analysis_html = &time_analysis($PerformanceData,
1.28 matthew 118: $ORdata);
1.26 matthew 119: $r->print($analysis_html);
1.28 matthew 120: } else {
121: $r->print('<h2>'.
122: &mt('The analysis you have selected is '.
123: 'not supported at this time').
124: '</h2>');
125: }
1.26 matthew 126: } else {
127: $r->print('<h2>'.
128: &mt('There is no student data for this problem.').
129: '</h2>');
130: }
1.23 matthew 131: } else {
132: $r->print('resource is undefined');
1.7 stredwic 133: }
1.23 matthew 134: $r->print('<hr />');
1.25 matthew 135: } else {
1.31 matthew 136: $r->print('<input type="submit" name="ProblemAnalysis" value="'.
137: &mt('Analyze Problem').'" />');
138: $r->print(' 'x5);
1.27 matthew 139: $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
1.31 matthew 140: $r->print(&OptionResponseProblemSelector());
1.1 stredwic 141: }
142: }
143:
1.25 matthew 144:
1.33 matthew 145: #########################################################
146: #########################################################
147: ##
148: ## Misc interface routines use by analysis code
149: ##
150: #########################################################
151: #########################################################
152: sub build_foil_index {
153: my ($ORdata) = @_;
1.36 matthew 154: my %Foildata = %{$ORdata->{'Foils'}};
155: my @Foils = sort(keys(%Foildata));
156: my %Concepts;
1.25 matthew 157: foreach my $foilid (@Foils) {
1.36 matthew 158: push(@{$Concepts{$Foildata{$foilid}->{'Concept'}}},
159: $foilid);
160: }
161: undef(@Foils);
162: # Having gathered the concept information in a hash, we now translate it
163: # into an array because we need to be consistent about order.
164: # Also put the foils in order, too.
165: my $sortfunction = sub {
166: my %Numbers = (one => 1,
167: two => 2,
168: three => 3,
169: four => 4,
170: five => 5,
171: six => 6,
172: seven => 7,
173: eight => 8,
174: nine => 9,
175: ten => 10,);
176: my $a1 = $a;
177: my $b1 = $b;
1.39 ! matthew 178: if (exists($Numbers{lc($a)})) {
! 179: $a1 = $Numbers{lc($a)};
1.36 matthew 180: }
1.39 ! matthew 181: if (exists($Numbers{lc($b)})) {
! 182: $b1 = $Numbers{lc($b)};
1.36 matthew 183: }
184: $a1 cmp $b1;
185: };
186: my @Concepts;
187: foreach my $concept (sort $sortfunction (keys(%Concepts))) {
1.39 ! matthew 188: push(@Concepts,{ name => $concept,
1.36 matthew 189: foils => [@{$Concepts{$concept}}]});
190: push(@Foils,(@{$Concepts{$concept}}));
1.25 matthew 191: }
1.31 matthew 192: #
193: # Build up the table of row labels.
194: my $table = '<table border="1" >'."\n";
1.39 ! matthew 195: if (@Concepts > 1) {
! 196: $table .= '<tr>'.
! 197: '<th>'.&mt('Concept Number').'</th>'.
! 198: '<th>'.&mt('Concept').'</th>'.
! 199: '<th>'.&mt('Foil Number').'</th>'.
! 200: '<th>'.&mt('Foil Name').'</th>'.
! 201: '<th>'.&mt('Foil Text').'</th>'.
! 202: '<th>'.&mt('Correct Value').'</th>'.
! 203: "</tr>\n";
! 204: } else {
! 205: $table .= '<tr>'.
! 206: '<th>'.&mt('Foil Number').'</th>'.
! 207: '<th>'.&mt('Foil Name').'</th>'.
! 208: '<th>'.&mt('Foil Text').'</th>'.
! 209: '<th>'.&mt('Correct Value').'</th>'.
! 210: "</tr>\n";
! 211: }
1.36 matthew 212: my $conceptindex = 1;
213: my $foilindex = 1;
214: foreach my $concept (@Concepts) {
215: my @FoilsInConcept = @{$concept->{'foils'}};
216: my $firstfoil = shift(@FoilsInConcept);
1.39 ! matthew 217: if (@Concepts > 1) {
! 218: $table .= '<tr>'.
! 219: '<td>'.$conceptindex.'</td>'.
! 220: '<td>'.$concept->{'name'}.'</td>'.
! 221: '<td>'.$foilindex++.'</td>'.
! 222: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
! 223: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
! 224: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
! 225: "</tr>\n";
! 226: } else {
1.36 matthew 227: $table .= '<tr>'.
1.39 ! matthew 228: '<td>'.$foilindex++.'</td>'.
! 229: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
! 230: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
! 231: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
1.36 matthew 232: "</tr>\n";
1.39 ! matthew 233: }
! 234: foreach my $foilid (@FoilsInConcept) {
! 235: if (@Concepts > 1) {
! 236: $table .= '<tr>'.
! 237: '<td></td>'.
! 238: '<td></td>'.
! 239: '<td>'.$foilindex.'</td>'.
! 240: '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
! 241: '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
! 242: '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
! 243: "</tr>\n";
! 244: } else {
! 245: $table .= '<tr>'.
! 246: '<td>'.$foilindex.'</td>'.
! 247: '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
! 248: '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
! 249: '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
! 250: "</tr>\n";
! 251: }
1.36 matthew 252: } continue {
253: $foilindex++;
254: }
1.31 matthew 255: } continue {
1.36 matthew 256: $conceptindex++;
1.25 matthew 257: }
1.31 matthew 258: $table .= "</table>\n";
1.39 ! matthew 259: #
! 260: # Build option index with color stuff
1.36 matthew 261: return ($table,\@Foils,\@Concepts);
1.33 matthew 262: }
263:
1.39 ! matthew 264: sub build_option_index {
! 265: my ($ORdata)= @_;
! 266: my $table = "<table>\n";
! 267: my $optionindex = 0;
! 268: my @Rows;
! 269: foreach my $option ('correct',@{$ORdata->{'Options'}}) {
! 270: push (@Rows,
! 271: '<tr>'.
! 272: '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
! 273: (' 'x4).'</td>'.
! 274: '<td>'.$option.'</td>'.
! 275: "</tr>\n");
! 276: }
! 277: $table .= join('',reverse(@Rows));
! 278: $table .= "</table>\n";
! 279: }
! 280:
1.33 matthew 281: #########################################################
282: #########################################################
283: ##
284: ## Tries Analysis
285: ##
286: #########################################################
287: #########################################################
288: sub tries_analysis {
289: my ($PerformanceData,$ORdata) = @_;
290: my $mintries = 1;
291: my $maxtries = $ENV{'form.NumPlots'};
1.39 ! matthew 292: my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
! 293: if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
! 294: $table = '<h3>'.
! 295: &mt('Not enough data for concept analysis. '.
! 296: 'Performing Foil Analysis').
! 297: '</h3>'.$table;
! 298: $ENV{'form.AnalyzeAs'} = 'Foils';
! 299: }
1.33 matthew 300: my %ResponseData = &analyze_option_data_by_tries($PerformanceData,
1.36 matthew 301: $mintries,$maxtries);
1.31 matthew 302: #
303: # Compute the data neccessary to make the plots
1.39 ! matthew 304: my @PlotData; # Array which holds the data for each plot
! 305: # @{$PlotData[$try]->{'datasetname'}} holds the data for
! 306: # try $try with respect to 'datasetname'. The array is
! 307: # filled either with per-foil or per-concept data.
! 308: my ($extrakey,$xlabel,$ylabel);
1.36 matthew 309: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
1.39 ! matthew 310: $extrakey = &build_option_index($ORdata);
1.36 matthew 311: $xlabel = 'Foil Number';
1.39 ! matthew 312: $ylabel = 'Option Chosen';
1.36 matthew 313: foreach my $foilid (@$Foils) {
314: for (my $i=$mintries;$i<=$maxtries;$i++) {
1.39 ! matthew 315: foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
! 316: push(@{$PlotData[$i]->{'_total'}},
! 317: $ResponseData{$foilid}->[$i]->{'_total'});
! 318: if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
! 319: push (@{$PlotData[$i]->{$option}},0);
! 320: } else {
! 321: push (@{$PlotData[$i]->{$option}},
! 322: 100 * $ResponseData{$foilid}->[$i]->{$option} /
! 323: $ResponseData{$foilid}->[$i]->{'_total'});
! 324: }
1.36 matthew 325: }
326: }
327: }
328: } else {
329: # Concept analysis
1.39 ! matthew 330: #
! 331: # Note: we do not bother with characterizing the students incorrect
! 332: # answers at the concept level because an incorrect answer for one foil
! 333: # may be a correct answer for another foil.
! 334: $extrakey = '';
1.36 matthew 335: $xlabel = 'Concept Number';
1.39 ! matthew 336: $ylabel = 'Percent Correct';
! 337: my %ConceptData;
! 338: foreach my $concept (@{$Concepts}) {
1.36 matthew 339: for (my $i=$mintries;$i<=$maxtries;$i++) {
340: #
341: # Gather the per-attempt data
1.39 ! matthew 342: my $cdata = $ConceptData{$concept}->[$i];
! 343: foreach my $foilid (@{$concept->{'foils'}}) {
! 344: $cdata->{'_correct'} +=
! 345: $ResponseData{$foilid}->[$i]->{'_correct'};
! 346: $cdata->{'_total'} +=
! 347: $ResponseData{$foilid}->[$i]->{'_total'};
1.36 matthew 348: }
1.39 ! matthew 349: push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
! 350: if ($cdata->{'_total'} == 0) {
! 351: push (@{$PlotData[$i]->{'_correct'}},0);
1.36 matthew 352: } else {
1.39 ! matthew 353: push (@{$PlotData[$i]->{'_correct'}},
! 354: 100*$cdata->{'_correct'}/$cdata->{'_total'});
1.36 matthew 355: }
356: }
1.25 matthew 357: }
1.39 ! matthew 358: } # End of work to fill @PlotData
1.31 matthew 359: #
360: # Build a table for the plots
361: $table .= "<table>\n";
362: my @Plots;
1.25 matthew 363: for (my $i=$mintries;$i<=$maxtries;$i++) {
1.39 ! matthew 364: my $minstu = $PlotData[$i]->{'_total'}->[0];
! 365: my $maxstu = $PlotData[$i]->{'_total'}->[0];
! 366: foreach my $count (@{$PlotData[$i]->{'_total'}}) {
1.36 matthew 367: if ($minstu > $count) {
368: $minstu = $count;
1.27 matthew 369: }
1.36 matthew 370: if ($maxstu < $count) {
371: $maxstu = $count;
1.27 matthew 372: }
373: }
1.39 ! matthew 374: $maxstu = 0 if (! defined($maxstu));
! 375: $minstu = 0 if (! defined($minstu));
1.35 matthew 376: my $title;
1.27 matthew 377: if ($maxstu == $minstu) {
1.35 matthew 378: $title = 'Attempt '.$i.', '.$maxstu.' students';
1.27 matthew 379: } else {
1.35 matthew 380: $title = 'Attempt '.$i.', '.$minstu.'-'.$maxstu.' students';
1.27 matthew 381: }
1.39 ! matthew 382: my @Datasets;
! 383: foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
! 384: next if (! exists($PlotData[$i]->{$option}));
! 385: push(@Datasets,$PlotData[$i]->{$option});
! 386: }
1.35 matthew 387: my $graphlink = &Apache::loncommon::DrawGraph($title,
1.36 matthew 388: $xlabel,
1.39 ! matthew 389: $ylabel,
1.35 matthew 390: 100,
1.39 ! matthew 391: $plotcolors,
! 392: @Datasets);
1.31 matthew 393: push(@Plots,$graphlink);
1.25 matthew 394: }
1.31 matthew 395: #
396: # Should this be something the user can set? Too many dialogs!
397: while (my $plotlink = shift(@Plots)) {
1.39 ! matthew 398: $table .= '<tr><td>'.$plotlink.'</td><td>'.$extrakey."</td></tr>\n";
1.25 matthew 399: }
1.31 matthew 400: $table .= "</table>\n";
1.25 matthew 401: return ($table);
402: }
403:
404: sub analyze_option_data_by_tries {
1.26 matthew 405: my ($PerformanceData,$mintries,$maxtries) = @_;
1.25 matthew 406: my %Trydata;
407: $mintries = 1 if (! defined($mintries) || $mintries < 1);
408: $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
1.26 matthew 409: foreach my $row (@$PerformanceData) {
410: next if (! defined($row));
1.25 matthew 411: my ($grading,$submission,$time,$tries) = @$row;
1.39 ! matthew 412: next if ($grading eq 'MISSING_ANSWER');
1.25 matthew 413: my @Foilgrades = split('&',$grading);
414: my @Foilsubs = split('&',$submission);
415: for (my $numtries = 1; $numtries <= $maxtries; $numtries++) {
416: if ($tries == $numtries) {
1.39 ! matthew 417: for (my $i=0;$i<=$#Foilgrades;$i++) {
! 418: my ($foilid,$correct) = split('=',$Foilgrades[$i]);
! 419: my (undef,$submission) = split('=',$Foilsubs[$i]);
! 420: $submission = &HTML::Entities::decode($submission);
! 421: $submission =~ s/\%20/ /g;
1.25 matthew 422: if ($correct) {
1.39 ! matthew 423: $Trydata{$foilid}->[$numtries]->{'_correct'}++;
1.25 matthew 424: } else {
1.39 ! matthew 425: $Trydata{$foilid}->[$numtries]->{$submission}++;
1.25 matthew 426: }
1.39 ! matthew 427: $Trydata{$foilid}->[$numtries]->{'_total'}++;
1.25 matthew 428: }
429: }
430: }
431: }
432: return %Trydata;
433: }
434:
1.33 matthew 435: #########################################################
436: #########################################################
437: ##
438: ## Time Analysis
439: ##
440: #########################################################
441: #########################################################
442: sub time_analysis {
443: my ($PerformanceData,$ORdata) = @_;
444: my $num_plots = $ENV{'form.NumPlots'};
1.36 matthew 445: my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
1.33 matthew 446: my $num_data = scalar(@$PerformanceData)-1;
447: my $percent = sprintf('%2f',100/$num_plots);
1.39 ! matthew 448: my $extratable = '';
! 449: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
! 450: $extratable = &build_option_index($ORdata);
! 451: }
1.37 matthew 452: $table .= "<table>\n";
1.33 matthew 453: for (my $i=0;$i<$num_plots;$i++) {
1.34 matthew 454: my $starttime = &Apache::lonhtmlcommon::get_date_from_form
455: ('startdate_'.$i);
456: my $endtime = &Apache::lonhtmlcommon::get_date_from_form
457: ('enddate_'.$i);
458: my ($begin_index,$end_index,$plottitle,$plothtml,$data);
459: if (! defined($starttime) || ! defined($endtime)) {
460: $begin_index = $i*int($num_data/$num_plots);
461: $end_index = ($i+1)*int($num_data/$num_plots);
462: my $lownum = sprintf('%2.1f',$i*$percent);
463: $lownum =~ s/(\.0)$//;
464: my $highnum = sprintf('%2.1f',($i+1)*$percent);
465: $highnum =~ s/(\.0)$//;
466: $plottitle = $lownum.'% to '.$highnum.'% of submissions';
467: } else {
468: my $j;
469: while (++$j < scalar(@$PerformanceData)) {
470: last if ($PerformanceData->[$j]->[2] > $starttime);
471: }
472: $begin_index = $j;
473: while (++$j < scalar(@$PerformanceData)) {
474: last if ($PerformanceData->[$j]->[2] > $endtime);
475: }
476: $end_index = $j;
1.37 matthew 477: $plottitle = $ENV{'form.plottitle_'.$i};
1.34 matthew 478: }
479: ($plothtml,$starttime,$endtime,$data) =
1.33 matthew 480: &analyze_option_data_by_time($PerformanceData,
481: $begin_index,$end_index,
1.39 ! matthew 482: $plottitle,$Foils,
! 483: $Concepts,$ORdata);
1.34 matthew 484: my $startdateform = &Apache::lonhtmlcommon::date_setter
485: ('Statistics','startdate_'.$i,$starttime);
486: my $enddateform = &Apache::lonhtmlcommon::date_setter
487: ('Statistics','enddate_'.$i,$endtime);
1.37 matthew 488: $table.="<tr><td>".$plothtml.'</td><td align="left" valign="top">'.
489: "<b>Start Time</b>: ".$startdateform."<br />".
490: "<b>End Time</b> : "." ".$enddateform."<br />".
1.38 matthew 491: '<b>Plot Title</b> :'.(" "x3).
1.37 matthew 492: '<input type="text" size="30" name="plottitle_'.$i.'" value="'.
1.39 ! matthew 493: &HTML::Entities::encode($plottitle).'" /><br />'.$extratable.
1.37 matthew 494: "</td></tr>\n";
1.33 matthew 495: }
1.37 matthew 496: $table .="</table>\n";
1.33 matthew 497: return $table;
498: }
499:
500: sub analyze_option_data_by_time {
1.39 ! matthew 501: my ($PerformanceData,$begin_index,
! 502: $end_index,$description,$Foils,$Concepts,$ORdata) = @_;
1.33 matthew 503: my %TimeData;
504: #
505: # Get the start and end times for this segment of the plot
506: my $starttime = $PerformanceData->[$begin_index]->[2];
507: my $endtime = $PerformanceData->[$end_index ]->[2];
508: #
509: # Compute the number getting the foils correct or incorrects
510: for (my $i=$begin_index;$i<=$end_index;$i++) {
511: my $row = $PerformanceData->[$i];
512: next if (! defined($row));
513: my ($grading,$submission,$time,$tries) = @$row;
1.39 ! matthew 514: next if ($grading eq 'MISSING_ANSWER');
1.33 matthew 515: my @Foilgrades = split('&',$grading);
516: my @Foilsubs = split('&',$submission);
1.39 ! matthew 517: for (my $j=0;$j<=$#Foilgrades;$j++) {
! 518: my ($foilid,$correct) = split('=',$Foilgrades[$j]);
! 519: my (undef,$submission) = split('=',$Foilsubs[$j]);
1.33 matthew 520: if ($correct) {
1.39 ! matthew 521: $TimeData{$foilid}->{'_correct'}++;
1.33 matthew 522: } else {
1.39 ! matthew 523: $submission = &HTML::Entities::decode($submission);
! 524: $submission =~ s/\%20/ /g;
! 525: $TimeData{$foilid}->{$submission}++;
1.33 matthew 526: }
1.39 ! matthew 527: $TimeData{$foilid}->{'_total'}++;
1.33 matthew 528: }
529: }
530: #
531: # Compute the total and percent correct
1.39 ! matthew 532: my @Plotdata;
! 533: my ($xlabel,$ylabel);
! 534: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
! 535: $xlabel = 'Foil Number';
! 536: $ylabel = 'Option Chosen';
! 537: foreach my $foil (@$Foils) {
! 538: my $total = $TimeData{$foil}->{'_total'};
! 539: my $optionidx = 0;
! 540: foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
! 541: if ($total > 0) {
! 542: push(@{$Plotdata[$optionidx]},
! 543: 100 * $TimeData{$foil}->{$option} / $total);
! 544: } else {
! 545: push(@{$Plotdata[$optionidx]},0);
! 546: }
! 547: } continue {
! 548: $optionidx++;
1.37 matthew 549: }
1.39 ! matthew 550: }
! 551: } else {
! 552: $xlabel = 'Concept Number';
! 553: $ylabel = 'Percent Correct';
! 554: foreach my $concept (@$Concepts) {
! 555: my $correct;
! 556: my $total;
! 557: foreach my $foil (@{$concept->{'foils'}}) {
! 558: $correct+=$TimeData{$foil}->{'_correct'};
! 559: $total +=$TimeData{$foil}->{'_total'};
1.37 matthew 560: }
1.39 ! matthew 561: if ($total > 0) {
! 562: push(@{$Plotdata[0]},100 * $correct / $total);
1.37 matthew 563: } else {
1.39 ! matthew 564: push(@{$Plotdata[0]},0);
1.37 matthew 565: }
1.33 matthew 566: }
567: }
568: #
569: # Create the plot
570: my $graphlink = &Apache::loncommon::DrawGraph
571: ($description,#'Time Interval Analysis',
1.37 matthew 572: $xlabel,
1.39 ! matthew 573: $ylabel,
1.33 matthew 574: 100,
1.39 ! matthew 575: $plotcolors,
! 576: @Plotdata);
1.33 matthew 577: #
578: return ($graphlink,$starttime,$endtime,\%TimeData);
1.1 stredwic 579: }
580:
1.33 matthew 581: #########################################################
582: #########################################################
583: ##
584: ## Interface
585: ##
586: #########################################################
587: #########################################################
1.23 matthew 588: sub CreateInterface {
1.28 matthew 589: ##
590: ## Environment variable initialization
1.36 matthew 591: if (! exists$ENV{'form.AnalyzeOver'}) {
592: $ENV{'form.AnalyzeOver'} = 'Tries';
1.28 matthew 593: }
594: ##
595: ## Build the menu
1.7 stredwic 596: my $Str = '';
1.23 matthew 597: $Str .= '<table cellspacing="5">'."\n";
598: $Str .= '<tr>';
599: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
600: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
1.31 matthew 601: # $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
602: $Str .= '<td align="center"> </td>';
1.23 matthew 603: $Str .= '</tr>'."\n";
1.31 matthew 604: ##
605: ##
1.23 matthew 606: $Str .= '<tr><td align="center">'."\n";
607: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.28 matthew 608: $Str .= '</td>';
609: #
610: $Str .= '<td align="center">';
1.23 matthew 611: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.28 matthew 612: $Str .= '</td>';
613: #
1.31 matthew 614: # $Str .= '<td align="center">';
1.23 matthew 615: my $only_seq_with_assessments = sub {
616: my $s=shift;
617: if ($s->{'num_assess'} < 1) {
618: return 0;
619: } else {
620: return 1;
621: }
622: };
1.31 matthew 623: &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
1.23 matthew 624: $only_seq_with_assessments);
1.36 matthew 625: ##
626: ##
1.28 matthew 627: $Str .= '<td>';
1.36 matthew 628: { # These braces are here to organize the code, not scope it.
629: {
630: $Str .= '<nobr>'.&mt('Analyze Over ');
631: $Str .='<select name="AnalyzeOver" >';
632: $Str .= '<option value="Tries" ';
633: if (! exists($ENV{'form.AnalyzeOver'}) ||
634: $ENV{'form.AnalyzeOver'} eq 'Tries'){
635: # Default to Tries
636: $Str .= ' selected ';
637: }
638: $Str .= '>'.&mt('Tries').'</option>';
639: $Str .= '<option value="Time" ';
640: $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
641: $Str .= '>'.&mt('Time').'</option>';
642: $Str .= '</select></nobr><br />';
643: }
644: {
645: $Str .= '<nobr>'.&mt('Analyze as ');
646: $Str .='<select name="AnalyzeAs" >';
647: $Str .= '<option value="Concepts" ';
648: if (! exists($ENV{'form.AnalyzeAs'}) ||
649: $ENV{'form.AnalyzeAs'} eq 'Concepts'){
650: # Default to Concepts
651: $Str .= ' selected ';
652: }
653: $Str .= '>'.&mt('Concepts').'</option>';
654: $Str .= '<option value="Foils" ';
655: $Str .= ' selected ' if ($ENV{'form.AnalyzeAs'} eq 'Foils');
656: $Str .= '>'.&mt('Foils').'</option>';
657: $Str .= '</select></nobr><br />';
658: }
659: {
660: $Str .= '<br /><nobr>'.&mt('Number of Plots:');
661: $Str .= '<select name="NumPlots">';
662: if (! exists($ENV{'form.NumPlots'})
663: || $ENV{'form.NumPlots'} < 1
664: || $ENV{'form.NumPlots'} > 20) {
665: $ENV{'form.NumPlots'} = 5;
666: }
667: foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
668: $Str .= '<option value="'.$i.'" ';
669: if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
670: $Str .= '>'.$i.'</option>';
671: }
672: $Str .= '</select></nobr>';
673: }
1.28 matthew 674: }
675: $Str .= '</td>';
1.36 matthew 676: ##
677: ##
1.28 matthew 678: $Str .= '</tr>'."\n";
1.23 matthew 679: $Str .= '</table>'."\n";
680: return ($Str);
681: }
682:
683: sub OptionResponseProblemSelector {
684: my $Str;
685: $Str = "\n<table>\n";
686: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
687: next if ($seq->{'num_assess'}<1);
688: my $seq_str = '';
689: foreach my $res (@{$seq->{'contents'}}) {
1.26 matthew 690: next if ($res->{'type'} ne 'assessment');
1.23 matthew 691: foreach my $part (@{$res->{'parts'}}) {
692: my $partdata = $res->{'partdata'}->{$part};
693: if (! exists($partdata->{'option'}) ||
694: $partdata->{'option'} == 0) {
695: next;
696: }
697: for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
698: my $respid = $partdata->{'ResponseIds'}->[$i];
699: my $resptype = $partdata->{'ResponseTypes'}->[$i];
700: if ($resptype eq 'option') {
1.25 matthew 701: my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$part.':'.$respid);
1.23 matthew 702: my $checked = '';
703: if ($ENV{'form.problemchoice'} eq $value) {
704: $checked = 'checked ';
705: }
706: $seq_str .= '<tr><td>'.
707: '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
708: '</td><td>'.
709: '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> ';
710: if ($partdata->{'option'} > 1) {
711: $seq_str .= &mt('response').' '.$respid;
712: }
713: $seq_str .= "</td></tr>\n";
1.11 minaeibi 714: }
715: }
716: }
717: }
1.23 matthew 718: if ($seq_str ne '') {
719: $Str .= '<tr><td> </td><td><b>'.$seq->{'title'}.'</b></td>'.
720: "</tr>\n".$seq_str;
721: }
1.11 minaeibi 722: }
1.23 matthew 723: $Str .= "</table>\n";
724: return $Str;
1.33 matthew 725: }
726:
727: #########################################################
728: #########################################################
729: ##
730: ## Misc functions
731: ##
732: #########################################################
733: #########################################################
734: sub get_problem_symb {
735: my $problemstring = shift();
736: my ($symb,$partid,$resid) = ($problemstring=~ /^(.*):([^:]*):([^:]*)$/);
737: return ($symb,$partid,$resid);
1.11 minaeibi 738: }
739:
1.23 matthew 740: sub get_resource_from_symb {
741: my ($symb) = @_;
742: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
743: foreach my $res (@{$seq->{'contents'}}) {
744: if ($res->{'symb'} eq $symb) {
745: return $res;
1.2 stredwic 746: }
1.1 stredwic 747: }
748: }
1.23 matthew 749: return undef;
1.1 stredwic 750: }
751:
1.39 ! matthew 752: ##
! 753: ## get problem data and put it into a useful data structure.
! 754: ## note: we must force each foil and option to not begin or end with
! 755: ## spaces as they are stored without such data.
! 756: ##
1.25 matthew 757: sub get_problem_data {
758: my ($url) = @_;
759: my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
1.23 matthew 760: (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
1.25 matthew 761: my %Answer;
1.23 matthew 762: %Answer=&Apache::lonnet::str2hash($Answ);
1.25 matthew 763: my %Partdata;
764: foreach my $part (@{$Answer{'parts'}}) {
765: while (my($key,$value) = each(%Answer)) {
766: next if ($key !~ /^$part/);
767: $key =~ s/^$part\.//;
768: if (ref($value) eq 'ARRAY') {
769: if ($key eq 'options') {
1.39 ! matthew 770: for(my $i=0;$i<scalar(@$value);$i++) {
! 771: $value->[$i]=~ s/(\s*$|^\s*)//g;
! 772: }
1.25 matthew 773: $Partdata{$part}->{'Options'}=$value;
774: } elsif ($key eq 'concepts') {
775: $Partdata{$part}->{'Concepts'}=$value;
776: } elsif ($key =~ /^concept\.(.*)$/) {
777: my $concept = $1;
778: foreach my $foil (@$value) {
1.39 ! matthew 779: $foil =~ s/(\s*$|^\s*)//g;
1.36 matthew 780: $Partdata{$part}->{'Foils'}->{$foil}->{'Concept'}=
781: $concept;
1.25 matthew 782: }
783: }
784: } else {
1.39 ! matthew 785: $value =~ s/(\s*$|^\s*)//g;
1.25 matthew 786: if ($key=~ /^foil\.text\.(.*)$/) {
787: my $foil = $1;
1.39 ! matthew 788: $foil =~ s/(\s*$|^\s*)//g;
1.36 matthew 789: $Partdata{$part}->{'Foils'}->{$foil}->{'name'}=$foil;
790: $Partdata{$part}->{'Foils'}->{$foil}->{'text'}=$value;
1.25 matthew 791: } elsif ($key =~ /^foil\.value\.(.*)$/) {
792: my $foil = $1;
1.39 ! matthew 793: $foil =~ s/(\s*$|^\s*)//g;
1.36 matthew 794: $Partdata{$part}->{'Foils'}->{$foil}->{'value'}=$value;
1.25 matthew 795: }
796: }
797: }
1.23 matthew 798: }
1.25 matthew 799: return %Partdata;
1.1 stredwic 800: }
801:
1.23 matthew 802: 1;
1.1 stredwic 803:
804: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>