Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimizing Pagination rendering

While looking at Pagination, the question of rendering complex pages arose. The API example, et al., typically specify a pageFactory that simply constructs a new control each time it is called. Indeed, profiling the example below while paging showed minimal memory pressure, with a flurry of new instances that were promptly collected. What can I do if growing complexity changes the picture?

simple page

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Pagination;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/** @see https://stackoverflow.com/q/76359690/230513 */

public class PaginationSample extends Application {

    private static final int N = 100;

    private record Item(String name, Color color) {

        private static final Random r = new Random();

        public static Item ofRandom() {
            var s = (char) ('A' + r.nextInt(26))
                + String.valueOf(r.nextInt(900_000) + 100_000);
            var c = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
            return new Item(s, c);
        }
    }

    @Override
    public void start(Stage stage) {
        stage.setTitle("PaginationTStreamest");
        List<Item> items = Stream.generate(Item::ofRandom)
            .limit(N).collect(Collectors.toList());
        var pagination = new Pagination(items.size(), 0);
        pagination.setPageFactory((i) -> createItemPane(items.get(i)));
        stage.setScene(new Scene(pagination));
        stage.show();
        pagination.requestFocus();
    }

    private StackPane createItemPane(Item item) {
        var pane = new StackPane();
        pane.setPadding(new Insets(16));
        var label = new Label(item.name, new Rectangle(320, 240, item.color));
        label.setTextFill(item.color.invert());
        label.setStyle("-fx-font-family: serif; -fx-font-size: 36;");
        label.setContentDisplay(ContentDisplay.CENTER);
        var button = new Button("Button");
        StackPane.setAlignment(button, Pos.BOTTOM_RIGHT);
        button.setOnAction((e) -> {
            System.out.println(item);
        });
        pane.getChildren().addAll(label, button);
        return pane;
    }

    public static void main(String[] args) {
        launch(args);
    }
}
like image 831
trashgod Avatar asked Oct 15 '25 19:10

trashgod


1 Answers

Profiling is the correct thing to do in this case, but optimizing the renderer's preparation is probably warranted. Like ListView and TableView, Pagination makes flyweight rendering easy:

  • Construct the minimal number of reusable display objects.

  • When called to render, update existing objects to effect a change.

In the RenderPane below, init() establishes a single pane, chart and button, while the fill() method invoked by the page factory's callback prepares the renderer with details of a specific Item for each page. Note that a single chart and data collection suffices; the listening chart will update itself in response to the change.

An ad hoc constructor and factory invocation in comments may be used for comparison. The benefit is measurable, suggesting it should scale well.

A related chart example is seen here, and additional Pagination topics are addressed here.

Caveats: As noted in comments by @Slaw below, the PaginationSkin animates the transition from one page to the next. With just a single view component, the animation is serviceable but less appealing. Moreover, the animation may interfere with subsequent updates. Among possible accommodations that may be required:

  • Disable the component's animation, shown in the example below:

      chart.setAnimated(false);
    
  • Schedule the the component's update to follow the animation.

      public Pane fill(int i, Item item) {
          Platform.runLater(() -> {
              …
          });
          return pane;
    

Complex page

PaginationTest.java

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.control.Pagination;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

/** @see https://stackoverflow.com/a/76359691/230513 */
public class PaginationTest extends Application {

    private static final int N = 100;

    private record Item(String name, Color color) {

        private static final Random r = new Random();

        public static Item ofRandom() {
            var s = (char) ('A' + r.nextInt(26))
                + String.valueOf(r.nextInt(900_000) + 100_000);
            var c = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
            return new Item(s, c);
        }
    }

    @Override
    public void start(Stage stage) {
        stage.setTitle("PaginationTest");
        List<Item> items = Stream.generate(Item::ofRandom)
            .limit(N).collect(Collectors.toList());
        var pagination = new Pagination(items.size(), 0);
        var renderer = new RenderPane();
        pagination.setPageFactory((i) -> renderer.fill(items.get(i)));
//        pagination.setPageFactory((i) -> {
//            var renderer = new RenderPane(items.get(i));
//            return renderer.pane;
//        });
        stage.setScene(new Scene(pagination));
        stage.show();
        pagination.requestFocus();
    }

    private final static class RenderPane {

        private final StackPane pane = new StackPane();
        private final PieChart chart = new PieChart();
        private final Circle circle = new Circle(10);
        private final Button button = new Button("Details", circle);

        public RenderPane() {
            init();
        }

//        public RenderPane(Item item) {
//            init();
//            fill(item);
//        }

        private void init() {
            pane.setPadding(new Insets(16));
            StackPane.setAlignment(button, Pos.BOTTOM_RIGHT);
            chart.setAnimated(false);
            chart.getData().add(0, new PieChart.Data("Red", 0));
            chart.getData().add(1, new PieChart.Data("Green", 0));
            chart.getData().add(2, new PieChart.Data("Blue", 0));
            chart.getStylesheets().add("PaginationTest.css");
            button.setGraphic(circle);
            pane.getChildren().addAll(chart, button);
        }

        public Pane fill(Item item) {
            chart.setTitle(item.name);
            chart.getData().get(0).setPieValue(item.color.getRed());
            chart.getData().get(1).setPieValue(item.color.getGreen());
            chart.getData().get(2).setPieValue(item.color.getBlue());
            circle.setFill(item.color);
            button.setOnAction((e) -> {
                System.out.println(item);
            });
            return pane;
        }
    }

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

PaginationTest.css

PaginationTest.css 
.default-color0.chart-pie { -fx-pie-color: #FF0000; }
.default-color1.chart-pie { -fx-pie-color: #00FF00; }
.default-color2.chart-pie { -fx-pie-color: #0000FF; }
like image 106
trashgod Avatar answered Oct 18 '25 08:10

trashgod



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!