title: JavaFX custom chart class - how to bind a node's layoutX and layoutY properties to the display positions of a NumberAxis?
body: <p>I'm writing a rudimentary <a href="https://en.wikipedia.org/wiki/Candlestick_chart" rel="nofollow noreferrer">Candlestick</a> chart class in which the candlesticks are created as <code>Regions</code> and are plotted by setting their <code>layoutX</code> and <code>layoutY</code> values to the <code>getDisplayPosition()</code> of the relevant axis.</p>
  <p>For example, to plot a candlestick at value 3 on an X axis, I do this:</p>
  <p><code>candlestick.setLayoutX(xAxis.getDisplayPosition(3));</code></p>
  <p>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 <code>ChangeListener</code>s for resize events and <code>Button.setOnAction()</code>s for zooming.</p>
  <p>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 &quot;displayPositionProperty&quot; (or similar) for a <code>NumberAxis</code>.</p>
  <p>Is it possible to do this?  Which <code>NumberAxis</code> property would I bind to?  ie.</p>
  <p><code>candlestick.layoutXProperty().bind(xAxis.WHICH_PROPERTY?);</code></p>
  <p>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.</p>
  <p>I've experimented with scaling the candlesticks to the axes' scale but can't use that approach because scaling a <code>Region</code> affects its border width.  For certain types of candlesticks, that can change its meaning.</p>
  <p>I've also played with the Ensemble candlestick demo chart.  It was useful in giving me a start but is too simplistic for my needs.</p>
  <p>Here's a MVCE that demonstrates my approach.  Any guidance re binding would be very much appreciated.</p>
  <p>I'm using OpenJFX 17.</p>
  <pre><code>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(&quot;-fx-background-color: black;&quot;);
  
          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) -&gt; {
              plotPoint(point, pointXValue, xAxis);
          });
  
          stage.maximizedProperty().addListener((obs, oldVal, newVal) -&gt; {
              plotPoint(point, pointXValue, xAxis);
          });        
  
          //Handle zooming (hard-coded upper and lower bounds for the 
          //sake of simplicity)
          Button btnZoomIn = new Button(&quot;Zoom in&quot;);
          btnZoomIn.setOnAction((event) -&gt; {
              xAxis.setLowerBound(2D);
              xAxis.setUpperBound(8D);
              xAxis.layout();
              plotPoint(point, pointXValue, xAxis);
          });
  
          Button btnZoomOut = new Button(&quot;Zoom out&quot;);
          btnZoomOut.setOnAction((event) -&gt; {
              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(&quot;Test bind layoutX&quot;);
          stage.setWidth(400D);
          stage.setHeight(200D);
          stage.show();
  
      }
  
      private void plotPoint(Region region, double axisPos, NumberAxis axis) {
  
          Platform.runLater(() -&gt; {
              double posX = axis.getDisplayPosition(axisPos);
              region.setLayoutX(posX);
              region.setLayoutY(80D);
          });
  
      }
  
      public static void main(String args[]){
         launch(args);
      }
  
  }
  </code></pre>