0

I am trying to create 2 similar dimension shapes (rectangles) one below the other with scaling effect. But however when the rectangles are scaled, the 2 shapes overlap on each other. This behavior is not expected.

I would expect the rectangles to be Scaled (Zoomed) and in addition the 2 rectangles should be one below the other without any gaps in-between. How can this be achieved ?

One option is to provide scaling on the group. But it would make the text inside also scaled which is not required. Another option is using VBox, but rather I want to achieve this functionality on a Pane.

Someone suggest me a solution here

The code with which i tried to achieve is below

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.input.ScrollEvent;
import javafx.stage.Stage;


@SuppressWarnings("restriction")
public class TestRectScaling extends Application {

/**
 * @param args
 */
public static void main(final String[] args) {
  launch(args);
}

/**
 * {@inheritDoc}
 */
@Override
public void start(final Stage primaryStage) throws Exception {
  javafx.scene.layout.Pane p = new javafx.scene.layout.Pane();
  javafx.scene.Group g = new javafx.scene.Group(p);
  javafx.scene.control.ScrollPane sp = new javafx.scene.control.ScrollPane(g);

  javafx.scene.layout.StackPane stackPane1 = new javafx.scene.layout.StackPane();

  javafx.scene.shape.Rectangle rect1 = new javafx.scene.shape.Rectangle();
  stackPane1.getChildren().add(rect1);
  rect1.setWidth(100);
  rect1.setHeight(100);
  rect1.setFill(javafx.scene.paint.Color.BLUE);
  javafx.scene.text.Text text1 = new javafx.scene.text.Text("This is sample Text Rect 1");
  text1.setWrappingWidth(30);
  stackPane1.getChildren().add(text1);

  javafx.scene.layout.StackPane stackPane2 = new javafx.scene.layout.StackPane();
  javafx.scene.shape.Rectangle rect2 = new javafx.scene.shape.Rectangle();
  rect2.setWidth(100);
  rect2.setHeight(100);
  rect2.setFill(javafx.scene.paint.Color.BLUEVIOLET);
  javafx.scene.text.Text text2 = new javafx.scene.text.Text("This is sample Text Rect 2");
  text2.setWrappingWidth(30);
  stackPane2.getChildren().add(rect2);
  stackPane2.getChildren().add(text2);

  stackPane1.setLayoutX(30);
  stackPane1.setLayoutY(20);

  stackPane2.setLayoutX(30);
  stackPane2.setLayoutY(120);

  g.getChildren().add(stackPane1);
  g.getChildren().add(stackPane2);


  sp.addEventFilter(javafx.scene.input.ScrollEvent.ANY, new EventHandler<ScrollEvent>() {

    @Override
    public void handle(final ScrollEvent event) {
      double scaleDelta = 1.3d;
      double scaleFactor = 0;
      double deltaY = event.getDeltaY();
      if (deltaY < 0) {
        scaleFactor = 1 / scaleDelta;
      }
      else {
        scaleFactor = scaleDelta;
      }
      rect1.setScaleY(rect1.getScaleY() * scaleFactor);
      rect2.setScaleY(rect2.getScaleY() * scaleFactor);
    }
  });

  javafx.scene.Scene s = new javafx.scene.Scene(sp);
  primaryStage.setScene(s);
  primaryStage.show();
}
}

Let me help with an image

enter image description here

So the main intention is to scale the rectangles without altering the distance between the 2 of them. Which basically means i need a way to calculate the new Y co-ordinates after Scaling them.

James_D
  • 201,275
  • 16
  • 291
  • 322
Deepak
  • 3
  • 2
  • It's unclear why using a `VBox` doesn't do what you want. What do you mean by "I want to achieve this functionality on a `Pane`?" – James_D Feb 08 '21 at 15:02
  • I am trying to come up with an application which is made up of multiple columns and each columns contain numerous rectangles. And there is a relationship between the rectangles present across the columns and within the columns too. Hence if a VBox is used, then creating such a relationship between the rectangles would be difficult. As i am new to Javafx, may be my understanding is not perfect. but however It would be helpful , if you can suggest me an alternative of Pane or a solution within the Pane for arranging the rectangles – Deepak Feb 08 '21 at 15:38
  • I don't really understand your requirements. – James_D Feb 08 '21 at 15:45
  • I have put in the actual and expected outcome image in the query. So after scaling, we need to figure out the new Y co-ordinates for the 2nd rectangle inorder to place it on the Pane and without altering the space between the 2 rectangles. – Deepak Feb 08 '21 at 16:13
  • Why don't you just change the height, instead of scaling? Then the new y value for the second rectangle should be trivial (y-value of first rectangle, plus height, plus whatever gap you want). – James_D Feb 08 '21 at 16:43
  • That said, this just looks wrong. Why not use a label instead of text+rectangle. Then you can style the labels to set the background color, set the alignment (which positions the text in the label), set the preferred size, and use a vbox. – James_D Feb 08 '21 at 16:44

2 Answers2

0

Irrespective of your discussion about the implementation, if you are still looking for a solution with your current approach, you need to include the below listeners to your rectangles boundsInParent property.

The idea is, when you scale a node, the boundsInParent get updated. And if you listen to that property, you can update the heights of the stackPanes and layoutY of second stackPane.

double gap = 15.0;
rect1.boundsInParentProperty().addListener((obs, old, bounds) -> {
    stackPane1.setPrefHeight(bounds.getHeight());
    stackPane2.setLayoutY(stackPane1.getLayoutY() + bounds.getHeight() + gap);
});

rect2.boundsInParentProperty().addListener((obs, old, val) -> {
    stackPane2.setPrefHeight(val.getHeight());
});

Update::

Below is the demo to limit the scaling for the max/min heights.

import javafx.application.Application;
import javafx.stage.Stage;

public class TestRectScaling extends Application {

    double defaultHeight = 100.0;
    double maxHeight = 400.0;
    double gap = 15.0;

    public static void main(final String[] args) {
        launch(args);
    }

    @Override
    public void start(final Stage primaryStage) throws Exception {
        javafx.scene.layout.Pane p = new javafx.scene.layout.Pane();
        javafx.scene.Group g = new javafx.scene.Group(p);
        javafx.scene.control.ScrollPane sp = new javafx.scene.control.ScrollPane(g);

        javafx.scene.layout.StackPane stackPane1 = new javafx.scene.layout.StackPane();

        javafx.scene.shape.Rectangle rect1 = new javafx.scene.shape.Rectangle();
        stackPane1.getChildren().add(rect1);
        rect1.setWidth(100);
        rect1.setHeight(defaultHeight);
        rect1.setFill(javafx.scene.paint.Color.BLUE);
        javafx.scene.text.Text text1 = new javafx.scene.text.Text("This is sample Text Rect 1");
        text1.setWrappingWidth(30);
        stackPane1.getChildren().add(text1);

        javafx.scene.layout.StackPane stackPane2 = new javafx.scene.layout.StackPane();
        javafx.scene.shape.Rectangle rect2 = new javafx.scene.shape.Rectangle();
        rect2.setWidth(100);
        rect2.setHeight(defaultHeight);
        rect2.setFill(javafx.scene.paint.Color.BLUEVIOLET);
        javafx.scene.text.Text text2 = new javafx.scene.text.Text("This is sample Text Rect 2");
        text2.setWrappingWidth(30);
        stackPane2.getChildren().add(rect2);
        stackPane2.getChildren().add(text2);

        rect1.boundsInParentProperty().addListener((obs, old, bounds) -> {
            stackPane1.setPrefHeight(bounds.getHeight());
            stackPane2.setLayoutY(stackPane1.getLayoutY() + bounds.getHeight() + gap);
        });

        rect2.boundsInParentProperty().addListener((obs, old, val) -> {
            stackPane2.setPrefHeight(val.getHeight());
        });

        stackPane1.setLayoutX(30);
        stackPane1.setLayoutY(20);

        stackPane2.setLayoutX(30);
        stackPane2.setLayoutY(120);

        g.getChildren().add(stackPane1);
        g.getChildren().add(stackPane2);

        sp.addEventFilter(javafx.scene.input.ScrollEvent.ANY, event -> {
            double scaleDelta = 1.3d;
            double scaleFactor = 0;
            double deltaY = event.getDeltaY();
            if (deltaY < 0) {
                scaleFactor = 1 / scaleDelta;
            } else {
                scaleFactor = scaleDelta;
            }

            double scale = rect1.getScaleY() * scaleFactor;

            // Determine the resultant height of this scale.
            double scaledHeight = scale * defaultHeight;
            if(scaledHeight > maxHeight){
                // If the target height is > max, then set the scale to max height.
                scale = maxHeight/defaultHeight;
            } else if(scaledHeight < defaultHeight){
                // If the target height is < default, then set the scale to default height.
                scale = 1;
            }
            rect1.setScaleY(scale);
            rect2.setScaleY(scale);
        });

        javafx.scene.Scene s = new javafx.scene.Scene(sp);
        primaryStage.setScene(s);
        primaryStage.show();
    }
}
Sai Dandem
  • 8,229
  • 11
  • 26
  • Thanks for the answer. This perfectly suited my requirement. – Deepak Feb 10 '21 at 06:14
  • On Scaling Is there a way to prevent the rectangles reduce it's height beyond a certain value ? – Deepak Feb 10 '21 at 07:05
  • Do you mean to stop growing the height after certain value ? or to reduce/shrink the height after certain value? – Sai Dandem Feb 10 '21 at 23:58
  • Yes you are right. The objective is to ensure that the rectangle does not shrink beyond a certain height – Deepak Feb 12 '21 at 10:14
  • Updated with the demo that will limit the scaling when the max and min heights are reached. – Sai Dandem Feb 14 '21 at 09:13
  • The above solution is perfect when there exists a gap in-between the rectangle placed one below the other.But How can we address the same scenario if the rectangles are adjacent to each other. Image : https://i.stack.imgur.com/fBF62.png On scaling, the 2 rectangle needs to be proportionally placed, which means on scaling, what parameter needs to be used to calculate the new y co-ordinates for the 2nd stack pane in the above code snippet? – Deepak Feb 18 '21 at 04:41
  • If i understand you requirement correctly, you need to comment out the line "stackPane2.setLayoutY..." in the rect1.boundsInParentProperty() listener. – Sai Dandem Feb 18 '21 at 06:33
0

By removing the below line it would not really meet the proportional scaling and positioning the rectangles accordingly

stackPane2.setLayoutY..

Let me try explaining it.

In the program provided in the question asked, make the below changes

stackPane2.setLayoutX(170);

stackPane2.setLayoutY(100);

Firstly now the rectangles are adjacent to each other. However there is a portion of the rectangles which are having overlapping regions in terms of y-axis values. refer attached image

So now the question is, Once we start scaling the rectangles, the 2nd rectangle is not starting at the same proportional height as initially placed. To give more insight, find the image representing the actual vs expected

Initially the 2nd rectangle is probably placed in the lower 1/4th height of the 1st rectangle. So the expected outcome would be that on scaling the 2nd rectangle should be placed at the 1/4th height of the 1st rectangle.

Incase we remove the line stackPane2.setLayoutY.. the 2nd rectangle moves upwards and loses its y co-ordinates which was proportional initially available.

I can understand that the control lies in the line stackPane2.setLayoutY... But the problem faced is , the y axis co-ordinates are not being calculated correctly. Hence the 2nd rectangle / stack pane is not placed correctly.

So How to calculate the 2nd rectangle y co-ordinates on scaling ?

Deepak
  • 3
  • 2