/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.ims.qti.statistics.ui; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.duration; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.format; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.getModeString; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.chart.BarSeries; import org.olat.core.gui.components.chart.BarSeries.Stringuified; import org.olat.core.gui.components.chart.StatisticsComponent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.link.LinkPopupSettings; import org.olat.core.gui.components.stack.TooledController; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.components.stack.TooledStackedPanel.Align; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.creator.ControllerCreator; import org.olat.core.gui.media.MediaResource; import org.olat.core.util.CodeHelper; import org.olat.course.nodes.QTICourseNode; import org.olat.course.nodes.iq.IQEditController; import org.olat.ims.qti.editor.beecom.objects.Item; import org.olat.ims.qti.editor.beecom.objects.QTIDocument; import org.olat.ims.qti.editor.beecom.objects.Section; import org.olat.ims.qti.statistics.QTIStatisticResourceResult; import org.olat.ims.qti.statistics.QTIStatisticsManager; import org.olat.ims.qti.statistics.QTIStatisticsResource; import org.olat.ims.qti.statistics.QTIType; import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.model.StatisticItem; import org.olat.ims.qti.statistics.model.StatisticSurveyItem; import org.olat.repository.RepositoryManager; /** * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class QTI12AssessmentStatisticsController extends BasicController implements TooledController { private final QTIType type; private final Float maxScore; private final Float cutValue; private final String mediaBaseURL; private final Long courseResourceID; private final Long repoEntryId; private final VelocityContainer mainVC; private final TooledStackedPanel stackPanel; private final Link printLink, downloadRawLink; private final SeriesFactory seriesfactory; private final QTIStatisticResourceResult resourceResult; private final QTIStatisticsManager qtiStatisticsManager; public QTI12AssessmentStatisticsController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, QTIStatisticResourceResult resourceResult, boolean printMode) { super(ureq, wControl); type = resourceResult.getType(); this.stackPanel = stackPanel; this.resourceResult = resourceResult; mediaBaseURL = resourceResult.getMediaBaseURL(); seriesfactory = new SeriesFactory(resourceResult, getTranslator()); qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); courseResourceID = RepositoryManager.getInstance().lookupRepositoryEntryKey(resourceResult.getCourseOres(), false); repoEntryId = resourceResult.getQTIRepositoryEntry().getResourceableId(); mainVC = createVelocityContainer("statistics_assessment"); mainVC.put("loadd3js", new StatisticsComponent("d3loader")); mainVC.contextPut("printMode", new Boolean(printMode)); if(stackPanel != null) { printLink = LinkFactory.createToolLink("print" + CodeHelper.getRAMUniqueID(), translate("print"), this); printLink.setIconLeftCSS("o_icon o_icon_print o_icon-lg"); printLink.setPopup(new LinkPopupSettings(680, 500, "qti-stats")); stackPanel.addTool(printLink, Align.right); downloadRawLink = LinkFactory.createToolLink("download" + CodeHelper.getRAMUniqueID(), translate("download.raw.data"), this); stackPanel.addTool(downloadRawLink, Align.right); } else { printLink = null; downloadRawLink = LinkFactory.createLink("download.raw.data", mainVC, this); downloadRawLink.setCustomEnabledLinkCSS("o_content_download"); mainVC.put("download", downloadRawLink); } downloadRawLink.setIconLeftCSS("o_icon o_icon_download o_icon-lg"); //cut value QTICourseNode testNode = resourceResult.getTestCourseNode(); StatisticAssessment stats = resourceResult.getQTIStatisticAssessment(); boolean hasEssay = false; List<Item> items = new ArrayList<>(); QTIDocument qtiDocument = resourceResult.getQTIDocument(); for(Section section:qtiDocument.getAssessment().getSections()) { for(Item item:section.getItems()) { items.add(item); String ident = item.getIdent(); if(ident != null && ident.startsWith("QTIEDIT:ESSAY")) { hasEssay = true; } } } if(hasEssay) { mainVC.contextPut("hasEssay", Boolean.TRUE); } cutValue = getCutValueSetting(testNode); maxScore = getMaxScoreSetting(testNode, items); initCourseNodeInformation(stats); initDurationHistogram(resourceResult.getQTIStatisticAssessment()); if(QTIType.test.equals(type)) { initScoreHistogram(stats); initScoreStatisticPerItem(items, stats.getNumOfParticipants()); } else { initItemsOverview(items); } putInitialPanel(mainVC); } @Override protected void doDispose() { if(stackPanel != null) { stackPanel.removeTool(downloadRawLink); stackPanel.removeTool(printLink); } } @Override public void initTools() { if(stackPanel != null) { stackPanel.addTool(printLink, Align.right); stackPanel.addTool(downloadRawLink, Align.right); } } private Float getCutValueSetting(QTICourseNode testNode) { Float cutValueSetting; if(QTIType.test.equals(type)) { Object cutScoreObj = testNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_CUTVALUE); if (cutScoreObj instanceof Float) { cutValueSetting = (Float)cutScoreObj; } else { cutValueSetting = null; } } else { cutValueSetting = null; } return cutValueSetting; } private Float getMaxScoreSetting(QTICourseNode testNode, List<Item> items) { Float maxScoreSetting; if(QTIType.test.equals(type)) { Object maxScoreObj = testNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_MAXSCORE); if (maxScoreObj instanceof Float) { maxScoreSetting = (Float)maxScoreObj; } else { // try to calculate max float max = 0; for (Item item: items) { if(item.getQuestion() != null) { max += item.getQuestion().getMaxValue(); } } maxScoreSetting = max > 0 ? max : null; } } else { maxScoreSetting = null; } return maxScoreSetting; } private void initCourseNodeInformation(StatisticAssessment stats) { mainVC.contextPut("numOfParticipants", stats.getNumOfParticipants()); mainVC.contextPut("type", resourceResult.getType()); mainVC.contextPut("courseId", courseResourceID); mainVC.contextPut("testId", repoEntryId); if(QTIType.test.equals(type)) { mainVC.contextPut("numOfPassed", stats.getNumOfPassed()); mainVC.contextPut("numOfFailed", stats.getNumOfFailed()); if (cutValue != null) { mainVC.contextPut("cutScore", format(cutValue)); } else { mainVC.contextPut("cutScore", "-"); } mainVC.contextPut("maxScore", format(maxScore)); mainVC.contextPut("average", format(stats.getAverage())); mainVC.contextPut("range", format(stats.getRange())); mainVC.contextPut("standardDeviation", format(stats.getStandardDeviation())); mainVC.contextPut("mode", getModeString(stats.getMode())); mainVC.contextPut("median", format(stats.getMedian())); } String duration = duration(stats.getAverageDuration()); mainVC.contextPut("averageDuration", duration); } private void initDurationHistogram(StatisticAssessment stats) { if(!BarSeries.hasNotNullDatas(stats.getDurations())) return; VelocityContainer durationHistogramVC = createVelocityContainer("histogram_duration"); durationHistogramVC.contextPut("datas", BarSeries.datasToString(stats.getDurations())); mainVC.put("durationHistogram", durationHistogramVC); } private void initScoreStatisticPerItem(List<Item> items, double numOfParticipants) { BarSeries d1 = new BarSeries(); BarSeries d2 = new BarSeries(); List<StatisticItem> statisticItems = qtiStatisticsManager .getStatisticPerItem(items, resourceResult.getSearchParams(), numOfParticipants); int i = 0; List<ItemInfos> itemInfos = new ArrayList<>(items.size()); for (StatisticItem statisticItem: statisticItems) { Item item = statisticItem.getItem(); String label = Integer.toString(++i); String text = item.getTitle(); d1.add(statisticItem.getAverageScore(), label); double numOfRightAnswers = statisticItem.getNumOfCorrectAnswers(); double res = numOfRightAnswers; d2.add(res, label); itemInfos.add(new ItemInfos(label, text)); } mainVC.contextPut("itemInfoList", itemInfos); VelocityContainer averageScorePeritemVC = createVelocityContainer("hbar_average_score_per_item"); Stringuified data1 = BarSeries.getDatasAndColors(Collections.singletonList(d1), "bar_default"); averageScorePeritemVC.contextPut("datas", data1); mainVC.put("averageScorePerItemChart", averageScorePeritemVC); VelocityContainer percentRightAnswersPerItemVC = createVelocityContainer("hbar_right_answer_per_item"); Stringuified data2 = BarSeries.getDatasAndColors(Collections.singletonList(d2), "bar_green"); percentRightAnswersPerItemVC.contextPut("datas", data2); percentRightAnswersPerItemVC.contextPut("numOfParticipants", Long.toString(Math.round(numOfParticipants))); mainVC.put("percentRightAnswersPerItemChart", percentRightAnswersPerItemVC); } private void initScoreHistogram(StatisticAssessment stats) { VelocityContainer scoreHistogramVC = createVelocityContainer("histogram_score"); scoreHistogramVC.contextPut("datas", BarSeries.datasToString(stats.getScores())); scoreHistogramVC.contextPut("cutValue", cutValue); mainVC.put("scoreHistogram", scoreHistogramVC); } private void initItemsOverview(List<Item> items) { List<StatisticSurveyItem> surveyItems = qtiStatisticsManager .getStatisticAnswerOptions(resourceResult.getSearchParams(), items); int count = 0; List<String> overviewList = new ArrayList<>(); for(StatisticSurveyItem surveyItem:surveyItems) { Item item = surveyItem.getItem(); Series series = seriesfactory.getSeries(item, null); if(series != null) {//essay hasn't a series String name = "overview_" + count++; VelocityContainer vc = createVelocityContainer(name, "hbar_item_overview"); vc.contextPut("series", series); vc.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); vc.contextPut("questionType", item.getQuestion().getType()); vc.contextPut("title", item.getTitle()); mainVC.put(vc.getDispatchID(), vc); overviewList.add(vc.getDispatchID()); } } mainVC.contextPut("overviewList", overviewList); } @Override protected void event(UserRequest ureq, Component source, Event event) { if(printLink == source) { printPages(ureq); } else if(downloadRawLink == source) { doDownloadRawData(ureq); } } private void doDownloadRawData(UserRequest ureq) { MediaResource resource = new QTIStatisticsResource(resourceResult, getLocale()); ureq.getDispatchResult().setResultingMediaResource(resource); } private void printPages(UserRequest ureq) { ControllerCreator printControllerCreator = new ControllerCreator() { public Controller createController(UserRequest lureq, WindowControl lwControl) { return new QTI12PrintController(lureq, lwControl, resourceResult); } }; ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createPrintPopupLayout(printControllerCreator); openInNewBrowserWindow(ureq, layoutCtrlr); } public static class ItemInfos { private final String label; private final String text; public ItemInfos(String label, String text) { this.label = label; this.text = text; } public String getLabel() { return label; } public String getText() { return text; } } }