Skip to content

Commit

Permalink
PLANNER-631 Indictment visualization in course scheduling, exam, nurs…
Browse files Browse the repository at this point in the history
…e rostering and TTP
  • Loading branch information
ge0ffrey committed Mar 14, 2017
1 parent 1e3fffa commit bed1140
Show file tree
Hide file tree
Showing 15 changed files with 502 additions and 33 deletions.
Expand Up @@ -26,7 +26,6 @@
import java.util.Map;
import java.util.function.Supplier;

import com.google.common.collect.Lists;
import org.drools.core.common.AgendaItem;
import org.kie.api.definition.rule.Rule;
import org.kie.api.runtime.rule.Match;
Expand Down Expand Up @@ -129,11 +128,8 @@ private ConstraintMatchTotal findConstraintMatchTotal(RuleContext kcontext) {
}

protected List<Object> extractJustificationList(RuleContext kcontext) {
// Unlike kcontext.getMatch().getObjects(), this includes those of accumulate
List<Object> droolsMatchObjects = ((org.drools.core.spi.Activation) kcontext.getMatch()).getObjectsDeep();
// Drools always returns the rule matches in reverse order
// Return a reversed view on the list for performance
return Lists.reverse(droolsMatchObjects);
// Unlike kcontext.getMatch().getObjects(), this includes the matches of accumulate and exists
return ((org.drools.core.spi.Activation) kcontext.getMatch()).getObjectsDeep();
}

private class ConstraintActivationUnMatchListener implements ActivationUnMatchListener {
Expand Down
Expand Up @@ -123,7 +123,7 @@ public String buildConstraintMatchSetText(ConstraintMatchTotal constraintMatchTo
Set<? extends ConstraintMatch> constraintMatchSet = constraintMatchTotal.getConstraintMatchSet();
StringBuilder text = new StringBuilder(constraintMatchSet.size() * 80);
for (ConstraintMatch constraintMatch : constraintMatchSet) {
text.append(constraintMatch.getJustificationList()).append("=")
text.append(constraintMatch.getJustificationList()).append(" = ")
.append(constraintMatch.getScore().toShortString()).append("\n");
}
return text.toString();
Expand Down
Expand Up @@ -16,16 +16,21 @@

package org.optaplanner.examples.common.swingui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.Scrollable;

import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.constraint.ConstraintMatch;
import org.optaplanner.core.api.score.constraint.Indictment;
import org.optaplanner.core.impl.solver.ProblemFactChange;
import org.optaplanner.examples.common.business.SolutionBusiness;
import org.optaplanner.swing.impl.TangoColorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -39,11 +44,24 @@ public abstract class SolutionPanel<Solution_> extends JPanel implements Scrolla
// Size fits into screen resolution 1024*768
public static final Dimension PREFERRED_SCROLLABLE_VIEWPORT_SIZE = new Dimension(800, 600);

protected static final Color[][] INDICTMENT_COLORS = {
{TangoColorFactory.SCARLET_3, TangoColorFactory.SCARLET_1},
{TangoColorFactory.ORANGE_3, TangoColorFactory.ORANGE_1},
{TangoColorFactory.BUTTER_3, TangoColorFactory.BUTTER_1},
{TangoColorFactory.CHAMELEON_3, TangoColorFactory.CHAMELEON_1},
{TangoColorFactory.SKY_BLUE_3, TangoColorFactory.SKY_BLUE_1},
{TangoColorFactory.PLUM_3, TangoColorFactory.PLUM_1}
};

protected final transient Logger logger = LoggerFactory.getLogger(getClass());

protected SolverAndPersistenceFrame solverAndPersistenceFrame;
protected SolutionBusiness<Solution_> solutionBusiness;

protected boolean useIndictmentColor = false;
protected TangoColorFactory normalColorFactory;
protected double[] indictmentMinimumLevelNumbers;

public SolverAndPersistenceFrame getSolverAndPersistenceFrame() {
return solverAndPersistenceFrame;
}
Expand All @@ -60,6 +78,14 @@ public void setSolutionBusiness(SolutionBusiness<Solution_> solutionBusiness) {
this.solutionBusiness = solutionBusiness;
}

public boolean isUseIndictmentColor() {
return useIndictmentColor;
}

public void setUseIndictmentColor(boolean useIndictmentColor) {
this.useIndictmentColor = useIndictmentColor;
}

public String getUsageExplanationPath() {
return USAGE_EXPLANATION_PATH;
}
Expand Down Expand Up @@ -109,6 +135,74 @@ public boolean getScrollableTracksViewportHeight() {
return false;
}

public boolean isIndictmentHeatMapEnabled() {
return false;
}

protected void preparePlanningEntityColors(List<?> planningEntityList) {
if (useIndictmentColor) {
indictmentMinimumLevelNumbers = null;
for (Object planningEntity : planningEntityList) {
Indictment indictment = solutionBusiness.getIndictmentMap().get(planningEntity);
if (indictment != null) {
Number[] levelNumbers = indictment.getScoreTotal().toLevelNumbers();
if (indictmentMinimumLevelNumbers == null) {
indictmentMinimumLevelNumbers = new double[levelNumbers.length];
for (int i = 0; i < levelNumbers.length; i++) {
indictmentMinimumLevelNumbers[i] = levelNumbers[i].doubleValue();
}
} else {
for (int i = 0; i < levelNumbers.length; i++) {
double levelNumber = levelNumbers[i].doubleValue();
if (levelNumber < indictmentMinimumLevelNumbers[i]) {
indictmentMinimumLevelNumbers[i] = levelNumber;
}
}
}
}
}
} else {
normalColorFactory = new TangoColorFactory();
}
}

public Color determinePlanningEntityColor(Object planningEntity, Object normalColorObject) {
if (useIndictmentColor) {
Indictment indictment = solutionBusiness.getIndictmentMap().get(planningEntity);
if (indictment != null) {
Number[] levelNumbers = indictment.getScoreTotal().toLevelNumbers();
for (int i = 0; i < levelNumbers.length; i++) {
if (i > INDICTMENT_COLORS.length) {
return TangoColorFactory.ALUMINIUM_3;
}
double levelNumber = levelNumbers[i].doubleValue();
if (levelNumber < 0.0) {
return TangoColorFactory.buildPercentageColor(
INDICTMENT_COLORS[i][0], INDICTMENT_COLORS[i][1],
1.0 - (levelNumber / indictmentMinimumLevelNumbers[i]));
}
}
}
return Color.WHITE;
} else {
return normalColorFactory.pickColor(normalColorObject);
}
}

public String determinePlanningEntityTooltip(Object planningEntity) {
Indictment indictment = solutionBusiness.getIndictmentMap().get(planningEntity);
if (indictment == null) {
return "No indictment.";
}
StringBuilder s = new StringBuilder("<html>Indictment: ").append(indictment.getScoreTotal().toShortString());
for (ConstraintMatch constraintMatch : indictment.getConstraintMatchSet()) {
s.append("<br/>&nbsp;&nbsp;").append(constraintMatch.getConstraintName())
.append(" = ").append(constraintMatch.getScore().toShortString());
}
s.append("<html>");
return s.toString();
}

public void doProblemFactChange(ProblemFactChange<Solution_> problemFactChange) {
doProblemFactChange(problemFactChange, false);
}
Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
Expand Down Expand Up @@ -73,6 +74,8 @@ public class SolverAndPersistenceFrame<Solution_> extends JFrame {
SolverAndPersistenceFrame.class.getResource("optaPlannerIcon.png"));

private final SolutionBusiness<Solution_> solutionBusiness;
private final ImageIcon indictmentHeatMapTrueIcon;
private final ImageIcon indictmentHeatMapFalseIcon;
private final ImageIcon refreshScreenDuringSolvingTrueIcon;
private final ImageIcon refreshScreenDuringSolvingFalseIcon;

Expand All @@ -88,6 +91,7 @@ public class SolverAndPersistenceFrame<Solution_> extends JFrame {
private Action importAction;
private Action exportAction;
private JToggleButton refreshScreenDuringSolvingToggleButton;
private JToggleButton indictmentHeatMapToggleButton;
private Action solveAction;
private JButton solveButton;
private Action terminateSolvingEarlyAction;
Expand All @@ -105,6 +109,8 @@ public SolverAndPersistenceFrame(SolutionBusiness<Solution_> solutionBusiness,
setIconImage(OPTA_PLANNER_ICON.getImage());
solutionPanel.setSolutionBusiness(solutionBusiness);
solutionPanel.setSolverAndPersistenceFrame(this);
indictmentHeatMapTrueIcon = new ImageIcon(getClass().getResource("indictmentHeatMapTrueIcon.png"));
indictmentHeatMapFalseIcon = new ImageIcon(getClass().getResource("indictmentHeatMapFalseIcon.png"));
refreshScreenDuringSolvingTrueIcon = new ImageIcon(getClass().getResource("refreshScreenDuringSolvingTrueIcon.png"));
refreshScreenDuringSolvingFalseIcon = new ImageIcon(getClass().getResource("refreshScreenDuringSolvingFalseIcon.png"));
registerListeners();
Expand Down Expand Up @@ -573,10 +579,24 @@ private JPanel createMiddlePanel() {

private JPanel createScorePanel() {
JPanel scorePanel = new JPanel(new BorderLayout(5, 0));
scorePanel.setBorder(BorderFactory.createEtchedBorder());
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
showConstraintMatchesDialogAction = new ShowConstraintMatchesDialogAction();
showConstraintMatchesDialogAction.setEnabled(false);
scorePanel.add(new JButton(showConstraintMatchesDialogAction), BorderLayout.WEST);
buttonPanel.add(new JButton(showConstraintMatchesDialogAction));
indictmentHeatMapToggleButton = new JToggleButton(
solutionPanel.isUseIndictmentColor() ? indictmentHeatMapTrueIcon : indictmentHeatMapFalseIcon,
solutionPanel.isUseIndictmentColor());
indictmentHeatMapToggleButton.setEnabled(false);
indictmentHeatMapToggleButton.setToolTipText("Show indictment heat map");
indictmentHeatMapToggleButton.addActionListener(e -> {
boolean selected = indictmentHeatMapToggleButton.isSelected();
indictmentHeatMapToggleButton.setIcon(selected ?
indictmentHeatMapTrueIcon : indictmentHeatMapFalseIcon);
solutionPanel.setUseIndictmentColor(selected);
resetScreen();
});
buttonPanel.add(indictmentHeatMapToggleButton);
scorePanel.add(buttonPanel, BorderLayout.WEST);
scoreField = new JTextField("Score:");
scoreField.setEditable(false);
scoreField.setForeground(Color.BLACK);
Expand Down Expand Up @@ -637,6 +657,7 @@ private void setSolvingState(boolean solving) {
progressBar.setIndeterminate(solving);
progressBar.setStringPainted(solving);
progressBar.setString(solving ? "Solving..." : null);
indictmentHeatMapToggleButton.setEnabled(solutionPanel.isIndictmentHeatMapEnabled() && !solving);
showConstraintMatchesDialogAction.setEnabled(!solving);
}

Expand Down
Expand Up @@ -20,7 +20,12 @@
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
Expand All @@ -33,6 +38,9 @@
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;

import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.constraint.Indictment;
import org.optaplanner.examples.common.swingui.CommonIcons;
import org.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.common.swingui.components.LabeledComboBoxRenderer;
Expand Down Expand Up @@ -187,16 +195,17 @@ private void fillDayCells(CourseSchedule courseSchedule) {
}

private void fillLectureCells(CourseSchedule courseSchedule) {
TangoColorFactory tangoColorFactory = new TangoColorFactory();
preparePlanningEntityColors(courseSchedule.getLectureList());
for (Lecture lecture : courseSchedule.getLectureList()) {
Color lectureColor = tangoColorFactory.pickColor(lecture.getCourse());
Color color = determinePlanningEntityColor(lecture, lecture.getCourse());
String toolTip = determinePlanningEntityTooltip(lecture);
roomsPanel.addCell(lecture.getRoom(), lecture.getPeriod(),
createButton(lecture, lectureColor));
createButton(lecture, color, toolTip));
teachersPanel.addCell(lecture.getTeacher(), lecture.getPeriod(),
createButton(lecture, lectureColor));
createButton(lecture, color, toolTip));
for (Curriculum curriculum : lecture.getCurriculumList()) {
curriculaPanel.addCell(curriculum, lecture.getPeriod(),
createButton(lecture, lectureColor));
createButton(lecture, color, toolTip));
}
}
}
Expand All @@ -210,15 +219,21 @@ private JPanel createTableHeader(JLabel label) {
return headerPanel;
}

private JButton createButton(Lecture lecture, Color color) {
private JButton createButton(Lecture lecture, Color color, String toolTip) {
JButton button = SwingUtils.makeSmallButton(new JButton(new LectureAction(lecture)));
button.setBackground(color);
if (lecture.isLocked()) {
button.setIcon(CommonIcons.LOCKED_ICON);
}
button.setToolTipText(toolTip);
return button;
}

@Override
public boolean isIndictmentHeatMapEnabled() {
return true;
}

private class LectureAction extends AbstractAction {

private Lecture lecture;
Expand Down
Expand Up @@ -179,17 +179,18 @@ private void fillPeriodCells(Examination examination) {
}

private void fillExamCells(Examination examination) {
TangoColorFactory tangoColorFactory = new TangoColorFactory();
preparePlanningEntityColors(examination.getExamList());
for (Exam exam : examination.getExamList()) {
Color examColor = tangoColorFactory.pickColor(exam);
Color color = determinePlanningEntityColor(exam, exam);
String toolTip = determinePlanningEntityTooltip(exam);
roomsPanel.addCell(exam.getRoom(), exam.getPeriod(),
createButton(exam, examColor));
createButton(exam, color, toolTip));
}
}

private JPanel createTableHeader(JLabel label, String toolTipText) {
if (toolTipText != null) {
label.setToolTipText(toolTipText);
private JPanel createTableHeader(JLabel label, String toolTip) {
if (toolTip != null) {
label.setToolTipText(toolTip);
}
JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.add(label, BorderLayout.NORTH);
Expand All @@ -199,15 +200,21 @@ private JPanel createTableHeader(JLabel label, String toolTipText) {
return headerPanel;
}

private JButton createButton(Exam exam, Color color) {
private JButton createButton(Exam exam, Color color, String toolTip) {
JButton button = SwingUtils.makeSmallButton(new JButton(new ExamAction(exam)));
button.setBackground(color);
button.setToolTipText(toolTip);
if (exam instanceof FollowingExam) {
button.setForeground(TangoColorFactory.ALUMINIUM_5);
}
return button;
}

@Override
public boolean isIndictmentHeatMapEnabled() {
return true;
}

private class ExamAction extends AbstractAction {

private Exam exam;
Expand Down
Expand Up @@ -279,9 +279,9 @@ public void stateChanged(ChangeEvent e) {
}
}

private JPanel createTableHeader(JLabel label, String toolTipText) {
if (toolTipText != null) {
label.setToolTipText(toolTipText);
private JPanel createTableHeader(JLabel label, String toolTip) {
if (toolTip != null) {
label.setToolTipText(toolTip);
}
JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.add(label, BorderLayout.NORTH);
Expand Down
Expand Up @@ -71,12 +71,12 @@ public void resetPanel(NQueens nQueens) {
if (queen.getColumn().getIndex() != column) {
throw new IllegalStateException("The queenList is not in the expected order.");
}
String toolTipText = "<html>Row " + row + "<br/>Column " + column + "</html>";
String toolTip = "<html>Row " + row + "<br/>Column " + column + "</html>";
if (queen.getRow() != null && queen.getRow().getIndex() == row) {
JButton button = new JButton(new QueenAction(queen));
button.setMinimumSize(new Dimension(20, 20));
button.setPreferredSize(new Dimension(20, 20));
button.setToolTipText(toolTipText);
button.setToolTipText(toolTip);
add(button);
} else {
JPanel panel = new JPanel();
Expand All @@ -85,7 +85,7 @@ public void resetPanel(NQueens nQueens) {
BorderFactory.createEmptyBorder(5, 5, 5, 5)));
Color background = (((row + column) % 2) == 0) ? Color.WHITE : TangoColorFactory.ALUMINIUM_3;
panel.setBackground(background);
panel.setToolTipText(toolTipText);
panel.setToolTipText(toolTip);
add(panel);
}
}
Expand Down

0 comments on commit bed1140

Please sign in to comment.