package aima.gui.fx.views;
import aima.core.search.csp.Assignment;
import aima.core.search.csp.CSP;
import aima.core.search.csp.Constraint;
import aima.core.search.csp.Variable;
import aima.core.util.math.geom.shapes.Point2D;
import javafx.geometry.VPos;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import java.util.Hashtable;
import java.util.List;
/**
* Controller class which provides functionality to visualize a binary CSP state in a pane.
*
* @author Ruediger Lunde
*/
public class CspViewCtrl<VAR extends Variable, VAL> {
protected Pane pane;
protected CSP<VAR, VAL> csp;
protected Assignment<VAR, VAL> assignment;
/**
* Maintains logical 2D-coordinates for the variables of the CSP.
*/
protected Hashtable<VAR, int[]> positionMapping = new Hashtable<>();
/**
* Maps domain values to colors.
*/
protected Hashtable<VAL, Color> colorMapping = new Hashtable<>();
public CspViewCtrl(StackPane viewRoot) {
pane = new Pane();
viewRoot.getChildren().add(pane);
viewRoot.setStyle("-fx-background-color: white");
pane.setMinWidth(0.0);
pane.widthProperty().addListener((obs, o, n) -> update());
pane.heightProperty().addListener((obs, o, n) -> update());
}
public void clearMappings() {
positionMapping.clear();
colorMapping.clear();
}
/**
* Defines a logical 2D-position for a variable. If no position is given for
* a certain variable, the viewer selects a position on a grid.
*/
public void setPositionMapping(VAR var, int x, int y) {
positionMapping.put(var, new int[]{x, y});
}
/**
* Defines a color for a domain value. It is used to visualize the current
* assignment. If no color mapping is found, the node coloring feature is
* disabled for the corresponding variable.
*
* @param value
* @param color
*/
public void setColorMapping(VAL value, Color color) {
colorMapping.put(value, color);
}
public void initialize(CSP<VAR, VAL> csp) {
this.csp = csp;
this.assignment = new Assignment<>();
update();
}
public void update(CSP<VAR, VAL> csp, Assignment<VAR, VAL> assignment) {
if (csp != null)
this.csp = csp;
if (assignment != null)
this.assignment = assignment;
update();
}
protected void update() {
adjustTransform();
pane.getChildren().clear();
if (csp != null) {
csp.getConstraints().forEach(this::visualize);
csp.getVariables().forEach(this::visualize);
}
}
protected void visualize(VAR var) {
Point2D pos = getPosition(var);
String label = var.getName();
VAL value = null;
Color fillColor = null;
if (assignment != null)
value = assignment.getValue(var);
if (value != null) {
label += " = " + value;
fillColor = colorMapping.get(value);
}
Circle circle = new Circle(pos.getX(), pos.getY(), 20);
circle.setStroke(Color.BLACK);
circle.setFill(fillColor != null ? fillColor : Color.WHITE);
Text t1 = new Text(pos.getX() + 25, pos.getY(), label);
t1.setTextOrigin(VPos.CENTER);
Text t2 = new Text(pos.getX(), pos.getY() + 40, csp.getDomain(var).toString());
pane.getChildren().addAll(circle, t1, t2);
}
protected void visualize(Constraint<VAR, VAL> constraint) {
List<VAR> scope = constraint.getScope();
if (scope.size() == 2) { // we show only binary constraints...
Point2D pos0 = getPosition(scope.get(0));
Point2D pos1 = getPosition(scope.get(1));
Line line = new Line(pos0.getX(), pos0.getY(), pos1.getX(), pos1.getY());
pane.getChildren().add(line);
//g2.drawLine(pos0[0] + 20, pos0[1] + 20, pos1[0] + 20, pos1[1] + 20);
}
}
/**
* Computes transforms (translations and scaling) and applies them to the environment state view. Those transforms
* map logical positions to screen positions in the viewer pane. The purpose is to show the graphical state
* representation as large as possible.
*
* @return The scale value.
*/
private double adjustTransform() {
double xMin = Double.POSITIVE_INFINITY;
double xMax = Double.NEGATIVE_INFINITY;
double yMin = Double.POSITIVE_INFINITY;
double yMax = Double.NEGATIVE_INFINITY;
if (csp != null)
for (VAR var : csp.getVariables()) {
Point2D point = getPosition(var);
xMin = Math.min(xMin, point.getX());
xMax = Math.max(xMax, point.getX());
yMin = Math.min(yMin, point.getY());
yMax = Math.max(yMax, point.getY());
}
double scale = Math.min(pane.getWidth() / (xMax - xMin + 300),
pane.getHeight() / (yMax - yMin + 150));
pane.setTranslateX((scale * (pane.getWidth() - xMin - xMax) / 2.0));
pane.setTranslateY((scale * (pane.getHeight() - yMin - yMax) / 2.0));
pane.setScaleX(scale);
pane.setScaleY(scale);
return scale;
}
/**
* Provides a 2D-position for each variable. If no mapping is given, a simple grid position is computed.
*/
protected Point2D getPosition(VAR var) {
int[] pos = positionMapping.get(var);
if (pos != null)
return new Point2D(pos[0], pos[1]);
else {
int vIndex = csp.indexOf(var);
int rows = Math.max((int) (pane.getHeight() / 100), 1);
int x = (vIndex / rows) * 160 + 40;
int y = (vIndex % rows) * 100 + 40;
return new Point2D(x, y);
}
}
}