JavaFX:在窗格上拖动节点时动态更新(重新计算+重绘)折线
JavaFX: Dynamically update (recalculate+redraw) Polyline when a node is dragged on a pane
我有一个 GUI,它在至少三个绘制样条曲线的节点之间进行插值。左键单击窗格会导致在单击的位置设置一个新的(可拖动的)节点。如果至少存在三个节点,则按下按钮 "Draw Spline" 将在节点之间绘制插值样条曲线。一切正常,除了 当其中一个节点被向上或向下拖动时,样条曲线不会动态重绘 。相反,在我移动一个节点后,我需要按下按钮 "Draw Spline",新的 Spline 就会出现。但我想看到节点立即移动位置的效果,而不是总是按下按钮。我在 NetBeans 8.1 中开发了这个 JavaFX 项目。
这是我的代码:
Main.java
package InterpolationMinimal;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
AnchorPane root = FXMLLoader.load(getClass().getResource("gui.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Interpolation using cubic splines");
stage.setScene(scene);
stage.show();
// this is needed to resize object if window/scene is resized
root.prefWidthProperty().bind(scene.widthProperty());
root.prefHeightProperty().bind(scene.heightProperty());
}
}
Controller.java
package InterpolationMinimal;
//import DraggableNodesGraph.MyAlerts;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Separator;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
public class Controller implements Initializable {
@FXML private AnchorPane anchorPane;
@FXML private Pane pane;
@FXML private Separator separator;
@FXML private ChoiceBox choseBoundaryConditions;
@FXML private ChoiceBox choseSolverTechnique;
private ObservableList boundaryConditionsToChose; //!< List of boundary condtions type
private ObservableList solvingTechniqueToChose; //!< List of techniques for solving a linear system of equations
public static Spline mySpline = new Spline();
@Override
public void initialize(URL url, ResourceBundle rb) {
// Set choices, default and event handling for the boundary conditions ChoiceBox
boundaryConditionsToChose = FXCollections.observableArrayList();
boundaryConditionsToChose.add("Natural");
boundaryConditionsToChose.add("Periodic");
choseBoundaryConditions.setItems(boundaryConditionsToChose);
choseBoundaryConditions.setValue(boundaryConditionsToChose.get(0));
// Set choices, default and event handling for the type of solving technique ChoiceBox
solvingTechniqueToChose = FXCollections.observableArrayList();
solvingTechniqueToChose.add("Jacobi");
solvingTechniqueToChose.add("Gauss-Seidel");
choseSolverTechnique.setItems(solvingTechniqueToChose);
choseSolverTechnique.setValue(solvingTechniqueToChose.get(0));
// Add listeners to the window size and redraw in case the window's size is changed.
// Subtract canvas.layout* to make sure the center of drawing is in the center
// of the canvas and not in the center of the whole window.
anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> {
pane.setPrefWidth(newValue.doubleValue() - pane.layoutXProperty().doubleValue());
rescaleObjects('x', oldValue.doubleValue(), newValue.doubleValue());
});
anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> {
pane.setPrefHeight(newValue.doubleValue() - pane.layoutYProperty().doubleValue());
separator.setPrefHeight(pane.getPrefHeight());
rescaleObjects('y', oldValue.doubleValue(), newValue.doubleValue());
});
MyMouseEvents.paneMouseEvents(pane);
}
@FXML public void handleButtonDrawSpline() {
if ( mySpline.getNodeList().size() >= 3 ) {
mySpline.calculate(String.valueOf(choseBoundaryConditions.getValue()),
String.valueOf(choseSolverTechnique.getValue()));
mySpline.draw(pane);
} /*else {
MyAlerts.displayAlert("You need at least 3 points for the interpolation!");
}*/
}
@FXML private void handleButtonClearCanvas(ActionEvent event) {
pane.getChildren().clear();
mySpline.getNodeList().clear();
}
private void rescaleObjects (char xOrY, double oldVal, double newVal) {
double scaleFactor = newVal / oldVal;
for (Node node: mySpline.getNodeList()) {
if ( xOrY == 'x' ) {
node.setX(node.getX()*scaleFactor);
node.relocate(node.getX(), node.getY());
} else if ( xOrY == 'y' ) {
node.setY(node.getY()*scaleFactor);
node.relocate(node.getX(), node.getY());
}
}
}
}
gui.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="anchorPane" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="InterpolationMinimal.Controller">
<children>
<HBox alignment="CENTER_LEFT" layoutX="0.0" prefHeight="40.0" prefWidth="250.0">
<children>
<ChoiceBox fx:id="choseBoundaryConditions" prefWidth="210.0">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</ChoiceBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="0.0" layoutY="40.0" prefHeight="40.0" prefWidth="250.0">
<children>
<ChoiceBox fx:id="choseSolverTechnique" prefWidth="210.0">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</ChoiceBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutY="80.0" prefHeight="40.0" prefWidth="250.0">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="250.0">
<children>
<Button fx:id="drawSpline" mnemonicParsing="false" onAction="#handleButtonDrawSpline" text="Draw Spline">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
<font>
<Font size="15.0" />
</font>
</Button>
</children>
</HBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutY="120.0" prefHeight="40.0" prefWidth="250.0">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="250.0">
<children>
<Button fx:id="clearCanvas" mnemonicParsing="false" onAction="#handleButtonClearCanvas" text="Clear Canvas">
<font>
<Font size="15.0" />
</font>
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
</children>
</HBox>
</children>
</HBox>
<Pane fx:id="pane" layoutX="250.0" prefHeight="700.0" prefWidth="750.0"/>
<Separator fx:id="separator" layoutX="250.0" layoutY="0.0" orientation="VERTICAL" prefHeight="700.0" />
</children>
</AnchorPane>
Node.java
package InterpolationMinimal;
import javafx.scene.shape.Circle;
public class Node extends Circle implements Comparable<Node> {
private double x;
private double y;
public Node () {
}
public Node (double x, double y)
{
this.x = x;
this.y = y;
// also initialize the Circle properties:
this.setCenterX(x);
this.setCenterY(y);
}
@Override
public int compareTo(Node n) {
return this.x<n.getX()?-1:this.x>n.getX()?1:0;
}
public double getX ()
{
return this.x;
}
public double getY ()
{
return this.y;
}
public void setX (double x)
{
this.x = x;
}
public void setY (double y)
{
this.y = y;
}
}
Spline.java
package InterpolationMinimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import javafx.scene.shape.Polyline;
import javafx.scene.layout.Pane;
public class Spline {
private ArrayList<Node> nodeList = new ArrayList<>();
private Polyline polyline = new Polyline();
private double[][] coefficients;
public ArrayList<Node> getNodeList()
{
return this.nodeList;
}
public void calculate (String boundaryConditions, String solverTechnique)
{
int numberOfNodes = this.nodeList.size();
int n = numberOfNodes - 1; // = number of splines
this.coefficients = new double[n][4];
Collections.sort(this.nodeList);
// System of equations to solve: Mc = m
// Fill the matrix M:
double[][] M = new double[n-1][n-1];
int i, j;
double h, hPlus1;
for (i=0; i<n-1; i++) {
j = i;
h = this.nodeList.get(i+1).getX() - this.nodeList.get(i).getX();
hPlus1 = this.nodeList.get(i+2).getX() - this.nodeList.get(i+1).getX();
M[i][j] = 2 * ( h + hPlus1 ); // Diagonale der Matrix mit i=j
if ( j > 0 ) M[i][j-1] = h;
if ( j < n-2 ) M[i][j+1] = hPlus1;
}
// Fill m
double[] m = new double[n-1];
for (i=0; i<n-1; i++) {
h = this.nodeList.get(i+1).getX() - this.nodeList.get(i ).getX();
hPlus1 = this.nodeList.get(i+2).getX() - this.nodeList.get(i+1).getX();
m[i] = 3 * ( this.nodeList.get(i+2).getY() - this.nodeList.get(i+1).getY() ) / hPlus1 -
3 * ( this.nodeList.get(i+1).getY() - this.nodeList.get(i ).getY() ) / h;
// Use the boundary conditions
if ( i==0 ) {
m[i] -= h * 0.0;
} else if ( i==n-2 ) {
m[i] -= h * 0.0;
}
}
// Iterative Lösung für c
double[] cTemp = new double[n-1];
double tolerance = 0.0001;
if ( Objects.equals(solverTechnique,"Jacobi") ) {
cTemp = jacobiVerfahren(M, m, tolerance);
} /*else if ( Objects.equals(solverTechnique,"Gauss-Seidel") ) {
cTemp = Matrix.gaussSeidelVerfahren(M, m, tolerance);
}*/
double[] c = new double[n];
c[0] = 0.0; // As in the left boundary condition
for (i=1; i<n; i++) {
c[i] = cTemp[i-1];
}
// Determine the other three coefficients
double[] a = new double[n];
double[] b = new double[n];
double[] d = new double[n];
for (i=0; i<n; i++) {
a[i] = this.nodeList.get(i).getY();
h = this.nodeList.get(i+1).getX() - this.nodeList.get(i).getX();
if ( i < n-1 ) {
b[i] = ((this.nodeList.get(i+1).getY()-this.nodeList.get(i).getY()) / h) -
h * (2*c[i] + c[i+1] ) / 3.0;
d[i] = (c[i+1] - c[i]) / (3.0 * h);
} else {
// c[i+1] = 0.0 wegen der natürlichen Randbedingungen
d[i] = (0.0 - c[i]) / (3.0 * h);
b[i] = ((this.nodeList.get(i+1).getY()-this.nodeList.get(i).getY()) / h) -
h * (2*c[i] + 0.0 ) / 3.0;
}
}
for (i=0; i<n; i++) {
this.coefficients[i][0] = a[i];
this.coefficients[i][1] = b[i];
this.coefficients[i][2] = c[i];
this.coefficients[i][3] = d[i];
}
}
/**
* Solve a linear system of equations (LSE) Ax=b
* @param A Matrix giving the coefficients of the LSE
* @param b Right-hand side of the equations
* @return Solution vector x of the LSE
*/
public static double[] jacobiVerfahren(double[][] A, double[] b, double tolerance)
{
int i, j;
int n = A.length; // A.lenght = A[0].length, da quadratisch
double[] x = new double[n];
double startwert = 0.0; // willkürlicher Startwert
double summe;
double[] xOld = new double[n];
for (i=0; i<n; i++) {
xOld[i] = startwert;
}
double[] genauigkeit = new double[n];
int howManyTimesUntilSmallerEpsilon = 0;
int maxIterations = 100;
boolean genauigkeitErreicht = false;
while ( ! genauigkeitErreicht ) {
howManyTimesUntilSmallerEpsilon++;
if ( howManyTimesUntilSmallerEpsilon > maxIterations ) break;
for (i=0; i<n; i++) {
summe = 0.0;
for (j=0; j<n; j++) {
if ( j != i ) {
summe += A[i][j] * xOld[j];
}
}
x[i] = ( b[i] - summe ) / A[i][i];
}
for (i=0; i<n; i++) {
genauigkeit[i] = Math.abs(x[i] - xOld[i]);
}
// Die Genauigkeit von epsilon muss für jedes Element erreicht sein, so dass
// bereits ein Element, auf das das nicht zutrifft, ausreicht, um die Iteration
// weiter zu führen.
for (i=0; i<n; i++) {
if ( genauigkeit[i] > tolerance ) {
genauigkeitErreicht = false;
break;
} else {
genauigkeitErreicht = true;
}
}
for (i=0; i<n; i++) {
xOld[i] = x[i];
}
}
return x;
}
public void draw (Pane pane)
{
int i, j, s;
int n = this.nodeList.size();
double oneStep;
double nSteps = 12.0;
double x, y, a, b, c, d;
double xDifference;
polyline.getPoints().clear();
for (i=0; i<n-1; i++) {
// Calculate the increment by means of the distance between two neighboring points
oneStep = Math.abs(this.nodeList.get(i).getX() - this.nodeList.get(i+1).getX()) / nSteps;
a = this.coefficients[i][0];
b = this.coefficients[i][1];
c = this.coefficients[i][2];
d = this.coefficients[i][3];
for (s=0; s<nSteps; s++) {
x = this.nodeList.get(i).getX() + s * oneStep;
// to calculate y we need the formula of a third-order cubic spline of the form
// y(x) = a(i) + b(i)(x-x(i))^1 + c(i)(x-x(i))^2 + d(i)(x-x(i))^3
xDifference = x - this.nodeList.get(i).getX();
y = a + (b*xDifference) + (c*Math.pow(xDifference,2.0)) + (d*Math.pow(xDifference,3.0));
polyline.getPoints().addAll(x, y);
}
}
polyline.getPoints().addAll(this.nodeList.get(n-1).getX(), this.nodeList.get(n-1).getY());
polyline.toBack();
pane.getChildren().add(polyline);
}
}
MyMouseEvents.java
package InterpolationMinimal;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
public class MyMouseEvents {
public static void paneMouseEvents(Pane pane) {
MouseGestures mg = new MouseGestures();
pane.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getTarget() == pane) {
Node node = new Node(mouseEvent.getSceneX()-pane.getLayoutX(), mouseEvent.getSceneY());
node.setRadius(10.0);
mg.makeDraggable(node);
Controller.mySpline.getNodeList().add(node);
pane.getChildren().add(node);
}
mouseEvent.consume();
}
});
}
}
MouseGestures.java
package InterpolationMinimal;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.input.MouseEvent;
public class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable(Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
//orgSceneX = me.getSceneX();
orgSceneY = me.getSceneY();
Node p = (Node) me.getSource();
//orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
//double offsetX = me.getSceneX() - orgSceneX;
double offsetY = me.getSceneY() - orgSceneY;
//double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Node p = (Node) me.getSource();
System.out.println();
//p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
//p.setX(newTranslateX);
p.setY(newTranslateY);
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("gui.fxml"));
loader.load();
Controller controller = loader.getController();
controller.handleButtonDrawSpline();
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
重新计算和重绘操作的实现方式是通过 MouseGestures.circleOnMouseDraggedEventHandler
访问 Controller.handleButtonDrawSpline
方法。但是这样做 pane.getChildren()
是空的(它至少应该包含已经添加到 pane
的 node
对象),而当我点击 "Draw Spline" 时它不是空的按钮。两次执行 handleButtonDrawSpline
方法,但 pane
对象不同。我有 "feeling" 问题出在某处。任何帮助是极大的赞赏。
每次调用 circleOnMouseDraggedEventHandler
方法时,您都会创建一个带有新 pane
的新 gui
控件。这意味着调用 controller.handleButtonDrawSpline()
每次都会绘制一个新的窗格实例。
避免这种情况的最简单方法是将控制器作为参数传递。
在控制器中:
@Override
public void initialize(URL url, ResourceBundle rb) {
[...]
MyMouseEvents.paneMouseEvents(pane, this);
}
在 MyMouseEvents 中:
public static void paneMouseEvents(Pane pane, Controller controller) {
MouseGestures mg = new MouseGestures(controller);
[...]
}
在鼠标手势中:
private final Controller controller;
public MouseGestures(Controller controller) {
this.controller = controller;
}
[...]
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
// double offsetX = me.getSceneX() - orgSceneX;
double offsetY = me.getSceneY() - orgSceneY;
// double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Node p = (Node) me.getSource();
System.out.println();
// p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
// p.setX(newTranslateX);
p.setY(newTranslateY);
controller.handleButtonDrawSpline();
}
};
因为 draw 方法现在正在 pane
的同一个实例上绘制,所以您必须避免在 Spline#draw
:
的窗格中多次添加多段线
polyline.getPoints().addAll(this.nodeList.get(n - 1).getX(), this.nodeList.get(n - 1).getY());
polyline.toBack();
if (!pane.getChildren().contains(polyline)) {
pane.getChildren().add(polyline);
}
结果:
我有一个 GUI,它在至少三个绘制样条曲线的节点之间进行插值。左键单击窗格会导致在单击的位置设置一个新的(可拖动的)节点。如果至少存在三个节点,则按下按钮 "Draw Spline" 将在节点之间绘制插值样条曲线。一切正常,除了 当其中一个节点被向上或向下拖动时,样条曲线不会动态重绘 。相反,在我移动一个节点后,我需要按下按钮 "Draw Spline",新的 Spline 就会出现。但我想看到节点立即移动位置的效果,而不是总是按下按钮。我在 NetBeans 8.1 中开发了这个 JavaFX 项目。
这是我的代码:
Main.java
package InterpolationMinimal;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
AnchorPane root = FXMLLoader.load(getClass().getResource("gui.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Interpolation using cubic splines");
stage.setScene(scene);
stage.show();
// this is needed to resize object if window/scene is resized
root.prefWidthProperty().bind(scene.widthProperty());
root.prefHeightProperty().bind(scene.heightProperty());
}
}
Controller.java
package InterpolationMinimal;
//import DraggableNodesGraph.MyAlerts;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Separator;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
public class Controller implements Initializable {
@FXML private AnchorPane anchorPane;
@FXML private Pane pane;
@FXML private Separator separator;
@FXML private ChoiceBox choseBoundaryConditions;
@FXML private ChoiceBox choseSolverTechnique;
private ObservableList boundaryConditionsToChose; //!< List of boundary condtions type
private ObservableList solvingTechniqueToChose; //!< List of techniques for solving a linear system of equations
public static Spline mySpline = new Spline();
@Override
public void initialize(URL url, ResourceBundle rb) {
// Set choices, default and event handling for the boundary conditions ChoiceBox
boundaryConditionsToChose = FXCollections.observableArrayList();
boundaryConditionsToChose.add("Natural");
boundaryConditionsToChose.add("Periodic");
choseBoundaryConditions.setItems(boundaryConditionsToChose);
choseBoundaryConditions.setValue(boundaryConditionsToChose.get(0));
// Set choices, default and event handling for the type of solving technique ChoiceBox
solvingTechniqueToChose = FXCollections.observableArrayList();
solvingTechniqueToChose.add("Jacobi");
solvingTechniqueToChose.add("Gauss-Seidel");
choseSolverTechnique.setItems(solvingTechniqueToChose);
choseSolverTechnique.setValue(solvingTechniqueToChose.get(0));
// Add listeners to the window size and redraw in case the window's size is changed.
// Subtract canvas.layout* to make sure the center of drawing is in the center
// of the canvas and not in the center of the whole window.
anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> {
pane.setPrefWidth(newValue.doubleValue() - pane.layoutXProperty().doubleValue());
rescaleObjects('x', oldValue.doubleValue(), newValue.doubleValue());
});
anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> {
pane.setPrefHeight(newValue.doubleValue() - pane.layoutYProperty().doubleValue());
separator.setPrefHeight(pane.getPrefHeight());
rescaleObjects('y', oldValue.doubleValue(), newValue.doubleValue());
});
MyMouseEvents.paneMouseEvents(pane);
}
@FXML public void handleButtonDrawSpline() {
if ( mySpline.getNodeList().size() >= 3 ) {
mySpline.calculate(String.valueOf(choseBoundaryConditions.getValue()),
String.valueOf(choseSolverTechnique.getValue()));
mySpline.draw(pane);
} /*else {
MyAlerts.displayAlert("You need at least 3 points for the interpolation!");
}*/
}
@FXML private void handleButtonClearCanvas(ActionEvent event) {
pane.getChildren().clear();
mySpline.getNodeList().clear();
}
private void rescaleObjects (char xOrY, double oldVal, double newVal) {
double scaleFactor = newVal / oldVal;
for (Node node: mySpline.getNodeList()) {
if ( xOrY == 'x' ) {
node.setX(node.getX()*scaleFactor);
node.relocate(node.getX(), node.getY());
} else if ( xOrY == 'y' ) {
node.setY(node.getY()*scaleFactor);
node.relocate(node.getX(), node.getY());
}
}
}
}
gui.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="anchorPane" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="InterpolationMinimal.Controller">
<children>
<HBox alignment="CENTER_LEFT" layoutX="0.0" prefHeight="40.0" prefWidth="250.0">
<children>
<ChoiceBox fx:id="choseBoundaryConditions" prefWidth="210.0">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</ChoiceBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="0.0" layoutY="40.0" prefHeight="40.0" prefWidth="250.0">
<children>
<ChoiceBox fx:id="choseSolverTechnique" prefWidth="210.0">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</ChoiceBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutY="80.0" prefHeight="40.0" prefWidth="250.0">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="250.0">
<children>
<Button fx:id="drawSpline" mnemonicParsing="false" onAction="#handleButtonDrawSpline" text="Draw Spline">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
<font>
<Font size="15.0" />
</font>
</Button>
</children>
</HBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutY="120.0" prefHeight="40.0" prefWidth="250.0">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="250.0">
<children>
<Button fx:id="clearCanvas" mnemonicParsing="false" onAction="#handleButtonClearCanvas" text="Clear Canvas">
<font>
<Font size="15.0" />
</font>
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
</children>
</HBox>
</children>
</HBox>
<Pane fx:id="pane" layoutX="250.0" prefHeight="700.0" prefWidth="750.0"/>
<Separator fx:id="separator" layoutX="250.0" layoutY="0.0" orientation="VERTICAL" prefHeight="700.0" />
</children>
</AnchorPane>
Node.java
package InterpolationMinimal;
import javafx.scene.shape.Circle;
public class Node extends Circle implements Comparable<Node> {
private double x;
private double y;
public Node () {
}
public Node (double x, double y)
{
this.x = x;
this.y = y;
// also initialize the Circle properties:
this.setCenterX(x);
this.setCenterY(y);
}
@Override
public int compareTo(Node n) {
return this.x<n.getX()?-1:this.x>n.getX()?1:0;
}
public double getX ()
{
return this.x;
}
public double getY ()
{
return this.y;
}
public void setX (double x)
{
this.x = x;
}
public void setY (double y)
{
this.y = y;
}
}
Spline.java
package InterpolationMinimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import javafx.scene.shape.Polyline;
import javafx.scene.layout.Pane;
public class Spline {
private ArrayList<Node> nodeList = new ArrayList<>();
private Polyline polyline = new Polyline();
private double[][] coefficients;
public ArrayList<Node> getNodeList()
{
return this.nodeList;
}
public void calculate (String boundaryConditions, String solverTechnique)
{
int numberOfNodes = this.nodeList.size();
int n = numberOfNodes - 1; // = number of splines
this.coefficients = new double[n][4];
Collections.sort(this.nodeList);
// System of equations to solve: Mc = m
// Fill the matrix M:
double[][] M = new double[n-1][n-1];
int i, j;
double h, hPlus1;
for (i=0; i<n-1; i++) {
j = i;
h = this.nodeList.get(i+1).getX() - this.nodeList.get(i).getX();
hPlus1 = this.nodeList.get(i+2).getX() - this.nodeList.get(i+1).getX();
M[i][j] = 2 * ( h + hPlus1 ); // Diagonale der Matrix mit i=j
if ( j > 0 ) M[i][j-1] = h;
if ( j < n-2 ) M[i][j+1] = hPlus1;
}
// Fill m
double[] m = new double[n-1];
for (i=0; i<n-1; i++) {
h = this.nodeList.get(i+1).getX() - this.nodeList.get(i ).getX();
hPlus1 = this.nodeList.get(i+2).getX() - this.nodeList.get(i+1).getX();
m[i] = 3 * ( this.nodeList.get(i+2).getY() - this.nodeList.get(i+1).getY() ) / hPlus1 -
3 * ( this.nodeList.get(i+1).getY() - this.nodeList.get(i ).getY() ) / h;
// Use the boundary conditions
if ( i==0 ) {
m[i] -= h * 0.0;
} else if ( i==n-2 ) {
m[i] -= h * 0.0;
}
}
// Iterative Lösung für c
double[] cTemp = new double[n-1];
double tolerance = 0.0001;
if ( Objects.equals(solverTechnique,"Jacobi") ) {
cTemp = jacobiVerfahren(M, m, tolerance);
} /*else if ( Objects.equals(solverTechnique,"Gauss-Seidel") ) {
cTemp = Matrix.gaussSeidelVerfahren(M, m, tolerance);
}*/
double[] c = new double[n];
c[0] = 0.0; // As in the left boundary condition
for (i=1; i<n; i++) {
c[i] = cTemp[i-1];
}
// Determine the other three coefficients
double[] a = new double[n];
double[] b = new double[n];
double[] d = new double[n];
for (i=0; i<n; i++) {
a[i] = this.nodeList.get(i).getY();
h = this.nodeList.get(i+1).getX() - this.nodeList.get(i).getX();
if ( i < n-1 ) {
b[i] = ((this.nodeList.get(i+1).getY()-this.nodeList.get(i).getY()) / h) -
h * (2*c[i] + c[i+1] ) / 3.0;
d[i] = (c[i+1] - c[i]) / (3.0 * h);
} else {
// c[i+1] = 0.0 wegen der natürlichen Randbedingungen
d[i] = (0.0 - c[i]) / (3.0 * h);
b[i] = ((this.nodeList.get(i+1).getY()-this.nodeList.get(i).getY()) / h) -
h * (2*c[i] + 0.0 ) / 3.0;
}
}
for (i=0; i<n; i++) {
this.coefficients[i][0] = a[i];
this.coefficients[i][1] = b[i];
this.coefficients[i][2] = c[i];
this.coefficients[i][3] = d[i];
}
}
/**
* Solve a linear system of equations (LSE) Ax=b
* @param A Matrix giving the coefficients of the LSE
* @param b Right-hand side of the equations
* @return Solution vector x of the LSE
*/
public static double[] jacobiVerfahren(double[][] A, double[] b, double tolerance)
{
int i, j;
int n = A.length; // A.lenght = A[0].length, da quadratisch
double[] x = new double[n];
double startwert = 0.0; // willkürlicher Startwert
double summe;
double[] xOld = new double[n];
for (i=0; i<n; i++) {
xOld[i] = startwert;
}
double[] genauigkeit = new double[n];
int howManyTimesUntilSmallerEpsilon = 0;
int maxIterations = 100;
boolean genauigkeitErreicht = false;
while ( ! genauigkeitErreicht ) {
howManyTimesUntilSmallerEpsilon++;
if ( howManyTimesUntilSmallerEpsilon > maxIterations ) break;
for (i=0; i<n; i++) {
summe = 0.0;
for (j=0; j<n; j++) {
if ( j != i ) {
summe += A[i][j] * xOld[j];
}
}
x[i] = ( b[i] - summe ) / A[i][i];
}
for (i=0; i<n; i++) {
genauigkeit[i] = Math.abs(x[i] - xOld[i]);
}
// Die Genauigkeit von epsilon muss für jedes Element erreicht sein, so dass
// bereits ein Element, auf das das nicht zutrifft, ausreicht, um die Iteration
// weiter zu führen.
for (i=0; i<n; i++) {
if ( genauigkeit[i] > tolerance ) {
genauigkeitErreicht = false;
break;
} else {
genauigkeitErreicht = true;
}
}
for (i=0; i<n; i++) {
xOld[i] = x[i];
}
}
return x;
}
public void draw (Pane pane)
{
int i, j, s;
int n = this.nodeList.size();
double oneStep;
double nSteps = 12.0;
double x, y, a, b, c, d;
double xDifference;
polyline.getPoints().clear();
for (i=0; i<n-1; i++) {
// Calculate the increment by means of the distance between two neighboring points
oneStep = Math.abs(this.nodeList.get(i).getX() - this.nodeList.get(i+1).getX()) / nSteps;
a = this.coefficients[i][0];
b = this.coefficients[i][1];
c = this.coefficients[i][2];
d = this.coefficients[i][3];
for (s=0; s<nSteps; s++) {
x = this.nodeList.get(i).getX() + s * oneStep;
// to calculate y we need the formula of a third-order cubic spline of the form
// y(x) = a(i) + b(i)(x-x(i))^1 + c(i)(x-x(i))^2 + d(i)(x-x(i))^3
xDifference = x - this.nodeList.get(i).getX();
y = a + (b*xDifference) + (c*Math.pow(xDifference,2.0)) + (d*Math.pow(xDifference,3.0));
polyline.getPoints().addAll(x, y);
}
}
polyline.getPoints().addAll(this.nodeList.get(n-1).getX(), this.nodeList.get(n-1).getY());
polyline.toBack();
pane.getChildren().add(polyline);
}
}
MyMouseEvents.java
package InterpolationMinimal;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
public class MyMouseEvents {
public static void paneMouseEvents(Pane pane) {
MouseGestures mg = new MouseGestures();
pane.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getTarget() == pane) {
Node node = new Node(mouseEvent.getSceneX()-pane.getLayoutX(), mouseEvent.getSceneY());
node.setRadius(10.0);
mg.makeDraggable(node);
Controller.mySpline.getNodeList().add(node);
pane.getChildren().add(node);
}
mouseEvent.consume();
}
});
}
}
MouseGestures.java
package InterpolationMinimal;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.input.MouseEvent;
public class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable(Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
//orgSceneX = me.getSceneX();
orgSceneY = me.getSceneY();
Node p = (Node) me.getSource();
//orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
//double offsetX = me.getSceneX() - orgSceneX;
double offsetY = me.getSceneY() - orgSceneY;
//double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Node p = (Node) me.getSource();
System.out.println();
//p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
//p.setX(newTranslateX);
p.setY(newTranslateY);
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("gui.fxml"));
loader.load();
Controller controller = loader.getController();
controller.handleButtonDrawSpline();
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
重新计算和重绘操作的实现方式是通过 MouseGestures.circleOnMouseDraggedEventHandler
访问 Controller.handleButtonDrawSpline
方法。但是这样做 pane.getChildren()
是空的(它至少应该包含已经添加到 pane
的 node
对象),而当我点击 "Draw Spline" 时它不是空的按钮。两次执行 handleButtonDrawSpline
方法,但 pane
对象不同。我有 "feeling" 问题出在某处。任何帮助是极大的赞赏。
每次调用 circleOnMouseDraggedEventHandler
方法时,您都会创建一个带有新 pane
的新 gui
控件。这意味着调用 controller.handleButtonDrawSpline()
每次都会绘制一个新的窗格实例。
避免这种情况的最简单方法是将控制器作为参数传递。
在控制器中:
@Override
public void initialize(URL url, ResourceBundle rb) {
[...]
MyMouseEvents.paneMouseEvents(pane, this);
}
在 MyMouseEvents 中:
public static void paneMouseEvents(Pane pane, Controller controller) {
MouseGestures mg = new MouseGestures(controller);
[...]
}
在鼠标手势中:
private final Controller controller;
public MouseGestures(Controller controller) {
this.controller = controller;
}
[...]
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
// double offsetX = me.getSceneX() - orgSceneX;
double offsetY = me.getSceneY() - orgSceneY;
// double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Node p = (Node) me.getSource();
System.out.println();
// p.setCenterX(newTranslateX);
p.setCenterY(newTranslateY);
// p.setX(newTranslateX);
p.setY(newTranslateY);
controller.handleButtonDrawSpline();
}
};
因为 draw 方法现在正在 pane
的同一个实例上绘制,所以您必须避免在 Spline#draw
:
polyline.getPoints().addAll(this.nodeList.get(n - 1).getX(), this.nodeList.get(n - 1).getY());
polyline.toBack();
if (!pane.getChildren().contains(polyline)) {
pane.getChildren().add(polyline);
}
结果: