我有一個在至少三個繪製樣條曲線的節點之間插入的GUI。在窗格上單擊鼠標左鍵即可在單擊位置設置新的(可拖動)節點。如果至少存在三個節點,則按下按鈕「繪製樣條線」將在節點之間繪製插值樣條曲線。除此之外,一切正常,當其中一個節點被向上或向下拖動時,Spline不會動態地重新繪製。相反,在我移動一個節點後,我需要按下「繪製樣條曲線」按鈕,並出現新的樣條曲線。但是我想看到一個節點立即轉移到其位置的效果,並不總是按下按鈕。我在NetBeans 8.1中開發了這個JavaFX項目。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
對象),而當我點擊「繪製樣條線」按鈕時它不爲空。 handleButtonDrawSpline
方法都執行,但pane
對象不同。我有「感覺」問題出現在那裏。任何幫助是極大的讚賞。
謝謝你這麼多@Modus託倫斯! – 732E772E