Принудительное ретрансляция Java FX Chart программно - PullRequest
2 голосов
/ 28 апреля 2020

Я внес программные c изменения в Java Линейную диаграмму FX, и мне нужен программный c способ заставить перепланировку JavaFX Chart произойти. Этот вопрос задавался / отвечался ранее, но не в моем контексте.

Я попробовал типичные методы, которые были представлены в качестве ответов на этот вопрос (см. Полный, минимальный пример кода ниже со встроенными попытками решения проблемы). Ни одно из типичных решений этой проблемы не работает.

В частности (sp является StackPane):

    sp.requestLayout(); // does not work

и

    sp.applyCss(); 
    sp.layout();        // does not work

, помещая вышеуказанный код в .runLater() не работает.

Я знаю, что мои изменения присутствуют в диаграмме, потому что (1) Когда я вручную изменяю размер диаграммы, внезапно появляются мои изменения (2) Когда я использую метод "изменения размера" программно, мой появляются изменения, НО есть другая ошибка (плюс предполагается, что метод «изменить размер» должны использовать только родительские узлы, а не мы, программисты).

Ниже приведен минимальный полный набор кода, который воспроизводит проблему. Когда вы запускаете код, я программно изменяю одну из точек данных на большую, когда отображается диаграмма. Этот размер работает правильно. Когда вы щелкаете правой кнопкой мыши на графике, появляется контекстное меню с одним выбором («Изменить все точки»). Когда вы выбираете эту единственную опцию, мой код изменяет размеры всех точек - НО - ни одна из точек данных не изменяется визуально. Если я вручную изменяю размер диаграммы путем перетаскивания стороны, диаграмма выполняет повторную компоновку, и все размеры узлов данных немедленно визуально изменяются на правильный размер (размер, который я программно установил для них).

Как я могу заставить перепланировку произойти программно, что я могу сделать вручную? Я не хотел бы делать взлом (например, программно установить размер сцены на 1 пиксель меньше, а затем установить его на один пиксель больше).

Примечание: я читал, что попытки сделать requestLayout() во время разработки макета будут игнорироваться, поэтому возможно что-то подобное происходит. Я думаю, что requestLayout() внутри runLater() решит проблему текущего Layout (), но это тоже не сработало.

Обновление: В качестве альтернативы было предложено масштабирование изменение размера StackPane. Это решение может быть полезно для некоторых, но не для меня. Внешний вид и масштабирование символа отличаются от внешнего вида, когда вы меняете размер областей и позволяете «символу» увеличиваться в этом размере.

В завершение это мой первый пост stackoverflow. Поэтому спасибо за все предыдущие примеры, которые я использовал на этом форуме в прошлом, и заранее благодарю за ответ на эту проблему.

import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.LineChart.SortingPolicy;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;


public class dummy  extends Application {

    @Override
    public void start(Stage primaryStage) {
        Random random = new Random();
        final NumberAxis xAxis = new NumberAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("X");
        yAxis.setLabel("Y");
        final LineChart<Number,Number> lineChart = new LineChart<Number,Number>(xAxis,yAxis);   

        Series<Number,Number> series = new Series<Number,Number>();
        series.setName("Dummy Data");
        // Generate data
        double x = 0.0;     
        double y = 0.0;
        for (int i = 0; i < 10; i++) {
            Data<Number,Number> data  = new Data(x += random.nextDouble(), y+=random.nextDouble());
            series.getData().add(data);
        }
        lineChart.getData().add(series);
        lineChart.setTitle("Random Data");
        lineChart.setAxisSortingPolicy(SortingPolicy.NONE); 

        Scene scene = new Scene(lineChart,1200,600);

        Stage stage = new Stage();
        stage.setScene(scene);
        stage.show();

        // This resizes the first data point directly (this resize is displayed correctly when program is run)
        Node node = series.getData().get(0).getNode();
        setSize((StackPane)node,20);

        // The context menu is invoked by a right click on the line Chart.  It will resize the data point based on a context menu pick
        // this resize does not work....unless I resize the window manually which causes a refresh/re-layout of the chart).
        lineChart.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent mouseEvent) {
                if (MouseButton.SECONDARY.equals(mouseEvent.getButton())) {
                    Scene scene = ((Node)mouseEvent.getSource()).getScene();
                    ContextMenu menu = createMenu(lineChart);
                    menu.show(scene.getWindow(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
                }  
            }
        });
    }

    private void setSize(StackPane sp, int size) {
        sp.setMinSize(size, size);
        sp.setMaxSize(size, size);
        sp.setPrefSize(size, size);
    }
    // this creates a context menu that will allow you to resize all the data point nodes
    private ContextMenu createMenu(LineChart<Number,Number> lineChart) {

        final ContextMenu contextMenu = new ContextMenu();

        final MenuItem resize = new MenuItem("Resize ALL the points");
        resize.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                for (Series<Number, Number> series : lineChart.getData()) {
                    for (Data<Number, Number> data : series.getData()) {
                        StackPane sp = (StackPane)data.getNode();
                        setSize (sp, 20);
                        // The above resizes do not take effect unless/until I manually resize the chart.

                        // the following two calls do not do anything;
                        sp.applyCss();
                        sp.layout();

                        // The request to layout the node does nothing
                        sp.requestLayout();

                        // Doing both of the above as runLaters does nothing
                        Platform.runLater(()->{sp.applyCss();sp.layout();});
                        Platform.runLater(()->{sp.requestLayout();});

                        // Going after the parent does nothing either
                        Group group = (Group)sp.getParent();
                        group.applyCss();
                        group.layout();
                        group.requestLayout();

                        // Going after the parent in a run later does nothing
                        Platform.runLater(()->{
                            group.applyCss();
                            group.layout();
                            group.requestLayout();
                        });

                        // note... doing a resize [commented out below] will work-ish.
                        // The documentation says NOT to use it thought that as it is for internal use only.
                        // By work-ish, the data points are enlarged BUT they are no longer centered on the data point
                        // When I resize the stage they get centered again - so this "solves" my original problem but causes a different problem
                        ////////////////////////////////////
                        //  sp.resize(20, 20);            // Uncomment this line to see how it mostly works but introduces a new issue
                        ////////////////////////////////////
                    }
                }
            }
        });
        contextMenu.getItems().add(resize);
        return contextMenu;
    }

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

Ответы [ 3 ]

1 голос
/ 28 апреля 2020

Вы можете принудительно выполнить ретрансляцию, используя, например, внутренний класс

  class LineChartX<X, Y> extends LineChart<X, Y>
  {
    public LineChartX(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis)
    {
      super(xAxis, yAxis);
    }

    @Override
    public void layoutPlotChildren()
    {
      super.layoutPlotChildren();
    }
  }

и вызывая

lineChart.layoutPlotChildren();

в своем действии меню.

Простой -line Решение:

узлы в графе сцены LineChart имеют следующие родительско-дочерние отношения: Pane chartContent - Group plotArea - Group plotContent - Path seriesLine;

определены запросы макета для группы plotArea, определенные в классе XYChart подавляются:

private final Group plotArea = new Group(){
  @Override public void requestLayout() {} // suppress layout requests
};

, но Pane chartContent принимает запросы макета:

Node node = series.getNode();
if (node instanceof Path) {
  Path seriesLine = (Path) node;
  Parent parent = seriesLine.getParent();
  if (parent instanceof Group) {
    Group plotContent = (Group) parent;
    parent = plotContent.getParent();
    if (parent instanceof Group) {
      Group plotArea = (Group) parent;
      parent = plotArea.getParent();
      if (parent instanceof Pane) {
        Pane chartContent = (Pane) parent;
        chartContent.requestLayout();
      }
    }
  }
}

, поэтому ретрансляцию вашей диаграммы можно принудительно добавить, добавив эту единственную строку

series.getNode().getParent().getParent().getParent().requestLayout();

до конца вашего обработчика действий меню.

0 голосов
/ 28 апреля 2020

@ c0der опубликовал решение в форме комментария к моему исходному сообщению, которое сработало, но выдало предупреждение во время выполнения (в Eclipse). Его решением было добавить фиктивную таблицу стилей на уровне lineChart с lineChart.getStylesheets().add(""); после окончания цикла for. Этот код выдает предупреждение «28 апреля 2020 г. 9:01:12 com.sun.javafx. css .StyleManager loadStylesheetUnPrivileged ПРЕДУПРЕЖДЕНИЕ: Ресурс" "не найден".

Что сработало без запуска предупреждением было загрузить пустой файл. css и добавить его в виде таблицы стилей:

lineChart.getStylesheets().add(CSS.class.getResource("Empty.css").toExternalForm()); // note: I keep my .css resource files at the same location as my CSS class // which is why I have the code "CSS.class" above

Это однострочное решение работало один раз, но я сомневался, что оно будет работать несколько раз. Я проверил это, увеличив размер StackPane на 5 для каждого последующего времени, когда было выбрано «Изменить размер ВСЕХ точек» (в моем фиктивном коде выше). Конечно же, это сработало только в первый раз.

Однако я добавил код no-op lineChart.getStylesheets().replaceAll((s)->s+" "); перед этим однострочным решением, и теперь он работает несколько раз подряд. Независимо от того, сколько раз я выполнял две строки кода

lineChart.getStylesheets().replaceAll((s)->s+" ");
lineChart.getStylesheets().add(CSS.class.getResource("Empty.css").toExternalForm()); ` 

, он (1) работал и (2) размер списка lineChart StyleSheets не превышал размер 1. Поэтому решение с загадкой

Примечание: если у вас есть существующая таблица стилей (я не использовал ее в своем фиктивном примере выше), lineChart.getStylesheets().replaceAll((s)->s+" "); сама по себе также может работать. По какой-то причине lineChart.getStylesheets().replaceAll((s)->s); без добавления "" на конце не сработало.

Примечание: Первоначально я думал, что мне придется кодировать решение переключения, чтобы добавить Empty. css и удалить Empty. css альтернативными вызовами, но это оказалось ненужным.

Итог: при наличии существующей таблицы стилей lineChart.getStylesheets().replaceAll((s)->s+" "); работает. Если у вас нет существующей таблицы стилей, добавление пустого файла. css в качестве таблицы стилей в сочетании с вышеупомянутым replaceAll работает.

Еще раз спасибо @ c0der за его оригинальный подход.

0 голосов
/ 28 апреля 2020

Вам не нужно приводить этот узел в StackPane и устанавливать размеры. Вам нужно использовать setScaleX() и setScaleY() методы

    Node node = series.getData().get(0).getNode();
    node.setScaleX(20);
    node.setScaleY(20);

    @Override
    public void handle(ActionEvent event) {
        for (Series<Number, Number> series : lineChart.getData()) {
            for (Data<Number, Number> data : series.getData()) {
                Node node = data.getNode();
                node.setScaleY(20);
                node.setScaleX(20);
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...