2017-01-09 5 views
0

Ich habe eine GUI, die zwischen mindestens drei Knoten interpoliert, die einen Spline zeichnen. Ein Linksklick auf den Bereich führt zum Setzen eines neuen (ziehbaren) Knotens an der angeklickten Position. Wenn mindestens drei Knoten vorhanden sind, wird ein Knopfdruck "Spline zeichnen" den interpolierten Spline zwischen den Knoten zeichnen. Alles funktioniert gut, außer dass der Spline nicht dynamisch neu gezogen wird, wenn einer der Knoten nach oben oder unten gezogen wird. Stattdessen muss ich, nachdem ich einen Knoten bewegt habe, den Knopf "Draw Spline" drücken und der neue Spline erscheint. Aber ich möchte den Effekt sehen, dass ein Knoten sofort in seiner Position verschoben wird und nicht immer auf den Knopf drückt. Ich habe dieses JavaFX-Projekt in NetBeans 8.1 entwickelt.JavaFX: Dynamische Aktualisierung (Neuberechnung + Neuzeichnen) Polylinie, wenn ein Knoten auf einen Bereich gezogen wird

Hier ist mein Code:

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(); 
      } 
     } 
    }; 
} 

Die Neu berechnen und neu zu zeichnen Aktion wird in einer Weise realisiert, die über die MouseGestures.circleOnMouseDraggedEventHandlerController.handleButtonDrawSpline Methode zugegriffen wird. Aber dabei ist pane.getChildren() leer (es sollte mindestens die node Objekte enthalten, die bereits zu pane hinzugefügt wurden), solange es nicht leer ist, wenn ich den "Draw Spline" Knopf drücke. Beide Male wird die handleButtonDrawSpline Methode ausgeführt, aber das Objekt pane ist anders. Ich habe das "Gefühl", dass das Problem irgendwo dort liegt. Jede Hilfe wird sehr geschätzt.

Antwort

2

Sie erstellen ein neues gui Steuerelement mit einem neuen pane jedes Mal, wenn Ihre circleOnMouseDraggedEventHandler Methode aufgerufen wird. Dies bedeutet, dass der Aufruf controller.handleButtonDrawSpline() jedes Mal auf eine neue Fensterinstanz verweist.

Der einfachste Weg, dies zu vermeiden, wäre, den Controller als Argument zu übergeben.

In Controller:

@Override 
public void initialize(URL url, ResourceBundle rb) { 
    [...] 
    MyMouseEvents.paneMouseEvents(pane, this); 
} 

In MyMouseEvents:

public static void paneMouseEvents(Pane pane, Controller controller) { 
    MouseGestures mg = new MouseGestures(controller); 
    [...] 
} 

In MouseGestures:

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(); 

    } 
}; 

Da die Streck-Verfahren wird nun auf der gleichen Instanz von pane zeichnen, Sie haben um zu vermeiden, die Polylinie mehr als einmal zum Fenster inhinzuzufügen:

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); 
} 

Ergebnis:

animated gif

+0

Vielen Dank @Modus Tollens! – 732E772E

Verwandte Themen