From c4ec8710614cfb5aff1d2a827ecf6084ea75bf55 Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Mon, 8 Jul 2019 14:19:38 +0200 Subject: [PATCH] OO-4130: Average of each slider in heat map --- .../quality/analysis/GroupedStatistic.java | 8 +-- .../quality/analysis/HeatMapStatistic.java | 38 ++++++++++ .../analysis/QualityAnalysisService.java | 2 + .../manager/QualityAnalysisServiceImpl.java | 6 ++ .../manager/StatisticsCalculator.java | 26 +++++++ .../analysis/model/HeatMapStatisticImpl.java | 59 ++++++++++++++++ .../analysis/ui/FooterGroupByDataModel.java | 69 +++++++++++++++++++ .../analysis/ui/GroupByController.java | 38 +++++++--- .../quality/analysis/ui/GroupByDataModel.java | 15 ++-- .../analysis/ui/HeatMapController.java | 47 ++++++++++++- .../quality/analysis/ui/HeatMapRenderer.java | 32 ++++++--- .../quality/analysis/ui/TrendController.java | 22 ++++++ .../ui/_i18n/LocalStrings_de.properties | 1 + .../ui/_i18n/LocalStrings_en.properties | 3 +- .../manager/StatisticsCalculatorTest.java | 23 +++++++ 15 files changed, 354 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/olat/modules/quality/analysis/HeatMapStatistic.java create mode 100644 src/main/java/org/olat/modules/quality/analysis/model/HeatMapStatisticImpl.java create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/FooterGroupByDataModel.java diff --git a/src/main/java/org/olat/modules/quality/analysis/GroupedStatistic.java b/src/main/java/org/olat/modules/quality/analysis/GroupedStatistic.java index 3199b2e5cb1..cdab2f897a8 100644 --- a/src/main/java/org/olat/modules/quality/analysis/GroupedStatistic.java +++ b/src/main/java/org/olat/modules/quality/analysis/GroupedStatistic.java @@ -19,22 +19,16 @@ */ package org.olat.modules.quality.analysis; -import org.olat.modules.forms.RubricRating; - /** * * Initial date: 11.09.2018<br> * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com * */ -public interface GroupedStatistic extends RawGroupedStatistic { +public interface GroupedStatistic extends RawGroupedStatistic, HeatMapStatistic { public int getSteps(); public boolean isRawAvgMaxGood(); - - public Double getAvg(); - - public RubricRating getRating(); } diff --git a/src/main/java/org/olat/modules/quality/analysis/HeatMapStatistic.java b/src/main/java/org/olat/modules/quality/analysis/HeatMapStatistic.java new file mode 100644 index 00000000000..9ed67ac3465 --- /dev/null +++ b/src/main/java/org/olat/modules/quality/analysis/HeatMapStatistic.java @@ -0,0 +1,38 @@ +/** + * <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.modules.quality.analysis; + +import org.olat.modules.forms.RubricRating; + +/** + * + * Initial date: 8 Jul 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public interface HeatMapStatistic { + + public Long getCount(); + + public Double getAvg(); + + public RubricRating getRating(); + +} diff --git a/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java b/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java index 2a7b5b5b8ff..47481ac66c0 100644 --- a/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java +++ b/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java @@ -112,6 +112,8 @@ public interface QualityAnalysisService { */ public MultiTrendSeries<MultiKey> calculateTrends(AnalysisSearchParameter searchParams, Set<Rubric> rubrics, MultiGroupBy groupBy, TemporalGroupBy temporalGroupBy); + + public HeatMapStatistic calculateTotal(List<HeatMapStatistic> statistics, Rubric rubric); public boolean isInsufficient(Rubric rubric, Double avg); } diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java b/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java index 317f8846be8..06540c9222d 100644 --- a/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java +++ b/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java @@ -55,6 +55,7 @@ import org.olat.modules.quality.analysis.EvaluationFormView; import org.olat.modules.quality.analysis.EvaluationFormViewSearchParams; import org.olat.modules.quality.analysis.GroupedStatistic; import org.olat.modules.quality.analysis.GroupedStatistics; +import org.olat.modules.quality.analysis.HeatMapStatistic; import org.olat.modules.quality.analysis.MultiGroupBy; import org.olat.modules.quality.analysis.MultiKey; import org.olat.modules.quality.analysis.MultiTrendSeries; @@ -342,6 +343,11 @@ public class QualityAnalysisServiceImpl implements QualityAnalysisService { .isPresent(); } + @Override + public HeatMapStatistic calculateTotal(List<HeatMapStatistic> statistics, Rubric rubric) { + return statisticsCalculator.calculateTotal(statistics, rubric); + } + @Override public boolean isInsufficient(Rubric rubric, Double avg) { RubricRating rating = evaluationFormManager.getRubricRating(rubric, avg); diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java b/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java index e5ee93d0f0a..e748e277d47 100644 --- a/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java +++ b/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java @@ -39,6 +39,7 @@ import org.olat.modules.forms.model.xml.Slider; import org.olat.modules.quality.analysis.GroupedStatistic; import org.olat.modules.quality.analysis.GroupedStatisticKeys; import org.olat.modules.quality.analysis.GroupedStatistics; +import org.olat.modules.quality.analysis.HeatMapStatistic; import org.olat.modules.quality.analysis.MultiKey; import org.olat.modules.quality.analysis.MultiTrendSeries; import org.olat.modules.quality.analysis.RawGroupedStatistic; @@ -47,6 +48,7 @@ import org.olat.modules.quality.analysis.TemporalKey; import org.olat.modules.quality.analysis.Trend; import org.olat.modules.quality.analysis.Trend.DIRECTION; import org.olat.modules.quality.analysis.model.GroupedStatisticImpl; +import org.olat.modules.quality.analysis.model.HeatMapStatisticImpl; import org.olat.modules.quality.analysis.model.RawGroupedStatisticImpl; import org.olat.modules.quality.analysis.model.TrendImpl; import org.springframework.beans.factory.annotation.Autowired; @@ -114,6 +116,30 @@ public class StatisticsCalculator { return statistic; } + HeatMapStatistic calculateTotal(List<HeatMapStatistic> statistics, Rubric rubric) { + HeatMapStatistic total; + long count = 0; + double sumValues = 0; + for (HeatMapStatistic statistic : statistics) { + if (statistic != null) { + Long statisticCount = statistic.getCount(); + if (statisticCount != null) { + count += statisticCount.longValue(); + sumValues += statisticCount.longValue() * statistic.getAvg().doubleValue(); + } + } + } + + if (count == 0) { + total = new HeatMapStatisticImpl(null, null, null); + } else { + double avg = sumValues / count; + RubricRating rating = evaluationFormManager.getRubricRating(rubric, avg); + total = new HeatMapStatisticImpl(count, avg, rating); + } + return total; + } + List<RawGroupedStatistic> reduceIdentifier(List<RawGroupedStatistic> statisticsList, Set<Rubric> rubrics) { Map<String, Integer> sliderToWeight = rubrics.stream() .map(Rubric::getSliders) diff --git a/src/main/java/org/olat/modules/quality/analysis/model/HeatMapStatisticImpl.java b/src/main/java/org/olat/modules/quality/analysis/model/HeatMapStatisticImpl.java new file mode 100644 index 00000000000..9a50b309441 --- /dev/null +++ b/src/main/java/org/olat/modules/quality/analysis/model/HeatMapStatisticImpl.java @@ -0,0 +1,59 @@ +/** + * <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.modules.quality.analysis.model; + +import org.olat.modules.forms.RubricRating; +import org.olat.modules.quality.analysis.HeatMapStatistic; + +/** + * + * Initial date: 8 Jul 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class HeatMapStatisticImpl implements HeatMapStatistic { + + private final Long count; + private final Double avg; + private final RubricRating rating; + + public HeatMapStatisticImpl(Long count, Double avg, RubricRating rating) { + this.count = count; + this.avg = avg; + this.rating = rating; + } + + @Override + public Long getCount() { + return count; + } + + @Override + public Double getAvg() { + return avg; + } + + @Override + public RubricRating getRating() { + return rating; + } + + +} diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/FooterGroupByDataModel.java b/src/main/java/org/olat/modules/quality/analysis/ui/FooterGroupByDataModel.java new file mode 100644 index 00000000000..e578b5c2464 --- /dev/null +++ b/src/main/java/org/olat/modules/quality/analysis/ui/FooterGroupByDataModel.java @@ -0,0 +1,69 @@ +/** + * <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.modules.quality.analysis.ui; + +import java.util.List; +import java.util.Locale; + +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableFooterModel; + +/** + * + * Initial date: 8 Jul 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +class FooterGroupByDataModel extends GroupByDataModel implements FlexiTableFooterModel { + + private final String footerHeader; + private List<?> footerDataValues; + + FooterGroupByDataModel(FlexiTableColumnModel columnsModel, Locale locale, String footerHeader) { + super(columnsModel, locale); + this.footerHeader = footerHeader; + } + + public void setObjects(List<GroupByRow> objects, List<?> footerDataValues) { + super.setObjects(objects); + this.footerDataValues = footerDataValues; + } + + @Override + public DefaultFlexiTableDataModel<GroupByRow> createCopyWithEmptyList() { + return new FooterGroupByDataModel(getTableColumnModel(), locale, footerHeader); + } + + @Override + public String getFooterHeader() { + return footerHeader; + } + + @Override + public Object getFooterValueAt(int col) { + if (footerDataValues != null && col >= GroupByController.DATA_OFFSET) { + int pos = col - GroupByController.DATA_OFFSET; + return footerDataValues.get(pos); + } + return null; + } + +} diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java b/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java index dc6906e57d4..7d2538f032a 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java +++ b/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java @@ -53,6 +53,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer; @@ -97,6 +98,8 @@ import org.springframework.beans.factory.annotation.Autowired; */ public abstract class GroupByController extends FormBasicController implements FilterableController { + public static final int DATA_OFFSET = 10; + private static final String CMD_GROUP_PREFIX = "CLICKED_"; private static final String CMD_TREND = "TREND"; private static final String[] INSUFFICIENT_KEYS = new String[] {"heatmap.insufficient.select"}; @@ -114,7 +117,6 @@ public abstract class GroupByController extends FormBasicController implements F private SingleSelection temporalGroupEl; private SingleSelection differenceEl; private SingleSelection rubricEl; - private GroupByDataModel dataModel; private FlexiTableElement tableEl; private FormLayoutContainer legendLayout; @@ -252,7 +254,15 @@ public abstract class GroupByController extends FormBasicController implements F protected abstract List<? extends GroupedStatistic> getGroupedStatistcList(MultiKey multiKey); protected abstract Set<MultiKey> getStatisticsMultiKeys(); - + + protected abstract boolean hasFooter(); + + protected abstract void initModel(FlexiTableColumnModel columnsModel); + + protected abstract FlexiTableDataModel<GroupByRow> getModel(); + + protected abstract void setModelOjects(List<GroupByRow> rows); + void setToolComponents(ToolComponents toolComponents) { this.toolComponents = toolComponents; toolComponents.setPrintVisibility(false); @@ -442,20 +452,21 @@ public abstract class GroupByController extends FormBasicController implements F } } - columnIndex = addDataColumns(columnsModel, columnIndex); + addDataColumns(columnsModel, DATA_OFFSET); DefaultFlexiColumnModel trendColumn = new DefaultFlexiColumnModel("heatmap.table.title.trend", columnIndex++, CMD_TREND, new StaticFlexiCellRenderer("", CMD_TREND, "o_icon o_icon-lg o_icon_qual_ana_trend", null)); trendColumn.setExportable(false); columnsModel.addFlexiColumnModel(trendColumn); - dataModel = new GroupByDataModel(columnsModel, getLocale()); + initModel(columnsModel); if (tableEl != null) flc.remove(tableEl); - tableEl = uifactory.addTableElement(getWindowControl(), "table", dataModel, getTranslator(), flc); + tableEl = uifactory.addTableElement(getWindowControl(), "table", getModel(), getTranslator(), flc); tableEl.setElementCssClass("o_qual_hm o_qual_trend"); tableEl.setEmtpyTableMessageKey("heatmap.empty"); tableEl.setNumOfRowsEnabled(false); tableEl.setCustomizeColumns(false); + tableEl.setFooter(hasFooter()); // legend if (legendLayout != null) flc.remove(legendLayout); @@ -498,7 +509,7 @@ public abstract class GroupByController extends FormBasicController implements F } else if (source == tableEl && event instanceof SelectionEvent) { SelectionEvent se = (SelectionEvent)event; String cmd = se.getCommand(); - GroupByRow row = dataModel.getObject(se.getIndex()); + GroupByRow row = getModel().getObject(se.getIndex()); if (CMD_TREND.equals(cmd)) { doShowTrend(ureq, row); } else if (cmd.indexOf(CMD_GROUP_PREFIX) > -1) { @@ -577,7 +588,10 @@ public abstract class GroupByController extends FormBasicController implements F updateTable(columnConfigs); rows.sort(new GroupNameAlphabeticalComparator()); - dataModel.setObjects(rows); + if (hasFooter()) { + + } + setModelOjects(rows); tableEl.reset(true, true, true); } @@ -656,9 +670,7 @@ public abstract class GroupByController extends FormBasicController implements F if (statistic != null) { Double avg = statistic.getAvg(); String identifier = statistic.getIdentifier(); - Rubric rubric = StringHelper.containsNonWhitespace(identifier) - ? getRubricByIdentifier(identifier) - : getSelectedRubric(); + Rubric rubric = getRubric(identifier); if (rubric != null) { boolean isInsufficient = analysisService.isInsufficient(rubric, avg); if (isInsufficient) { @@ -670,6 +682,12 @@ public abstract class GroupByController extends FormBasicController implements F return true; } + protected Rubric getRubric(String identifier) { + return StringHelper.containsNonWhitespace(identifier) + ? getRubricByIdentifier(identifier) + : getSelectedRubric(); + } + private Rubric getRubricByIdentifier(String identifier) { for (SliderWrapper sliderWrapper : getSliders()) { if (identifier.equals(sliderWrapper.getIdentifier())) { diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/GroupByDataModel.java b/src/main/java/org/olat/modules/quality/analysis/ui/GroupByDataModel.java index 7b0f9d8bcfe..5dd5c0271ee 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/GroupByDataModel.java +++ b/src/main/java/org/olat/modules/quality/analysis/ui/GroupByDataModel.java @@ -37,7 +37,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFl class GroupByDataModel extends DefaultFlexiTableDataModel<GroupByRow> implements SortableFlexiTableDataModel<GroupByRow> { - private final Locale locale; + protected final Locale locale; GroupByDataModel(FlexiTableColumnModel columnsModel, Locale locale) { super(columnsModel); @@ -58,13 +58,14 @@ class GroupByDataModel extends DefaultFlexiTableDataModel<GroupByRow> @Override public Object getValueAt(GroupByRow row, int col) { - int index = col; - if (index < row.getGroupNamesSize()) { - return row.getGroupName(index); + if (col >= GroupByController.DATA_OFFSET) { + int pos = col - GroupByController.DATA_OFFSET; + if (pos < row.getStatisticsSize()) { + return row.getStatistic(pos); + } } - index = col - row.getGroupNamesSize(); - if (index < row.getStatisticsSize()) { - return row.getStatistic(index); + if (col < row.getGroupNamesSize()) { + return row.getGroupName(col); } return null; } diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java b/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java index b2b164b2ac5..08f1377318c 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java +++ b/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java @@ -28,6 +28,7 @@ import java.util.Set; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModel; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.WindowControl; import org.olat.modules.forms.model.xml.Form; @@ -35,6 +36,7 @@ import org.olat.modules.forms.model.xml.Rubric; import org.olat.modules.quality.analysis.AvailableAttributes; import org.olat.modules.quality.analysis.GroupedStatistic; import org.olat.modules.quality.analysis.GroupedStatistics; +import org.olat.modules.quality.analysis.HeatMapStatistic; import org.olat.modules.quality.analysis.MultiGroupBy; import org.olat.modules.quality.analysis.MultiKey; import org.olat.modules.quality.analysis.QualityAnalysisService; @@ -48,7 +50,8 @@ import org.springframework.beans.factory.annotation.Autowired; * */ public class HeatMapController extends GroupByController { - + + private FooterGroupByDataModel dataModel; private GroupedStatistics<GroupedStatistic> statistics; private int maxCount; @@ -61,6 +64,7 @@ public class HeatMapController extends GroupByController { TrendDifference trendDifference, String rubricId) { super(ureq, wControl, stackPanel, filterCtrl, evaluationForm, availableAttributes, multiGroupBy, insufficientOnly, temporalGroupBy, trendDifference, rubricId); + } @Override @@ -97,8 +101,9 @@ public class HeatMapController extends GroupByController { protected int addDataColumns(FlexiTableColumnModel columnsModel, int columnIndex) { for (SliderWrapper sliderWrapper : getSliders()) { DefaultFlexiColumnModel columnModel = new DefaultFlexiColumnModel("", columnIndex++, - new HeatMapRenderer(maxCount)); + HeatMapRenderer.variableSize(maxCount)); columnModel.setHeaderLabel(sliderWrapper.getLabelCode()); + columnModel.setFooterCellRenderer(HeatMapRenderer.fixedSize()); columnsModel.addFlexiColumnModel(columnModel); } return columnIndex; @@ -119,4 +124,42 @@ public class HeatMapController extends GroupByController { protected Set<MultiKey> getStatisticsMultiKeys() { return statistics.getMultiKeys(); } + + @Override + protected boolean hasFooter() { + return true; + } + + @Override + protected void initModel(FlexiTableColumnModel columnsModel) { + dataModel = new FooterGroupByDataModel(columnsModel, getLocale(), translate("heatmap.footer.title")); + } + + @Override + protected FlexiTableDataModel<GroupByRow> getModel() { + return dataModel; + } + + @Override + protected void setModelOjects(List<GroupByRow> rows) { + List<HeatMapStatistic> footerStatistics = new ArrayList<>(); + if (!rows.isEmpty()) { + int statisticsSize = rows.get(0).getStatisticsSize(); + for (int i = 0; i < statisticsSize; i++) { + Rubric rubric = null; + ArrayList<HeatMapStatistic> columnStatistics = new ArrayList<>(rows.size()); + for (GroupByRow row : rows) { + GroupedStatistic columnStatistic = row.getStatistic(i); + columnStatistics.add(columnStatistic); + if (rubric == null && columnStatistic != null) { + rubric = getRubric(columnStatistic.getIdentifier()); + } + } + HeatMapStatistic total = analysisService.calculateTotal(columnStatistics, rubric); + footerStatistics.add(total); + } + } + dataModel.setObjects(rows, footerStatistics); + } + } diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapRenderer.java b/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapRenderer.java index d2feff31e6d..95f8f32c77f 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapRenderer.java +++ b/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapRenderer.java @@ -28,7 +28,7 @@ import org.olat.core.gui.translator.Translator; import org.olat.modules.forms.RubricRating; import org.olat.modules.forms.ui.EvaluationFormFormatter; import org.olat.modules.forms.ui.RubricAvgRenderer; -import org.olat.modules.quality.analysis.GroupedStatistic; +import org.olat.modules.quality.analysis.HeatMapStatistic; /** * @@ -39,17 +39,29 @@ import org.olat.modules.quality.analysis.GroupedStatistic; public class HeatMapRenderer implements FlexiCellRenderer { private static final int MAX_CIRCLE_SIZE = 18; - private int maxCount; + private final int maxCount; + private final boolean variableSize; + + public static HeatMapRenderer fixedSize() { + return new HeatMapRenderer(0, false); + } + + public static HeatMapRenderer variableSize(int maxCount) { + return new HeatMapRenderer(maxCount, true); + } - public HeatMapRenderer(int maxCount) { + private HeatMapRenderer(int maxCount, boolean variableSize) { this.maxCount = maxCount; + this.variableSize = variableSize; } @Override public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source, URLBuilder ubu, Translator translator) { - if (cellValue instanceof GroupedStatistic) { - GroupedStatistic statistic = (GroupedStatistic) cellValue; + if (cellValue instanceof HeatMapStatistic) { + HeatMapStatistic statistic = (HeatMapStatistic) cellValue; + if (statistic.getCount() == null) return; + target.append("<div class='o_circle_container'>"); target.append("<div class='o_circle_box' style='width:").append(MAX_CIRCLE_SIZE).append("px;'>"); target.append("<div class='o_circle "); @@ -67,7 +79,7 @@ public class HeatMapRenderer implements FlexiCellRenderer { } } - public String getColorCss(GroupedStatistic statistic) { + public String getColorCss(HeatMapStatistic statistic) { RubricRating rating = statistic.getRating(); String colorCss = RubricAvgRenderer.getRatingCssClass(rating); if (colorCss == null) { @@ -77,8 +89,12 @@ public class HeatMapRenderer implements FlexiCellRenderer { } private void appendSize(StringOutput target, Long count) { - // The circle areas (not the diameter) are proportional to the count value. - double size = MAX_CIRCLE_SIZE * Math.sqrt(count.doubleValue() / maxCount); + double size = MAX_CIRCLE_SIZE; + if (variableSize) { + double circleCount = count.doubleValue() <= maxCount? count.doubleValue(): maxCount; + // The circle areas (not the diameter) are proportional to the count value. + size = MAX_CIRCLE_SIZE * Math.sqrt(circleCount / maxCount); + } target.append(" style='width: ").append(size).append("px; height: ").append(size).append("px'"); } diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/TrendController.java b/src/main/java/org/olat/modules/quality/analysis/ui/TrendController.java index 26d55836570..43060bb6250 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/TrendController.java +++ b/src/main/java/org/olat/modules/quality/analysis/ui/TrendController.java @@ -26,6 +26,7 @@ import java.util.Set; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModel; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.WindowControl; import org.olat.modules.forms.model.xml.Form; @@ -47,6 +48,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class TrendController extends GroupByController { + private GroupByDataModel dataModel; private MultiTrendSeries<MultiKey> multiTrendSeries; @Autowired @@ -109,4 +111,24 @@ public class TrendController extends GroupByController { return multiTrendSeries.getIdentifiers(); } + @Override + protected boolean hasFooter() { + return false; + } + + @Override + protected void initModel(FlexiTableColumnModel columnsModel) { + dataModel = new GroupByDataModel(columnsModel, getLocale()); + } + + @Override + protected FlexiTableDataModel<GroupByRow> getModel() { + return dataModel; + } + + @Override + protected void setModelOjects(List<GroupByRow> rows) { + dataModel.setObjects(rows); + } + } diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties index eacab65d736..20ca96d9a79 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties @@ -62,6 +62,7 @@ heatmap.group2.label=Gruppierung 2 heatmap.group2= heatmap.group3.label=Gruppierung 3 heatmap.group3= +heatmap.footer.title=Durchschnitt heatmap.insufficient.label=Bewertung heatmap.insufficient.select=Nur ungen\u00FCgende heatmap.insufficient= diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties index 99a116f56ce..a7d79d282c9 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties @@ -56,12 +56,13 @@ heatmap.group.topic.curriculum=Topic curriculum heatmap.group.topic.identity=Topic coach heatmap.group.topic.organisation=Topic organisation heatmap.group.topic.repository=Topic course -heatmap.group1.label=Groupung 1 +heatmap.group1.label=Grouping 1 heatmap.group1= heatmap.group2.label=Grouping 2 heatmap.group2= heatmap.group3.label=Grouping 3 heatmap.group3= +heatmap.footer.title=Average heatmap.insufficient.label=Rating heatmap.insufficient.select=Only insufficient heatmap.insufficient= diff --git a/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java b/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java index afd55141c0e..5a9a83725e8 100644 --- a/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java +++ b/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java @@ -21,6 +21,8 @@ package org.olat.modules.quality.analysis.manager; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.offset; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; import static org.olat.test.JunitTestHelper.random; import java.util.ArrayList; @@ -35,11 +37,13 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.olat.modules.forms.EvaluationFormManager; +import org.olat.modules.forms.RubricRating; import org.olat.modules.forms.model.xml.Rubric; import org.olat.modules.forms.model.xml.ScaleType; import org.olat.modules.forms.model.xml.Slider; import org.olat.modules.quality.analysis.GroupedStatistic; import org.olat.modules.quality.analysis.GroupedStatistics; +import org.olat.modules.quality.analysis.HeatMapStatistic; import org.olat.modules.quality.analysis.MultiKey; import org.olat.modules.quality.analysis.MultiTrendSeries; import org.olat.modules.quality.analysis.RawGroupedStatistic; @@ -49,6 +53,7 @@ import org.olat.modules.quality.analysis.Trend; import org.olat.modules.quality.analysis.Trend.DIRECTION; import org.olat.modules.quality.analysis.TrendSeries; import org.olat.modules.quality.analysis.model.GroupedStatisticImpl; +import org.olat.modules.quality.analysis.model.HeatMapStatisticImpl; import org.olat.modules.quality.analysis.model.RawGroupedStatisticImpl; /** @@ -386,5 +391,23 @@ public class StatisticsCalculatorTest { softly.fail("No statistic for %s, %s", multiKey, temporalKey); } + @Test + public void shouldCalculateTotal() { + when(evaluationFormManagerMock.getRubricRating(any(), any())).thenReturn(RubricRating.NEUTRAL); + + Rubric rubric = new Rubric(); + List<HeatMapStatistic> statistic = new ArrayList<>(); + statistic.add(new HeatMapStatisticImpl(2l, 3.0, null)); + statistic.add(new HeatMapStatisticImpl(1l, 1.5, null)); + statistic.add(new HeatMapStatisticImpl(3l, 5.0, null)); + statistic.add(new HeatMapStatisticImpl(null, null, null)); + + HeatMapStatistic total = sut.calculateTotal(statistic, rubric); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(total.getCount()).isEqualTo(6); + softly.assertThat(total.getAvg()).isEqualTo(3.75, offset(0.001)); + softly.assertThat(total.getRating()).isEqualTo(RubricRating.NEUTRAL); + softly.assertAll(); + } } -- GitLab