简体   繁体   English

JavaFX 自定义图表类 - 如何将节点的 layoutX 和 layoutY 属性绑定到 NumberAxis 的显示位置?

[英]JavaFX custom chart class - how to bind a node's layoutX and layoutY properties to the display positions of a NumberAxis?

I'm writing a rudimentary Candlestick chart class in which the candlesticks are created as Regions and are plotted by setting their layoutX and layoutY values to the getDisplayPosition() of the relevant axis.我正在编写一个基本的烛台图表类,其中烛台被创建为Regions ,并通过将它们的layoutXlayoutY值设置为相关轴的getDisplayPosition()来绘制。

For example, to plot a candlestick at value 3 on an X axis, I do this:例如,要在 X 轴上绘制值为 3 的烛台,我这样做:

candlestick.setLayoutX(xAxis.getDisplayPosition(3));

When the stage resizes or when the axes are zoomed in or out, the candlesticks' layout values have to be reset so that the chart renders correctly.当舞台调整大小或轴放大或缩小时,必须重置烛台的布局值,以便图表正确呈现。 I'm currently handling this via ChangeListener s for resize events and Button.setOnAction() s for zooming.我目前正在通过ChangeListener s 处理调整大小事件和Button.setOnAction() s 进行缩放。

However, I'd rather bind the candlesticks' layout properties to the axes' display positions than set/reset the layout values, but can't find a "displayPositionProperty" (or similar) for a NumberAxis .但是,我宁愿将烛台的布局属性绑定到轴的显示位置,而不是设置/重置布局值,但找不到NumberAxis的“displayPositionProperty”(或类似的)。

Is it possible to do this?是否有可能做到这一点? Which NumberAxis property would I bind to?我将绑定到哪个NumberAxis属性? ie. IE。

candlestick.layoutXProperty().bind(xAxis.WHICH_PROPERTY?);

Also, would binding the properties be more efficient than resetting layout positions?另外,绑定属性会比重置布局位置更有效吗? Some of the charts could potentially have thousands of candlesticks but I can't test resource usage until I figure out how to code the bind.有些图表可能有数千个烛台,但在我弄清楚如何对绑定进行编码之前,我无法测试资源使用情况。

I've experimented with scaling the candlesticks to the axes' scale but can't use that approach because scaling a Region affects its border width.我已经尝试将烛台缩放到轴的比例,但不能使用这种方法,因为缩放Region会影响其边框宽度。 For certain types of candlesticks, that can change its meaning.对于某些类型的烛台,这可能会改变其含义。

I've also played with the Ensemble candlestick demo chart.我还使用了 Ensemble 烛台演示图表。 It was useful in giving me a start but is too simplistic for my needs.它对我的开始很有用,但对于我的需求来说太简单了。

Here's a MVCE that demonstrates my approach.这是一个 MVCE,它展示了我的方法。 Any guidance re binding would be very much appreciated.任何重新绑定的指导将不胜感激。

I'm using OpenJFX 17.我正在使用 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);
    }

}

Something like this would work像这样的东西会起作用

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

}

This creates a custom double binding with a function that calculates the value of the binding every time the dependencies are changed, see createDoubleBinding​ for more info.这将创建一个定制的双用计算的每一个依赖关系发生变化时绑定,请参阅值的函数结合createDoubleBinding获取更多信息。

+1 for @LukasOwen answer which actually you actual question related to bindings. +1 @LukasOwen 答案实际上是您与绑定相关的实际问题。

But as you are aware that every problem has more than one approach, I am suggesting mine, considering the scalability (adding many points) and too many bindings (for every point).但正如您所知,每个问题都有不止一种方法,我建议我考虑可扩展性(添加很多点)和太多绑定(对于每个点)。

The key things in this approach are:这种方法的关键是:

  • You add all your points numbers and its node to a map.您将所有点数及其节点添加到地图中。
  • Every time the xAxis is rendered, you update the all the points position.每次渲染 xAxis 时,都会更新所有点的位置。 So this will be implicitly done if you resize, change range, or maximize the window.因此,如果您调整大小、更改范围或最大化窗口,这将隐式完成。

Below is the example of the approach:以下是该方法的示例:

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

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM