У меня есть графический интерфейс, который интерполирует по крайней мере между тремя узлами, рисующими сплайн. Щелчок левой кнопкой мыши на панели приводит к установке нового (перетаскиваемого) узла в месте щелчка. Если присутствуют как минимум три узла, нажатие кнопки «Нарисовать сплайн» нарисует интерполированный сплайн между узлами. Все работает отлично, за исключением того, что сплайн не перерисовывается динамически, когда один из узлов перетаскивается вверх или вниз. Вместо этого после того, как я переместил узел, мне нужно нажать кнопку «Нарисовать сплайн», и появится новый сплайн. Но я хочу видеть эффект смещения узла сразу, а не всегда нажимать кнопку. Я разработал этот проект JavaFX в NetBeans 8.1.
Вот мой код:
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());
}
}
Контроллер.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;
}
}
Сплайн.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()
пуст (он должен по крайней мере содержать объекты node
, которые уже были добавлены в pane
), в то время как он не пуст, когда я нажимаю кнопку «Нарисовать сплайн». Оба раза выполняется метод handleButtonDrawSpline
, но объект pane
отличается. У меня такое "чувство", что проблема где-то там. Любая помощь приветствуется.