Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have the LineChart with 4 different series. There are checkboxes which should show/hide the correspondent series from chart. But when I pressed checkboxes for "Euler", "Improved Euler", "Runge-Kutta", they do not affect the chart at all, until I press the "Exact"'s checkbox. Can't get the problem absolutely.
Here is how I try to hide/show:
#FXML private NumberAxis xAxis = new NumberAxis();
#FXML private NumberAxis yAxis = new NumberAxis();
#FXML private LineChart<Number, Number> chartAll = new LineChart<>(xAxis, yAxis);
private XYChart.Series<Number, Number> exactSeries = new XYChart.Series<>();
private XYChart.Series<Number, Number> eulerSeries= new XYChart.Series<>();
private XYChart.Series<Number, Number> improvedEulerSeries = new XYChart.Series<>();
private XYChart.Series<Number, Number> rungeKuttaSeries = new XYChart.Series<>();
#FXML private CheckBox exactChartCheckbox;
#FXML private CheckBox eulerChartCheckbox;
#FXML private CheckBox improvedEulerChartCheckbox;
#FXML private CheckBox rungeKuttaChartCheckbox;
#FXML
protected void handlePlotButton(ActionEvent event) {
chartAll.getData().clear();
double x0 = Double.parseDouble(x0Input.getText());
double y0 = Double.parseDouble(y0Input.getText());
double x = Double.parseDouble(xInput.getText());
double step = Double.parseDouble(stepInput.getText());
IVP ivpForExact = new IVP(x0, y0, x, step);
Exact exact = new Exact(ivpForExact);
Euler euler = new Euler(ivpForExact);
ImprovedEuler improvedEuler = new ImprovedEuler(ivpForExact);
RungeKutta rungeKutta = new RungeKutta(ivpForExact);
exactSeries.setName("Exact");
eulerSeries.setName("Euler");
improvedEulerSeries.setName("Improved Euler");
rungeKuttaSeries.setName("Runge-Kutta");
for (int i = 0; i < exact.getGrid().getSize(); i++) {
exactSeries.getData().add(new XYChart.Data<>(exact.getGrid().getXat(i), exact.getGrid().getYat(i)));
eulerSeries.getData().add(new XYChart.Data<>(euler.getGrid().getXat(i), euler.getGrid().getYat(i)));
improvedEulerSeries.getData().add(new XYChart.Data<>(improvedEuler.getGrid().getXat(i), improvedEuler.getGrid().getYat(i)));
rungeKuttaSeries.getData().add(new XYChart.Data<>(rungeKutta.getGrid().getXat(i), rungeKutta.getGrid().getYat(i)));
}
chartAll.getData().add(exactSeries);
chartAll.getData().add(eulerSeries);
chartAll.getData().add(improvedEulerSeries);
chartAll.getData().add(rungeKuttaSeries);
stepInput.clear();
}
#FXML
protected void exactChartCheckboxPressed(ActionEvent event) {
if(!exactChartCheckbox.isSelected()) {
chartAll.getData().get(0).getNode().setVisible(false);
} else {
chartAll.getData().get(0).getNode().setVisible(true);
}
}
#FXML
protected void eulerChartCheckboxPressed(ActionEvent event) {
if(!eulerChartCheckbox.isSelected()) {
chartAll.getData().get(1).getNode().setVisible(false);
} else {
chartAll.getData().get(1).getNode().setVisible(true);
}
}
Here is FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="scene" alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<children>
<Pane fx:id="pane" prefHeight="600.0" prefWidth="1080.0">
<children>
<LineChart fx:id="chartAll" createSymbols="false" layoutX="48.0" layoutY="135.0" prefHeight="427.0" prefWidth="698.0">
<xAxis>
<NumberAxis label="x" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" autoRanging="false" label="y" lowerBound="-10.0" upperBound="40.0" />
</yAxis>
<opaqueInsets>
<Insets />
</opaqueInsets>
</LineChart>
<Button layoutX="48.0" layoutY="54.0" mnemonicParsing="false" prefHeight="41.0" prefWidth="120.0" text="Solutions" />
<TextField layoutX="883.0" layoutY="211.0" prefHeight="25.0" prefWidth="96.0" text="0" fx:id="x0Input" />
<TextField fx:id="y0Input" layoutX="883.0" layoutY="254.0" prefHeight="25.0" prefWidth="96.0" text="3" />
<TextField layoutX="883.0" layoutY="296.0" prefHeight="25.0" prefWidth="96.0" text="5.5" fx:id="xInput" />
<TextField fx:id="stepInput" layoutX="883.0" layoutY="336.0" prefHeight="25.0" prefWidth="96.0" text="0.1" />
<Label layoutX="841.0" layoutY="215.0" text="x0" />
<Label layoutX="841.0" layoutY="258.0" text="y0" />
<Label layoutX="841.0" layoutY="300.0" text="X" />
<Label layoutX="835.0" layoutY="340.0" text="step" />
<Button fx:id="plotButton" layoutX="896.0" layoutY="385.0" mnemonicParsing="false" onAction="#handlePlotButton" prefHeight="41.0" prefWidth="71.0" text="Plot" />
<CheckBox fx:id="exactChartCheckbox" layoutX="347.0" layoutY="562.0" mnemonicParsing="false" onAction="#exactChartCheckboxPressed" selected="true" text="Exact" />
<CheckBox fx:id="improvedEulerChartCheckbox" layoutX="523.0" layoutY="562.0" mnemonicParsing="false" onAction="#improvedEulerChartCheckboxPressed" selected="true" text="Improved Euler" />
<CheckBox fx:id="rungeKuttaChartCheckbox" layoutX="646.0" layoutY="562.0" mnemonicParsing="false" onAction="#rungeKuttaChartCheckboxPressed" selected="true" text="Runge-Kutta" />
<CheckBox fx:id="eulerChartCheckbox" layoutX="444.0" layoutY="562.0" mnemonicParsing="false" onAction="#eulerChartCheckboxPressed" selected="true" text="Euler" />
</children>
</Pane>
</children>
</GridPane>
Here is the result
after I hide firstly "Euler" and then "Exact":
after I show "Exact" then "Euler":
The problem actually is that chart is only updated when I check/uncheck "Exact" box, all three others do not affect the chart. But when I press the "Exact"'s checkbox, they are trying to be updated also but do it improperly.
To effectively hide the node try setting the managed property
series.getNode().setManaged(false);
Defines whether or not this node's layout will be managed by it's parent. If the node is managed, it's parent will factor the node's geometry into its own preferred size and layoutBounds calculations and will lay it out during the scene's layout pass. If a managed node's layoutBounds changes, it will automatically trigger relayout up the scene-graph to the nearest layout root (which is typically the scene's root node).
If the node is unmanaged, its parent will ignore the child in both preferred size computations and layout. Changes in layoutBounds will not trigger relayout above it. If an unmanaged node is of type Parent, it will act as a "layout root", meaning that calls to Parent.requestLayout() beneath it will cause only the branch rooted by the node to be relayed out, thereby isolating layout changes to that root and below. It's the application's responsibility to set the size and position of an unmanaged node.
Based on the length of my comment, I am posting it in answer rather than in comment.
Firstly, if you check the documentation of node in XYChart.Series
The node to display for this series. This is created by the chart if
it uses nodes to represent the whole series. For example line chart
uses this for the line but scatter chart does not use it. This node
will be set as soon as the series is added to the chart. You can then
get it to add mouse listeners etc.
From the above, it is clear that you can easily rely on node if it is line chart. And from your screenshot I can see that you are already using LineChart. Which is not a problem. If you inspect the node type, it will be of type Path. But things get a little messy if your line chart has data points displayed on it.Sometimes toggling the node(Path) visibility alone cannot get the desired behavior. For example check the below screen shot.
So to hide a series from the chart, I would recommend to add/remove the series from the data rather than toggling the visibility of the series node.
Coming to your problem, I gave a quick check to toggle the lines visiblity and it working fine for all series. Might be some issue in your code. As #Slaw mentioned, a Minimal, Complete, and Verifiable example can help us to understand the issue or even solve your problem by yourself.
It seems, like it depends on your luck or some legacy JavaFX code. When I changed each series to Array of series:
private ArrayList<XYChart.Series<Number, Number>> exactSeries = new ArrayList<>();
private ArrayList<XYChart.Series<Number, Number>> eulerSeries = new ArrayList<>();
private ArrayList<XYChart.Series<Number, Number>> improvedEulerSeries = new ArrayList<>();
private ArrayList<XYChart.Series<Number, Number>> rungeKuttaSeries = new ArrayList<>();
and then create the common function for all cases:
private void turnVisibility(CheckBox checkBox, ArrayList<XYChart.Series<Number, Number>> seriesList) {
if (!checkBox.isSelected()) {
for(XYChart.Series<Number, Number> series: seriesList) {
series.getNode().setVisible(false);
}
} else {
for(XYChart.Series<Number, Number> series: seriesList) {
series.getNode().setVisible(true);
}
}
}
visibility starts work correctly.
Related
I have problem with my program.
I want to draw chart after providing data (coefficients of the equation).
I tried to change my import (it helped with some variables)
I changed java.awt* for javafx.scene ... (few imports).
FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="grahps.Controller">
<children>
<TextField fx:id="factorA" layoutX="24.0" layoutY="598.0" prefHeight="33.0" prefWidth="106.0" text="a=" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
<TextField fx:id="factorB" layoutX="24.0" layoutY="630.0" prefHeight="33.0" prefWidth="106.0" text="b=" AnchorPane.bottomAnchor="37.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
<TextField fx:id="factorC" layoutX="24.0" layoutY="674.0" prefHeight="33.0" prefWidth="106.0" text="c=" AnchorPane.bottomAnchor="1.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
<TextField layoutX="158.0" layoutY="592.0" prefHeight="47.0" prefWidth="120.0" text="xMin=" AnchorPane.bottomAnchor="61.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" fx:id="xMin" />
<TextField layoutX="158.0" layoutY="650.0" prefHeight="47.0" prefWidth="120.0" text="xMax=" AnchorPane.bottomAnchor="3.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" fx:id="xMax" />
<Label fx:id="label" layoutX="468.0" layoutY="629.0" prefHeight="61.0" prefWidth="276.0" text="f(x)=" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="468.0" AnchorPane.rightAnchor="56.0">
<font>
<Font size="18.0" />
</font>
</Label>
<LineChart fx:id="drawChart" prefHeight="598.0" prefWidth="800.0" title="Chart">
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
<Button fx:id="button" layoutX="317.0" layoutY="612.0" mnemonicParsing="false" prefHeight="61.0" prefWidth="98.0" text="Rysuj wykres" />
</children>
</AnchorPane>
The part with LineChart (fx:id="drawChart") generated communicate: "Unresolved fx:id reference
My main class:
package grahps;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
private Controller controller;//
#Override
public void start(Stage stage) throws Exception {
stage.show();
System.out.println(getClass().getResource("/fxml/sample.fxml").getPath());
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sample.fxml"));
loader.setController(controller);
Parent root = FXMLLoader.load(getClass().getResource("/fxml/sample.fxml"));
Scene scene = new Scene(root, 800, 800);
controller.drawChart(stage);
stage.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
And finally my controller:
package grahps;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
public class Controller {
#FXML
TextField factorA;
#FXML
TextField factorB;
#FXML
TextField factorC;
#FXML
TextField xMin;
#FXML
TextField xMax;
#FXML
Label label;
#FXML
Button button;
#FXML
XYChart.Series<Number, Number> chart;
//Parser Text Field -> double
double xMax1 = Double.parseDouble(xMax.getText());
double xMin1 = Double.parseDouble(xMin.getText());
double a = Double.parseDouble(factorA.getText());
double b = Double.parseDouble(factorB.getText());
double c = Double.parseDouble(factorC.getText());
#FXML
public void drawChart(Stage stage) {
XYChart.Series<Number, Number> series = chart;
series.setName("Chart");
final NumberAxis xAxis = new NumberAxis(xMin1, xMax1, 1);
final NumberAxis yAxis = new NumberAxis();
yAxis.setTickUnit(1);
xAxis.setLabel("X Label");
yAxis.setLabel("Y Label");
final javafx.scene.chart.LineChart<Number, Number> lineChart = new javafx.scene.chart.LineChart<Number, Number>(xAxis, yAxis);
double y;
String pattern;
if (a == 0 && c == 0) {
pattern = "f(x)=" + factorB;
label.setText(pattern);
} else if (c == 0) {
pattern = "f(x)=" + factorA + "x+" + factorB;
label.setText(pattern);
for (double i = xMin1; i <= xMax1; i++) {
y = a * i + b;
series.getData().add(new XYChart.Data(i, y));
}
} else {
pattern = "f(x)=" + factorC + "x^2" + factorA + "x+" + factorB;
label.setText(pattern);
for (double i = xMin1; i < xMax1; i++) {
y = a * i * i + b * i + c;
series.getData().add(new XYChart.Data(i, y));
}
}
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 800);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
}
Please notice one thing: this is my first JavaFX project.
Simply I want to fill in coefficients of the equation and generate equation pattern + draw chart after clicking button.
I'll be greaftul for help.
When I deleted #FXML annotation above method and compiled code I got those errors:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:748)
Caused by: javafx.fxml.LoadException:
/C:/Users/Damian/IdeaProjects/Graphs/target/classes/fxml/sample.fxml:14
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at grahps.Main.start(Main.java:22)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
... 1 more
Caused by: java.lang.NullPointerException
at grahps.Controller.<init>(Controller.java:31)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
... 17 more
Exception running application grahps.Main
Process finished with exit code 1
Also I changed onAction="drawChart" for onAction="chart" and now I can see highlighted communicate: "cannot set javafx.scene.control.LineChart to chart"
There are several bugs in the code, e.g.:
The //Parser Text Field -> double-block in the controller is executed too early => NullPointerException.
When calling controller.drawChart(stage) in the start-method controller equals null => NullPointerException.
In the drawChart-method in the controller series equals null, because chart equals null => NullPointerException.
A reference to the chart is missing in the controller. This has already been noted in the comments.
CategoryAxis is used as the type of the x-axis, although the x-data are numerical values.
Before fixing these bugs, the architecture should be improved (this will automatically fix some of the bugs):
Controller-class: Since the chart has to be initialized and then updated with every click on the button, the following changes in the controller would be useful:
Implement an initialize-method, in which the necessary initializations can be made.
Implement an updateChart-method, which is called when the button is clicked and which updates the chart.
Define a reference to the LineChart.
Define references to both axes.
Thus, the Controller-class looks as follows:
public class Controller {
#FXML
TextField factorA;
#FXML
TextField factorB;
#FXML
TextField factorC;
#FXML
TextField xMin;
#FXML
TextField xMax;
#FXML
Label label;
#FXML
Button button;
#FXML
LineChart<Number, Number> chart;
#FXML
NumberAxis xAxis;
#FXML
NumberAxis yAxis;
#FXML
public void updateChart() {/*ToDo*/}
public void initialize(){/*ToDo*/}
}
Main-class: In the start-method only the FXML needs to be loaded:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/sample.fxml"));
Scene scene = new Scene(root, 800, 800);
stage.setScene(scene);
stage.show();
}
FXML: The following changes should be made:
Change the ID of the LineChart to fx:id="chart"
Change the type of the x-axis of the LineChart to NumberAxis
Add onAction="#updateChart" to the button. This calls the updateChart-method when the button is clicked.
Define an ID for both axes (fx:id="xAxis" and fx:id="yAxis").
Remove all initializations with non-numeric characters (e.g. text="a=") for all text fields, otherwise there are problems with parsing (it makes more sense to use labels or watermarks, e.g. promptText="a").
Then, the FXML becomes:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="grahps.Controller">
<children>
<TextField fx:id="factorA" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="a" />
<TextField fx:id="factorB" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="37.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="b" />
<TextField fx:id="factorC" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="1.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="c" />
<TextField fx:id="xMin" prefHeight="47.0" prefWidth="120.0" AnchorPane.bottomAnchor="61.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" promptText="xMin" />
<TextField fx:id="xMax" prefHeight="47.0" prefWidth="120.0" AnchorPane.bottomAnchor="3.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" promptText="xMax" />
<Label fx:id="label" prefHeight="61.0" prefWidth="276.0" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="468.0" AnchorPane.rightAnchor="56.0" text="f(x)=" >
<font>
<Font size="18.0" />
</font>
</Label>
<LineChart fx:id="chart" prefHeight="598.0" prefWidth="800.0" title="Chart">
<xAxis>
<NumberAxis fx:id="xAxis" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
<Button fx:id="button" prefHeight="61.0" prefWidth="98.0" layoutX="317.0" layoutY="612.0" mnemonicParsing="false" text="Rysuj wykres" onAction="#updateChart" />
</children>
</AnchorPane>
With these changes, an empty chart is displayed when the application is started (since no initialization has yet been implemented). Clicking on the button has no effect (since no updating has yet been implemented).
Initialization:
public void initialize(){
initChartProperties();
initInputControls();
XYChart.Series<Number, Number> series = getSeries();
chart.getData().add(series);
}
The getSeries-method essentially contains the logic of the drawChart-method:
private XYChart.Series<Number, Number> getSeries() {
double xMax1 = Double.parseDouble(xMax.getText());
double xMin1 = Double.parseDouble(xMin.getText());
double a = Double.parseDouble(factorA.getText());
double b = Double.parseDouble(factorB.getText());
double c = Double.parseDouble(factorC.getText());
XYChart.Series<Number,Number> series = new XYChart.Series<Number, Number>();
series.setName("Chart");
String pattern;
if (a == 0 && c == 0) {
pattern = "f(x)=" + factorB.getText();
label.setText(pattern);
} else if (c == 0) {
pattern = "f(x)=" + factorA.getText() + "x+" + factorB.getText();
label.setText(pattern);
for (double i = xMin1; i <= xMax1; i++) {
double y = a * i + b;
series.getData().add(new Data<Number, Number>(i, y));
}
} else {
pattern = "f(x)=" + factorA.getText() + "x^2+" + factorB.getText() + "x+" + factorC.getText();
label.setText(pattern);
for (double i = xMin1; i < xMax1; i++) {
double y = a * i * i + b * i + c;
series.getData().add(new Data<Number, Number>(i, y));
}
}
return series;
}
The initInputControls-method initializes the input controls, e.g.:
private void initInputControls() {
xMax.setText("100.0");
xMin.setText("10.0");
factorA.setText("1.0");
factorB.setText("2.0");
factorC.setText("3.0");
}
The initChartProperties-method initializes the chart:
private void initChartProperties() {
chart.setAnimated(true);
xAxis.setLabel("X Label");
yAxis.setLabel("Y Label");
}
If you want to display an empty chart at startup, simply remove the last three lines in the initialize-method.
Updating: Updating simply deletes the old series and adds the new series to the chart:
#FXML
public void updateChart() {
XYChart.Series<Number, Number> series = getSeries();
chart.getData().clear();
chart.getData().add(series);
}
After these changes, the application behaves as expected. The figure on the left shows the application after startup, the figure on the right after updating the input values.
Some things can still be improved, e.g. the application does not scale properly if the window size is changed. In addition, the validation of the input fields is missing. Chart animation can be disabled in the initChartProperties-method.
UPDATE:
If no symbols are to be displayed, add in the initChartProperties-method:
chart.setCreateSymbols(false);
The result is:
I am using GluonHQ Maps 1.0.3 in JavaFX and I have a problem painting nodes on the map.
I have a class GridMapController which has a MapView called gridMap.
I also have a class GridPaintLayer that extends the MapLayer and where all paintings are done. The GridPaintLayer is added to the GridMapController's gridMap with the method gridMap.addLayer(gridPaintLayer). This class (GridPaintLayer) has a method addNode(NodeInputModel) to which I pass a node (that contains lat/lon values) that shall be painted. The mentioned gridMap (MapView) can be right-clicked, a popup menu opens where I can choose "Create Node". This calls a method createNodeItemClicked() which is implemented in the GridPaintLayer (see below). When I click the "Create Node" menu item, everything works fine and a point appears on the map.
In another part of the programm I have an observable object that notifies its observers whenever a node is added. The notification message is handled in the GridMapController which then calls the addNode() method from the GridPaintLayer. But now, when I want the GridPaintLayer's baseMap to calculate the the points position on the scene, the method baseMap.getMapPoint(double lat, double lon) returns null because the baseMap's scene is null. So the node cannot be painted.
This happens i.e. when I want to create a node at another part of the application. The observable notifies the GridMapController (among others) correctly but then the problem appears as described above.
The methods below are both implemented in the GridPaintLayer class.
public void addNode(NodeInputModel nodeInputModel) {
Circle circle = new Circle(7, Color.RED);
circle.setCursor(Cursor.HAND);
circle.setVisible(true);
paintedNodes.put(nodeInputModel, circle);
Point2D mapPoint = baseMap.getMapPoint(nodeInputModel.getLat(), nodeInputModel.getLon());
if(mapPoint != null) {
circle.setTranslateX(mapPoint.getX());
circle.setTranslateY(mapPoint.getY());
} else {
System.out.println("Could not calculate node position, baseMap scene = null: " + (baseMap.getScene() == null));
}
AtomicReference<Double> orgSceneX = new AtomicReference<>(0d);
AtomicReference<Double> orgSceneY = new AtomicReference<>(0d);
circle.setOnMousePressed(mousePressedEvent -> {...});
circle.setonMouseClicked(mouseEvent -> {...});
this.getChildren().add(circle);
this.markDirty();
}
public void createNodeItemClicked(MouseEvent mouseEvent) {
// Convert mouseEvent position to geo position
MapPoint mapPoint = baseMap.getMapPosition(mouseEvent.getX(), mouseEvent.getY());
LatLon latLon = new LatLon(mapPoint.getLatitude(), mapPoint.getLongitude());
Point geoPosition = Utils.latlonToPoint(latLon);
// Create NodeInputModel
NodeInputModel nodeInputModel = new NodeInputModel();
nodeInputModel.setGeoPosition(geoPosition);
// TODO: set the other parameters
// Add node to Map
addNode(nodeInputModel);
// Send update message to GridModel
UpdateMessage updateMessage =
new UpdateMessage(null, Arrays.asList(nodeInputModel), UpdateMessage.UpdateType.ADD);
gridMapController.sendUpdateMessage(updateMessage);
}
I have tried several things to "reactivate" the scene but nothing worked. Is there a way to do that properly?
I hope I have explained everything clearly. If not, please feel free to ask.
Edit:
I am using multiple FXML files. The root FXML (MainView.fxml):
<VBox xmlns="http://javafx.com/javafx/8.0.172-ea"
xmlns:fx="http://javafx.com/fxml/1"
fx:id="main"
fx:controller="edu.ie3.controller.main.MainController">
<fx:include fx:id="io" source="IoView.fxml"/>
<fx:include fx:id="tool" source="ToolView.fxml"/>
<HBox prefHeight="${main.height}">
<!-- vertical button bar for grid info -->
<ButtonBar fx:id="leftButtonBar" rotate="-90">
<buttons>
<Button fx:id="gridInfoButton" text="Grid Info"/>
</buttons>
</ButtonBar>
<SplitPane fx:id="splitPane" prefWidth="${main.width}">
<fx:include fx:id="gridTab" source="GridTabView.fxml"/>
</SplitPane>
</HBox>
<fx:define>
<fx:include fx:id="gridInfo" source="GridInfoView.fxml"/>
<fx:include fx:id="gridMap" source="GridMapView.fxml"/>
<fx:include fx:id="gridSchema" source="GridSchemaView.fxml"/>
</fx:define>
</VBox>
The Splitpane which contains the GridMapView:
<TabPane
xmlns="http://javafx.com/javafx/8.0.172-ea"
xmlns:fx="http://javafx.com/fxml/1"
tabClosingPolicy="UNAVAILABLE"
fx:id="gridTabPane"
fx:controller="edu.ie3.controller.gridTab.GridTabController">
<Tab fx:id="gridMapTab" text="Map View">
<fx:include fx:id="gridMapLayer" source="GridMapView.fxml"/>
</Tab>
<Tab fx:id="gridSchemaTab" text="Schema View">
<fx:include fx:id="gridSchema" source="GridSchemaView.fxml"/>
</Tab>
</TabPane>
The GridMapView.fxml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import com.gluonhq.maps.MapView?>
<MapView xmlns="http://javafx.com/javafx/8.0.172-ea"
xmlns:fx="http://javafx.com/fxml/1"
fx:id="gridMap"
fx:controller="edu.ie3.controller.gridTab.gridMap.GridMapController">
</MapView>
I have already tried commenting some statements out to avoid double including the GridMapView but nothing worked.
I solved it.
The problem was that I included the GridMapController once in the MainController and once in the GridTabController. I removed the fx:include from the MainController and now the GridMapController only initializes once.
Nevertheless thank you very much, José Pereda, you brought me on the right way.
I am using JFoenix library elements. I have a simple login interface.
I use JFXTextfields with a LabelFloat option. When the text field is empty the prompt text weight is normal. But when the text field is unfocused and non-empty I need the text weight to be changed to be bold and its size increased, I also need the label Float to stay with th initial font size and a normal weight.
This is my css:
.input-field-blue {
-fx-prompt-text-fill: #a1a1a1;
-jfx-unfocus-color : #bcbcbc;
-fx-background-repeat : no-repeat;
-fx-background-position: right center;
-fx-padding : 0 -8 4 0;
-fx-text-fill : #3fc9ee;
}
.input-field-blue:filled {
-fx-font-size: 14;
-fx-font-weight: bold;
-fx-text-fill : #00adde;
-jfx-unfocus-color : #00adde;
}
This is my fxml file:
<AnchorPane fx:id="backPanel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="675.0" prefWidth="1200.0" stylesheets="#../sample/sylesheet.css"
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="View.LoginView">
<children>
<Pane fx:id="frontPanel" layoutX="396.0" layoutY="164.0" prefHeight="351.0" prefWidth="450.0"
style="-fx-background-color: #ffffff;" styleClass="front-panel">
<children>
<Label fx:id="hiLabel" layoutX="166.0" layoutY="43.0" styleClass="title-blue" text="Bonjour !"
textAlignment="CENTER"/>
<Label fx:id="highlightLabel" layoutX="115.0" layoutY="99.0" styleClass="highlight"
text="Veuillez vous authentifier s’il vous plaît"/>
<JFXPasswordField fx:id="pwd" focusColor="#3fc9ee" labelFloat="true" layoutX="77.0" layoutY="207.0"
prefHeight="26.0" prefWidth="297.0" promptText="Mot de passe" unFocusColor="#bcbcbc">
<styleClass>
<String fx:value="input-field-blue"/>
</styleClass>
</JFXPasswordField>
<JFXButton fx:id="authButton" layoutX="155.0" layoutY="277.0" prefHeight="38.0" prefWidth="140.0"
styleClass="button-blue" text="S'authentifier" textAlignment="CENTER"/>
<JFXTextField fx:id="userName" focusColor="#3fc9ee" labelFloat="true" layoutX="76.0" layoutY="147.0"
prefHeight="26.0" prefWidth="297.0" promptText="Nom d'utilisateur" unFocusColor="#bcbcbc">
<styleClass>
<String fx:value="input-field-blue"/>
</styleClass>
</JFXTextField>
</children>
</Pane>
</children>
<styleClass>
<String fx:value="back-panel"/>
<String fx:value="back-panel-blue"/>
</styleClass>
</AnchorPane>
I have this interface:
But what I want:
Is there anyway to do that ?
You should add a custom class to your css and toggle this if the focused state or the length state changes.
Add the following to your css file:
.input-field-blue.not-empty-and-unfocused {
-fx-font-weight: bold;
}
Now in your controller add a checkState method which adds or removes the not-empty-and-unfocused class. Add listeners to lengthProperty and focusedProperty in the initialize method. Here is the important part for your controller:
public class LoginView implements Initializable {
#FXML
public JFXPasswordField pwd;
#FXML
public JFXTextField userName;
#Override
public void initialize(URL location, ResourceBundle resources) {
userName.lengthProperty().addListener((observable, oldValue, newValue) -> checkState(userName));
userName.focusedProperty().addListener((observable, oldValue, newValue) -> checkState(userName));
pwd.lengthProperty().addListener((observable, oldValue, newValue) -> checkState(pwd));
pwd.focusedProperty().addListener((observable, oldValue, newValue) -> checkState(pwd));
}
private void checkState(TextField field) {
if (field.isFocused() || field.getLength() == 0) {
field.getStyleClass().remove("not-empty-and-unfocused");
} else {
field.getStyleClass().add("not-empty-and-unfocused");
}
}
}
EDIT:
Another option you may want to consider is using a custom PseudoClass. Therefore you have to change your css to the following:
.input-field-blue:not-empty-and-unfocused {
-fx-font-weight: bold;
}
In your Controller you create an PseudoClass and change the checkState method a bit:
private static final PseudoClass NOT_EMPTY_AND_UNFOCUSED = PseudoClass.getPseudoClass("not-empty-and-unfocused");
private void checkState(TextField field) {
field.pseudoClassStateChanged(NOT_EMPTY_AND_UNFOCUSED, !(field.isFocused() || field.getLength() == 0));
}
I am trying to change the data of the StackedAreaChart dynamically. So I have created an fxml file and a controller for it.
fig.fxml
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="495.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.gollahalli.test.controller">
<children>
<StackedAreaChart fx:id="graph" layoutX="24.0" layoutY="95.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</StackedAreaChart>
<TextField fx:id="number" layoutX="57.0" layoutY="39.0" />
<Button fx:id="button" layoutX="330.0" layoutY="39.0" mnemonicParsing="false" text="Button" />
</children>
</AnchorPane>
Controller.java
public class controller {
#FXML
private Button button;
#FXML
private TextField number;
#FXML
private StackedAreaChart<Number, Number> graph;
public void initialize(){
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
button.setOnAction(event -> {
int number1 = Integer.parseInt(number.getText());
System.out.println(number1);
for (int i = 0; i < number1; i++) {
series.getData().addAll(new XYChart.Data(i, i));
}
graph.getData().add(series);
});
}
}
When I enter a number say 100, I am able to get the graph correctly, when I change the data say 101 and click on the button I get an error as Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Duplicate series added.
I do know that to overcome this I would have to use ObservableList, but I am not sure how to use it.
Now the question is, how should I change/refresh the data every time I click on the button?
The exception message is clear. To avoid it add series only once:
public void initialize(){
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
// add series only once at init
graph.getData().add(series);
button.setOnAction(event -> {
int number1 = Integer.parseInt(number.getText());
System.out.println(number1);
// clear current data
series.getData().clear();
// add new data
for (int i = 0; i < number1; i++) {
series.getData().add(new XYChart.Data(i, i));
}
});
}
I'm writing a custom control which displays an error icon and a message in a tooltip if the validation in a form fails. My version without the custom control looks like this:
<HBox>
<TextField fx:id="name"></TextField>
<Label fx:id="error" focusTraversable="false" visible="false">
<graphic>
<ImageView fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true"/>
</graphic>
<tooltip>
<Tooltip fx:id="errorTooltip"/>
</tooltip>
</Label>
</HBox>
The result is this:
My efforts to create a custom control lead to this:
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml">
<children/>
<Label fx:id="error" focusTraversable="false" visible="false">
<graphic>
<ImageView fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true"/>
</graphic>
<tooltip>
<Tooltip fx:id="errorToolTip"/>
</tooltip>
</Label>
</fx:root>
This is the code behind the fxml:
package control;
[imports omitted for brevity]
#DefaultProperty(value = "children")
public final class ValidatedControl extends HBox implements Initializable {
#FXML
private Label error;
#FXML
private Tooltip errorToolTip;
private StringProperty errorToolTipProperty;
public ValidatedControl() {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ValidatedControl.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void setErrorToolTip(final String errorToolTip) {
this.getErrorToolTipProperty().setValue(errorToolTip);
}
public String getErrorToolTip() {
return this.getErrorToolTipProperty().getValueSafe();
}
#Override
public void initialize(final URL location, final ResourceBundle resources) {
this.errorToolTip.textProperty().bind(this.getErrorToolTipProperty());
this.error.visibleProperty().bind(this.getErrorToolTipProperty().isNotEmpty());
}
public StringProperty getErrorToolTipProperty() {
if (this.errorToolTipProperty == null) {
this.errorToolTipProperty = new SimpleStringProperty();
}
return this.errorToolTipProperty;
}
}
I can use the control in fxml but the child component I add is always the last child which means the error icon is displayed to its left.
My control is used like this:
<ValidatedControl>
<TextField>
</TextField>
</ValidatedControl>
How do I get it to display the icon on the right side?
Now I do understand your problem. This might not fix your problem when you add your ValidatedControl in FXML, but when you do it programmatically try this:
ValidatedControl vc = new ValidatedControl();
TextField textField = new TextField();
vc.getChildren().add(textField);
textField.toBack();
Another way would be to go ItachiUchiha's way and add a Pane in your FXML as first child. But instead of overwriting the getChildren() method, write a new Method addNode(Node n) and add the node to the Pane.
forget about my first answer ;)