JavaFX 自定义图表 class - 如何将节点的 layoutX 和 layoutY 属性绑定到 NumberAxis 的显示位置?
JavaFX custom chart class - how to bind a node's layoutX and layoutY properties to the display positions of a NumberAxis?
我正在写一个基本的 Candlestick 图表 class,其中烛台被创建为 Regions
并通过设置它们的 layoutX
和 layoutY
值到相关轴的 getDisplayPosition()
。
例如,要在 X 轴上绘制值为 3 的烛台,我这样做:
candlestick.setLayoutX(xAxis.getDisplayPosition(3));
调整舞台大小时或放大或缩小轴时,必须重置烛台的布局值,以便正确呈现图表。我目前正在通过 ChangeListener
s 处理调整大小事件和 Button.setOnAction()
s 进行缩放。
但是,我宁愿将烛台的布局属性绑定到坐标轴的显示位置,而不是 set/reset 布局值,但是找不到 NumberAxis
.
这可以吗?我会绑定到哪个 NumberAxis
属性?即
candlestick.layoutXProperty().bind(xAxis.WHICH_PROPERTY?);
另外,绑定属性会比重置布局位置更有效吗?一些图表可能有数千个烛台,但在我弄清楚如何对绑定进行编码之前,我无法测试资源使用情况。
我已经尝试将烛台缩放到坐标轴的比例,但无法使用该方法,因为缩放 Region
会影响其边框宽度。对于某些类型的烛台,这可能会改变其含义。
我还玩过 Ensemble 烛台演示图表。它对我的入门很有用,但对我的需求来说太简单了。
这是一个演示我的方法的 MVCE。任何重新绑定的指导将不胜感激。
我正在使用 OpenJFX 17。
package test023;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test023 extends Application {
@Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
plotPoint(point, pointXValue, xAxis);
//*****************************************************************
//Can the listeners and button.setOnActions be replaced by binding
//the point's layout value to the axis display position?
//*****************************************************************
//Handle resize events
pChart.widthProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
stage.maximizedProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void plotPoint(Region region, double axisPos, NumberAxis axis) {
Platform.runLater(() -> {
double posX = axis.getDisplayPosition(axisPos);
region.setLayoutX(posX);
region.setLayoutY(80D);
});
}
public static void main(String args[]){
launch(args);
}
}
像这样的东西会起作用
@Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
point.setLayoutY(80D);
point.layoutXProperty().bind(Bindings.createDoubleBinding(()-> {
return xAxis.getDisplayPosition(pointXValue);
}, xAxis.lowerBoundProperty(), xAxis.upperBoundProperty(), pChart.widthProperty()));
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
这将创建一个自定义双重绑定,其中包含一个函数,每次更改依赖项时都会计算绑定的值,请参阅 createDoubleBinding 了解详细信息。
+1 为@LukasOwen 的回答,它回答了您与绑定相关的实际问题。
但正如您所知,每个问题都有不止一种方法,考虑到可扩展性(添加很多点)和太多绑定(对于每个点),我建议我的方法。
这种方法的关键是:
- 您将所有点数及其节点添加到地图中。
- 每次渲染 xAxis 时,您都会更新所有点的位置。因此,如果您调整大小、更改范围或最大化 window.
,这将隐式完成
下面是方法示例:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
public class Test023 extends Application {
Map<Double, Region> plotPoints = new HashMap<>();
double yOffset = 80D;
Pane pChart;
@Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
xAxis.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if(!needsLayout) {
plotPoints.forEach((num, point) -> {
double posX = xAxis.getDisplayPosition(num);
point.setLayoutX(posX);
point.setLayoutY(yOffset);
});
}
});
pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane root = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
addPoint(3D, "black");
addPoint(4D, "red");
addPoint(5D, "blue");
//Handle zooming (hard-coded upper and lower bounds for the sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
});
root.setCenter(vb);
root.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(root));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void addPoint(double num, String color) {
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: " + color);
plotPoints.put(num, point);
pChart.getChildren().add(point);
}
public static void main(String args[]) {
launch(args);
}
}
我正在写一个基本的 Candlestick 图表 class,其中烛台被创建为 Regions
并通过设置它们的 layoutX
和 layoutY
值到相关轴的 getDisplayPosition()
。
例如,要在 X 轴上绘制值为 3 的烛台,我这样做:
candlestick.setLayoutX(xAxis.getDisplayPosition(3));
调整舞台大小时或放大或缩小轴时,必须重置烛台的布局值,以便正确呈现图表。我目前正在通过 ChangeListener
s 处理调整大小事件和 Button.setOnAction()
s 进行缩放。
但是,我宁愿将烛台的布局属性绑定到坐标轴的显示位置,而不是 set/reset 布局值,但是找不到 NumberAxis
.
这可以吗?我会绑定到哪个 NumberAxis
属性?即
candlestick.layoutXProperty().bind(xAxis.WHICH_PROPERTY?);
另外,绑定属性会比重置布局位置更有效吗?一些图表可能有数千个烛台,但在我弄清楚如何对绑定进行编码之前,我无法测试资源使用情况。
我已经尝试将烛台缩放到坐标轴的比例,但无法使用该方法,因为缩放 Region
会影响其边框宽度。对于某些类型的烛台,这可能会改变其含义。
我还玩过 Ensemble 烛台演示图表。它对我的入门很有用,但对我的需求来说太简单了。
这是一个演示我的方法的 MVCE。任何重新绑定的指导将不胜感激。
我正在使用 OpenJFX 17。
package test023;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Test023 extends Application {
@Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
plotPoint(point, pointXValue, xAxis);
//*****************************************************************
//Can the listeners and button.setOnActions be replaced by binding
//the point's layout value to the axis display position?
//*****************************************************************
//Handle resize events
pChart.widthProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
stage.maximizedProperty().addListener((obs, oldVal, newVal) -> {
plotPoint(point, pointXValue, xAxis);
});
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
plotPoint(point, pointXValue, xAxis);
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void plotPoint(Region region, double axisPos, NumberAxis axis) {
Platform.runLater(() -> {
double posX = axis.getDisplayPosition(axisPos);
region.setLayoutX(posX);
region.setLayoutY(80D);
});
}
public static void main(String args[]){
launch(args);
}
}
像这样的东西会起作用
@Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
Pane pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane bp = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: black;");
pChart.getChildren().add(point);
//Plot the point in its initial position (value 3 on the axis)
double pointXValue = 3D;
point.setLayoutY(80D);
point.layoutXProperty().bind(Bindings.createDoubleBinding(()-> {
return xAxis.getDisplayPosition(pointXValue);
}, xAxis.lowerBoundProperty(), xAxis.upperBoundProperty(), pChart.widthProperty()));
//Handle zooming (hard-coded upper and lower bounds for the
//sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
xAxis.layout();
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
xAxis.layout();
});
bp.setCenter(vb);
bp.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(bp));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
这将创建一个自定义双重绑定,其中包含一个函数,每次更改依赖项时都会计算绑定的值,请参阅 createDoubleBinding 了解详细信息。
+1 为@LukasOwen 的回答,它回答了您与绑定相关的实际问题。
但正如您所知,每个问题都有不止一种方法,考虑到可扩展性(添加很多点)和太多绑定(对于每个点),我建议我的方法。
这种方法的关键是:
- 您将所有点数及其节点添加到地图中。
- 每次渲染 xAxis 时,您都会更新所有点的位置。因此,如果您调整大小、更改范围或最大化 window. ,这将隐式完成
下面是方法示例:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
public class Test023 extends Application {
Map<Double, Region> plotPoints = new HashMap<>();
double yOffset = 80D;
Pane pChart;
@Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis(0D, 10D, 1D);
xAxis.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if(!needsLayout) {
plotPoints.forEach((num, point) -> {
double posX = xAxis.getDisplayPosition(num);
point.setLayoutX(posX);
point.setLayoutY(yOffset);
});
}
});
pChart = new Pane();
Pane pAxis = new Pane();
VBox vb = new VBox();
BorderPane root = new BorderPane();
pChart.setPrefHeight(100D);
pAxis.getChildren().add(xAxis);
xAxis.prefWidthProperty().bind(pAxis.widthProperty());
xAxis.setAnimated(false);
vb.setPadding(new Insets(10D));
vb.getChildren().addAll(pChart, pAxis);
addPoint(3D, "black");
addPoint(4D, "red");
addPoint(5D, "blue");
//Handle zooming (hard-coded upper and lower bounds for the sake of simplicity)
Button btnZoomIn = new Button("Zoom in");
btnZoomIn.setOnAction((event) -> {
xAxis.setLowerBound(2D);
xAxis.setUpperBound(8D);
});
Button btnZoomOut = new Button("Zoom out");
btnZoomOut.setOnAction((event) -> {
xAxis.setLowerBound(0D);
xAxis.setUpperBound(10D);
});
root.setCenter(vb);
root.setTop(new HBox(btnZoomIn, btnZoomOut));
stage.setScene(new Scene(root));
stage.setTitle("Test bind layoutX");
stage.setWidth(400D);
stage.setHeight(200D);
stage.show();
}
private void addPoint(double num, String color) {
Region point = new Region();
point.setPrefSize(5D, 5D);
point.setStyle("-fx-background-color: " + color);
plotPoints.put(num, point);
pChart.getChildren().add(point);
}
public static void main(String args[]) {
launch(args);
}
}